From def60e21c75dc97560d35ab18cd94ce0e80cfdc6 Mon Sep 17 00:00:00 2001 From: wxy <3050128610@qq.com> Date: Fri, 6 Mar 2026 14:01:32 +0800 Subject: [PATCH] first commit --- .gitignore | 35 + README.md | 53 + orion-ops-api/orion-ops-common/pom.xml | 36 + .../ops/annotation/DemoDisableApi.java | 10 + .../cn/orionsec/ops/annotation/EventLog.java | 19 + .../orionsec/ops/annotation/IgnoreAuth.java | 11 + .../orionsec/ops/annotation/IgnoreCheck.java | 11 + .../cn/orionsec/ops/annotation/IgnoreLog.java | 12 + .../ops/annotation/IgnoreWrapper.java | 11 + .../orionsec/ops/annotation/RequireRole.java | 21 + .../orionsec/ops/annotation/RestWrapper.java | 12 + .../cn/orionsec/ops/constant/CnConst.java | 52 + .../java/cn/orionsec/ops/constant/Const.java | 184 + .../cn/orionsec/ops/constant/KeyConst.java | 156 + .../orionsec/ops/constant/MessageConst.java | 231 + .../cn/orionsec/ops/constant/ParamConst.java | 14 + .../ops/constant/PropertiesConst.java | 49 + .../cn/orionsec/ops/constant/ResultCode.java | 63 + .../orionsec/ops/constant/SchedulerPools.java | 245 + .../constant/alarm/AlarmGroupNotifyType.java | 33 + .../ops/constant/app/ActionStatus.java | 58 + .../orionsec/ops/constant/app/ActionType.java | 104 + .../ops/constant/app/ApplicationEnvAttr.java | 85 + .../ops/constant/app/BuildStatus.java | 52 + .../constant/app/PipelineDetailStatus.java | 58 + .../ops/constant/app/PipelineLogStatus.java | 84 + .../ops/constant/app/PipelineStatus.java | 68 + .../ops/constant/app/ReleaseStatus.java | 68 + .../ops/constant/app/ReleaseType.java | 37 + .../ops/constant/app/RepositoryAuthType.java | 52 + .../ops/constant/app/RepositoryStatus.java | 51 + .../ops/constant/app/RepositoryTokenType.java | 64 + .../ops/constant/app/RepositoryType.java | 33 + .../orionsec/ops/constant/app/StageType.java | 39 + .../orionsec/ops/constant/app/TimedType.java | 38 + .../ops/constant/app/TransferFileType.java | 39 + .../ops/constant/app/TransferMode.java | 39 + .../ops/constant/command/CommandConst.java | 24 + .../ops/constant/command/ExecStatus.java | 53 + .../ops/constant/command/ExecType.java | 32 + .../ops/constant/common/AuditStatus.java | 37 + .../ops/constant/common/EnableType.java | 51 + .../constant/common/ExceptionHandlerType.java | 52 + .../ops/constant/common/SerialType.java | 50 + .../ops/constant/common/StainCode.java | 245 + .../ops/constant/common/TreeMoveType.java | 46 + .../constant/download/FileDownloadType.java | 98 + .../orionsec/ops/constant/env/EnvConst.java | 99 + .../ops/constant/env/EnvViewType.java | 92 + .../ops/constant/event/EventClassify.java | 171 + .../ops/constant/event/EventKeys.java | 268 + .../ops/constant/event/EventType.java | 720 ++ .../ops/constant/history/HistoryOperator.java | 41 + .../constant/history/HistoryValueType.java | 41 + .../constant/machine/MachineAlarmType.java | 38 + .../ops/constant/machine/MachineAuthType.java | 51 + .../ops/constant/machine/MachineConst.java | 20 + .../ops/constant/machine/MachineEnvAttr.java | 89 + .../ops/constant/machine/ProxyType.java | 56 + .../ops/constant/message/MessageClassify.java | 43 + .../ops/constant/message/MessageType.java | 133 + .../ops/constant/message/ReadStatus.java | 39 + .../ops/constant/monitor/MonitorConst.java | 86 + .../ops/constant/monitor/MonitorStatus.java | 41 + .../scheduler/SchedulerTaskMachineStatus.java | 56 + .../scheduler/SchedulerTaskStatus.java | 51 + .../ops/constant/sftp/SftpNotifyType.java | 41 + .../ops/constant/sftp/SftpPackageType.java | 41 + .../ops/constant/sftp/SftpTransferStatus.java | 56 + .../ops/constant/sftp/SftpTransferType.java | 48 + .../ops/constant/system/SystemCleanType.java | 58 + .../ops/constant/system/SystemConfigKey.java | 256 + .../ops/constant/system/SystemEnvAttr.java | 197 + .../system/ThreadPoolMetricsType.java | 89 + .../ops/constant/tail/FileTailMode.java | 59 + .../ops/constant/tail/FileTailType.java | 72 + .../terminal/TerminalClientOperate.java | 65 + .../ops/constant/terminal/TerminalConst.java | 40 + .../orionsec/ops/constant/user/RoleType.java | 56 + .../ops/constant/webhook/WebhookType.java | 45 + .../orionsec/ops/constant/ws/WsCloseCode.java | 130 + .../ops/constant/ws/WsCloseReason.java | 27 + .../orionsec/ops/constant/ws/WsProtocol.java | 73 + .../cn/orionsec/ops/utils/AttrConverts.java | 165 + .../orionsec/ops/utils/AvatarPicHolder.java | 89 + .../cn/orionsec/ops/utils/EventLogUtils.java | 24 + .../cn/orionsec/ops/utils/FileCleaner.java | 116 + .../cn/orionsec/ops/utils/PathBuilders.java | 286 + .../cn/orionsec/ops/utils/ResourceLoader.java | 40 + .../java/cn/orionsec/ops/utils/Utils.java | 263 + .../java/cn/orionsec/ops/utils/Valid.java | 110 + .../java/cn/orionsec/ops/utils/ValueMix.java | 208 + .../ops/constant/event/AddLicenseHeader.java | 83 + .../ops/constant/event/EventClassifyTest.java | 37 + .../ops/constant/event/EventTypeTest.java | 40 + .../ops/constant/event/ReplaceVersion.java | 103 + orion-ops-api/orion-ops-dao/pom.xml | 68 + .../cn/orionsec/ops/dao/AlarmGroupDAO.java | 9 + .../orionsec/ops/dao/AlarmGroupNotifyDAO.java | 9 + .../orionsec/ops/dao/AlarmGroupUserDAO.java | 8 + .../ops/dao/ApplicationActionDAO.java | 21 + .../ops/dao/ApplicationActionLogDAO.java | 45 + .../orionsec/ops/dao/ApplicationBuildDAO.java | 94 + .../orionsec/ops/dao/ApplicationEnvDAO.java | 28 + .../orionsec/ops/dao/ApplicationInfoDAO.java | 43 + .../ops/dao/ApplicationMachineDAO.java | 16 + .../ops/dao/ApplicationPipelineDAO.java | 8 + .../ops/dao/ApplicationPipelineDetailDAO.java | 8 + .../ops/dao/ApplicationPipelineTaskDAO.java | 55 + .../dao/ApplicationPipelineTaskDetailDAO.java | 27 + .../dao/ApplicationPipelineTaskLogDAO.java | 8 + .../ops/dao/ApplicationProfileDAO.java | 8 + .../ops/dao/ApplicationReleaseDAO.java | 57 + .../ops/dao/ApplicationReleaseMachineDAO.java | 43 + .../ops/dao/ApplicationRepositoryDAO.java | 27 + .../cn/orionsec/ops/dao/CommandExecDAO.java | 17 + .../orionsec/ops/dao/CommandTemplateDAO.java | 8 + .../cn/orionsec/ops/dao/FileTailListDAO.java | 8 + .../orionsec/ops/dao/FileTransferLogDAO.java | 8 + .../ops/dao/HistoryValueSnapshotDAO.java | 8 + .../ops/dao/MachineAlarmConfigDAO.java | 8 + .../ops/dao/MachineAlarmGroupDAO.java | 8 + .../ops/dao/MachineAlarmHistoryDAO.java | 8 + .../cn/orionsec/ops/dao/MachineEnvDAO.java | 27 + .../cn/orionsec/ops/dao/MachineGroupDAO.java | 26 + .../orionsec/ops/dao/MachineGroupRelDAO.java | 8 + .../cn/orionsec/ops/dao/MachineInfoDAO.java | 58 + .../orionsec/ops/dao/MachineMonitorDAO.java | 30 + .../cn/orionsec/ops/dao/MachineProxyDAO.java | 8 + .../orionsec/ops/dao/MachineSecretKeyDAO.java | 19 + .../orionsec/ops/dao/MachineTerminalDAO.java | 8 + .../ops/dao/MachineTerminalLogDAO.java | 8 + .../cn/orionsec/ops/dao/SchedulerTaskDAO.java | 8 + .../ops/dao/SchedulerTaskMachineDAO.java | 8 + .../dao/SchedulerTaskMachineRecordDAO.java | 43 + .../ops/dao/SchedulerTaskRecordDAO.java | 47 + .../cn/orionsec/ops/dao/SystemEnvDAO.java | 26 + .../cn/orionsec/ops/dao/UserEventLogDAO.java | 8 + .../java/cn/orionsec/ops/dao/UserInfoDAO.java | 17 + .../orionsec/ops/dao/WebSideMessageDAO.java | 8 + .../cn/orionsec/ops/dao/WebhookConfigDAO.java | 8 + .../ops/entity/domain/AlarmGroupDO.java | 45 + .../ops/entity/domain/AlarmGroupNotifyDO.java | 48 + .../ops/entity/domain/AlarmGroupUserDO.java | 48 + .../entity/domain/ApplicationActionDO.java | 67 + .../entity/domain/ApplicationActionLogDO.java | 94 + .../ops/entity/domain/ApplicationBuildDO.java | 112 + .../ops/entity/domain/ApplicationEnvDO.java | 65 + .../ops/entity/domain/ApplicationInfoDO.java | 56 + .../entity/domain/ApplicationMachineDO.java | 60 + .../entity/domain/ApplicationPipelineDO.java | 48 + .../domain/ApplicationPipelineDetailDO.java | 56 + .../domain/ApplicationPipelineTaskDO.java | 120 + .../ApplicationPipelineTaskDetailDO.java | 95 + .../domain/ApplicationPipelineTaskLogDO.java | 64 + .../entity/domain/ApplicationProfileDO.java | 59 + .../entity/domain/ApplicationReleaseDO.java | 181 + .../domain/ApplicationReleaseMachineDO.java | 78 + .../domain/ApplicationRepositoryDO.java | 91 + .../ops/entity/domain/CommandExecDO.java | 101 + .../ops/entity/domain/CommandTemplateDO.java | 66 + .../ops/entity/domain/FileTailListDO.java | 70 + .../ops/entity/domain/FileTransferLogDO.java | 89 + .../entity/domain/HistoryValueSnapshotDO.java | 72 + .../entity/domain/MachineAlarmConfigDO.java | 61 + .../entity/domain/MachineAlarmGroupDO.java | 46 + .../entity/domain/MachineAlarmHistoryDO.java | 55 + .../ops/entity/domain/MachineEnvDO.java | 53 + .../ops/entity/domain/MachineGroupDO.java | 50 + .../ops/entity/domain/MachineGroupRelDO.java | 45 + .../ops/entity/domain/MachineInfoDO.java | 90 + .../ops/entity/domain/MachineMonitorDO.java | 61 + .../ops/entity/domain/MachineProxyDO.java | 65 + .../ops/entity/domain/MachineSecretKeyDO.java | 53 + .../ops/entity/domain/MachineTerminalDO.java | 85 + .../entity/domain/MachineTerminalLogDO.java | 84 + .../ops/entity/domain/SchedulerTaskDO.java | 87 + .../entity/domain/SchedulerTaskMachineDO.java | 45 + .../domain/SchedulerTaskMachineRecordDO.java | 94 + .../entity/domain/SchedulerTaskRecordDO.java | 62 + .../ops/entity/domain/SystemEnvDO.java | 59 + .../ops/entity/domain/UserEventLogDO.java | 73 + .../ops/entity/domain/UserInfoDO.java | 98 + .../ops/entity/domain/WebSideMessageDO.java | 76 + .../ops/entity/domain/WebhookConfigDO.java | 57 + .../dto/ApplicationActionConfigDTO.java | 22 + .../dto/ApplicationBuildStatisticsDTO.java | 29 + .../ApplicationPipelineTaskStatisticsDTO.java | 30 + .../dto/ApplicationReleaseStatisticsDTO.java | 29 + .../ops/entity/dto/MachineMonitorDTO.java | 45 + .../dto/SchedulerTaskRecordStatisticsDTO.java | 35 + .../ops/entity/query/MachineMonitorQuery.java | 25 + .../cn/orionsec/ops/utils/CodeGenerator.java | 190 + .../java/cn/orionsec/ops/utils/DataQuery.java | 99 + .../resources/mapper/AlarmGroupMapper.xml | 21 + .../mapper/AlarmGroupNotifyMapper.xml | 22 + .../resources/mapper/AlarmGroupUserMapper.xml | 22 + .../mapper/ApplicationActionLogMapper.xml | 69 + .../mapper/ApplicationActionMapper.xml | 44 + .../mapper/ApplicationBuildMapper.xml | 160 + .../resources/mapper/ApplicationEnvMapper.xml | 40 + .../mapper/ApplicationInfoMapper.xml | 54 + .../mapper/ApplicationMachineMapper.xml | 35 + .../ApplicationPipelineDetailMapper.xml | 22 + .../mapper/ApplicationPipelineMapper.xml | 21 + .../ApplicationPipelineTaskDetailMapper.xml | 51 + .../ApplicationPipelineTaskLogMapper.xml | 23 + .../mapper/ApplicationPipelineTaskMapper.xml | 115 + .../mapper/ApplicationProfileMapper.xml | 22 + .../ApplicationReleaseMachineMapper.xml | 68 + .../mapper/ApplicationReleaseMapper.xml | 130 + .../mapper/ApplicationRepositoryMapper.xml | 49 + .../resources/mapper/CommandExecMapper.xml | 40 + .../mapper/CommandTemplateMapper.xml | 26 + .../resources/mapper/FileTailListMapper.xml | 25 + .../mapper/FileTransferLogMapper.xml | 29 + .../mapper/HistoryValueSnapshotMapper.xml | 24 + .../mapper/MachineAlarmConfigMapper.xml | 24 + .../mapper/MachineAlarmGroupMapper.xml | 21 + .../mapper/MachineAlarmHistoryMapper.xml | 23 + .../resources/mapper/MachineEnvMapper.xml | 36 + .../resources/mapper/MachineGroupMapper.xml | 37 + .../mapper/MachineGroupRelMapper.xml | 21 + .../resources/mapper/MachineInfoMapper.xml | 73 + .../resources/mapper/MachineMonitorMapper.xml | 83 + .../resources/mapper/MachineProxyMapper.xml | 23 + .../mapper/MachineSecretKeyMapper.xml | 34 + .../mapper/MachineTerminalLogMapper.xml | 28 + .../mapper/MachineTerminalMapper.xml | 26 + .../mapper/ScheduleTaskMachineMapper.xml | 20 + .../ScheduleTaskMachineRecordMapper.xml | 73 + .../resources/mapper/ScheduleTaskMapper.xml | 27 + .../mapper/ScheduleTaskRecordMapper.xml | 111 + .../main/resources/mapper/SystemEnvMapper.xml | 35 + .../resources/mapper/UserEventLogMapper.xml | 24 + .../main/resources/mapper/UserInfoMapper.xml | 37 + .../resources/mapper/WebSideMessageMapper.xml | 26 + .../resources/mapper/WebhookConfigMapper.xml | 23 + orion-ops-api/orion-ops-data/pom.xml | 30 + .../ops/DataModuleConversionProvider.java | 5 + .../orionsec/ops/constant/DataClearRange.java | 43 + .../orionsec/ops/constant/DataClearType.java | 70 + .../cn/orionsec/ops/constant/ExportConst.java | 45 + .../cn/orionsec/ops/constant/ExportType.java | 124 + .../cn/orionsec/ops/constant/ImportType.java | 150 + .../entity/exporter/ApplicationExportDTO.java | 50 + .../exporter/ApplicationProfileExportDTO.java | 49 + .../ApplicationRepositoryExportDTO.java | 93 + .../exporter/CommandTemplateExportDTO.java | 41 + .../entity/exporter/EventLogExportDTO.java | 76 + .../MachineAlarmHistoryExportDTO.java | 62 + .../entity/exporter/MachineInfoExportDTO.java | 88 + .../exporter/MachineProxyExportDTO.java | 70 + .../exporter/MachineTailFileExportDTO.java | 63 + .../exporter/MachineTerminalLogExportDTO.java | 75 + .../ops/entity/exporter/WebhookExportDTO.java | 45 + .../entity/importer/ApplicationImportDTO.java | 56 + .../importer/ApplicationProfileImportDTO.java | 55 + .../ApplicationRepositoryImportDTO.java | 109 + .../entity/importer/BaseDataImportDTO.java | 19 + .../importer/CommandTemplateImportDTO.java | 49 + .../ops/entity/importer/DataImportDTO.java | 50 + .../entity/importer/MachineInfoImportDTO.java | 106 + .../importer/MachineProxyImportDTO.java | 86 + .../importer/MachineTailFileImportDTO.java | 73 + .../ops/entity/importer/WebhookImportDTO.java | 57 + .../exporter/AbstractDataExporter.java | 105 + .../exporter/AppProfileDataExporter.java | 30 + .../exporter/AppRepositoryDataExporter.java | 35 + .../exporter/ApplicationDataExporter.java | 56 + .../exporter/CommandTemplateDataExporter.java | 30 + .../ops/handler/exporter/IDataExporter.java | 59 + .../MachineAlarmHistoryDataExporter.java | 67 + .../exporter/MachineInfoDataExporter.java | 59 + .../exporter/MachineProxyDataExporter.java | 35 + .../exporter/TailFileDataExporter.java | 70 + .../exporter/TerminalLogDataExporter.java | 44 + .../exporter/UserEventLogDataExporter.java | 58 + .../handler/exporter/WebhookDataExporter.java | 43 + .../importer/checker/AbstractDataChecker.java | 261 + .../checker/AppProfileDataChecker.java | 37 + .../checker/AppRepositoryDataChecker.java | 37 + .../checker/ApplicationDataChecker.java | 49 + .../checker/CommandTemplateDataChecker.java | 37 + .../importer/checker/IDataChecker.java | 48 + .../checker/MachineInfoDataChecker.java | 49 + .../checker/MachineProxyDataChecker.java | 26 + .../importer/checker/TailFileDataChecker.java | 49 + .../importer/checker/WebhookDataChecker.java | 37 + .../importer/impl/AbstractDataImporter.java | 227 + .../importer/impl/AppProfileDataImporter.java | 26 + .../impl/AppRepositoryDataImporter.java | 41 + .../impl/ApplicationDataImporter.java | 26 + .../impl/CommandTemplateDataImporter.java | 31 + .../handler/importer/impl/IDataImporter.java | 45 + .../impl/MachineInfoDataImporter.java | 39 + .../impl/MachineProxyDataImporter.java | 17 + .../importer/impl/TailFileDataImporter.java | 17 + .../importer/impl/WebhookDataImporter.java | 17 + .../ApplicationProfileValidator.java | 65 + .../ApplicationRepositoryValidator.java | 126 + .../validator/ApplicationValidator.java | 57 + .../validator/CommandTemplateValidator.java | 55 + .../importer/validator/DataValidator.java | 13 + .../importer/validator/FileTailValidator.java | 87 + .../validator/MachineProxyValidator.java | 98 + .../importer/validator/MachineValidator.java | 118 + .../importer/validator/WebhookValidator.java | 58 + .../ops/service/api/DataClearService.java | 72 + .../ops/service/api/DataImportService.java | 23 + .../service/impl/DataClearServiceImpl.java | 286 + .../service/impl/DataImportServiceImpl.java | 48 + .../import/app-profile-import-template.xlsx | Bin 0 -> 9276 bytes .../app-repository-import-template.xlsx | Bin 0 -> 9450 bytes .../import/application-import-template.xlsx | Bin 0 -> 9126 bytes .../command-template-import-template.xlsx | Bin 0 -> 9099 bytes .../import/machine-import-template.xlsx | Bin 0 -> 9639 bytes .../import/machine-proxy-import-template.xlsx | Bin 0 -> 9360 bytes .../import/tail-file-import-template.xlsx | Bin 0 -> 9285 bytes .../import/webhook-import-template.xlsx | Bin 0 -> 9824 bytes orion-ops-api/orion-ops-mapping/pom.xml | 38 + .../ops/MappingConversionProvider.java | 33 + .../mapping/alarm/AlarmGroupConversion.java | 33 + .../app/ApplicationActionConversion.java | 99 + .../app/ApplicationBuildConversion.java | 123 + .../mapping/app/ApplicationConversion.java | 37 + .../mapping/app/ApplicationEnvConversion.java | 28 + .../app/ApplicationMachineConversion.java | 22 + .../app/ApplicationPipelineConversion.java | 48 + .../ApplicationPipelineTaskConversion.java | 276 + .../app/ApplicationProfileConversion.java | 44 + .../app/ApplicationReleaseConversion.java | 224 + .../app/ApplicationRepositoryConversion.java | 57 + .../mapping/exec/CommandExecConversion.java | 62 + .../ops/mapping/file/FileTailConversion.java | 42 + .../file/FileTransferLogConversion.java | 27 + .../history/HistoryValueConversion.java | 26 + .../mapping/home/StatisticsConversion.java | 21 + .../machine/MachineAlarmConversion.java | 36 + .../mapping/machine/MachineConversion.java | 49 + .../mapping/machine/MachineEnvConversion.java | 28 + .../machine/MachineGroupConversion.java | 21 + .../machine/MachineMonitorConversion.java | 42 + .../machine/MachineProxyConversion.java | 24 + .../machine/MachineSecretKeyConversion.java | 22 + .../machine/MachineTerminalConversion.java | 77 + .../message/WebSideMessageConversion.java | 28 + .../scheduler/SchedulerTaskConversion.java | 35 + .../SchedulerTaskRecordConversion.java | 157 + .../ops/mapping/sftp/SftpConversion.java | 46 + .../system/SystemAnalysisConversion.java | 29 + .../mapping/system/SystemEnvConversion.java | 27 + .../template/CommandTemplateConversion.java | 30 + .../mapping/upload/BatchUploadConversion.java | 42 + .../mapping/user/UserEventLogConversion.java | 28 + .../ops/mapping/user/UserInfoConversion.java | 45 + .../webhook/WebhookConfigConversion.java | 22 + orion-ops-api/orion-ops-model/pom.xml | 41 + .../entity/config/TerminalConnectConfig.java | 50 + .../ApplicationPipelineStageConfigDTO.java | 32 + .../entity/dto/app/ApplicationProfileDTO.java | 21 + .../ops/entity/dto/file/FileDownloadDTO.java | 31 + .../ops/entity/dto/file/FileTailDTO.java | 46 + .../dto/sftp/FileTransferNotifyDTO.java | 25 + .../sftp/FileTransferNotifyProgressDTO.java | 23 + .../entity/dto/sftp/SftpSessionTokenDTO.java | 23 + .../entity/dto/sftp/SftpUploadInfoDTO.java | 26 + .../dto/statistic/StatisticsCountDTO.java | 24 + .../dto/system/SystemSpaceAnalysisDTO.java | 48 + .../dto/terminal/TerminalConnectDTO.java | 22 + .../entity/dto/terminal/TerminalSizeDTO.java | 18 + .../dto/terminal/TerminalWatcherDTO.java | 31 + .../ops/entity/dto/user/LoginBindDTO.java | 22 + .../orionsec/ops/entity/dto/user/UserDTO.java | 45 + .../alarm/AlarmGroupNotifyRequest.java | 26 + .../request/alarm/AlarmGroupRequest.java | 32 + .../app/AppBuildStatisticsRequest.java | 19 + .../app/AppPipelineTaskStatisticsRequest.java | 15 + .../app/AppReleaseStatisticsRequest.java | 18 + .../app/ApplicationActionLogRequest.java | 15 + .../request/app/ApplicationBuildRequest.java | 60 + .../app/ApplicationConfigActionRequest.java | 25 + .../app/ApplicationConfigEnvRequest.java | 52 + .../request/app/ApplicationConfigRequest.java | 39 + .../request/app/ApplicationEnvRequest.java | 48 + .../request/app/ApplicationInfoRequest.java | 50 + .../app/ApplicationPipelineDetailRequest.java | 22 + .../app/ApplicationPipelineRequest.java | 45 + .../ApplicationPipelineTaskDetailRequest.java | 48 + .../app/ApplicationPipelineTaskRequest.java | 73 + .../app/ApplicationProfileRequest.java | 35 + .../app/ApplicationReleaseAuditRequest.java | 25 + .../app/ApplicationReleaseRequest.java | 82 + .../app/ApplicationRepositoryRequest.java | 70 + .../app/ApplicationSyncConfigRequest.java | 23 + .../entity/request/data/DataClearRequest.java | 73 + .../request/data/DataExportRequest.java | 50 + .../request/data/DataImportRequest.java | 15 + .../request/exec/CommandExecRequest.java | 66 + .../request/file/FileDownloadRequest.java | 23 + .../entity/request/file/FileTailRequest.java | 66 + .../request/history/HistoryValueRequest.java | 28 + .../request/home/HomeStatisticsRequest.java | 15 + .../machine/MachineAlarmConfigRequest.java | 36 + .../machine/MachineAlarmHistoryRequest.java | 42 + .../request/machine/MachineAlarmRequest.java | 30 + .../request/machine/MachineEnvRequest.java | 45 + .../machine/MachineGroupRelRequest.java | 29 + .../request/machine/MachineGroupRequest.java | 39 + .../request/machine/MachineInfoRequest.java | 70 + .../request/machine/MachineKeyRequest.java | 39 + .../MachineMonitorEndpointRequest.java | 26 + .../machine/MachineMonitorRequest.java | 40 + .../machine/MachineMonitorSyncRequest.java | 21 + .../request/machine/MachineProxyRequest.java | 45 + .../machine/MachineTerminalLogRequest.java | 57 + .../MachineTerminalManagerRequest.java | 51 + .../machine/MachineTerminalRequest.java | 44 + .../message/WebSideMessageRequest.java | 55 + .../scheduler/SchedulerTaskRecordRequest.java | 48 + .../scheduler/SchedulerTaskRequest.java | 64 + .../SchedulerTaskStatisticsRequest.java | 15 + .../entity/request/sftp/FileBaseRequest.java | 18 + .../request/sftp/FileChangeGroupRequest.java | 20 + .../entity/request/sftp/FileChmodRequest.java | 20 + .../entity/request/sftp/FileChownRequest.java | 20 + .../request/sftp/FileDownloadRequest.java | 19 + .../entity/request/sftp/FileListRequest.java | 17 + .../entity/request/sftp/FileMkdirRequest.java | 17 + .../entity/request/sftp/FileMoveRequest.java | 20 + .../entity/request/sftp/FileOpenRequest.java | 15 + .../request/sftp/FilePresentCheckRequest.java | 25 + .../request/sftp/FileRemoveRequest.java | 19 + .../entity/request/sftp/FileTouchRequest.java | 17 + .../request/sftp/FileTruncateRequest.java | 17 + .../request/sftp/FileUploadRequest.java | 30 + .../request/system/ConfigIpListRequest.java | 24 + .../request/system/SystemEnvRequest.java | 39 + .../system/SystemFileCleanRequest.java | 19 + .../request/system/SystemOptionRequest.java | 22 + .../template/CommandTemplateRequest.java | 35 + .../request/upload/BatchUploadRequest.java | 26 + .../entity/request/user/EventLogRequest.java | 67 + .../entity/request/user/UserInfoRequest.java | 55 + .../entity/request/user/UserLoginRequest.java | 21 + .../entity/request/user/UserResetRequest.java | 21 + .../request/webhook/WebhookConfigRequest.java | 34 + .../ops/entity/vo/alarm/AlarmGroupUserVO.java | 24 + .../ops/entity/vo/alarm/AlarmGroupVO.java | 32 + .../app/ApplicationActionLogStatisticsVO.java | 31 + .../entity/vo/app/ApplicationActionLogVO.java | 69 + .../vo/app/ApplicationActionStatisticsVO.java | 24 + .../vo/app/ApplicationActionStatusVO.java | 45 + .../entity/vo/app/ApplicationActionVO.java | 29 + .../vo/app/ApplicationBuildReleaseListVO.java | 26 + .../ApplicationBuildStatisticsChartVO.java | 24 + .../ApplicationBuildStatisticsMetricsVO.java | 27 + ...cationBuildStatisticsMetricsWrapperVO.java | 18 + .../ApplicationBuildStatisticsRecordVO.java | 40 + .../app/ApplicationBuildStatisticsViewVO.java | 26 + .../vo/app/ApplicationBuildStatusVO.java | 46 + .../ops/entity/vo/app/ApplicationBuildVO.java | 94 + .../entity/vo/app/ApplicationConfigEnvVO.java | 52 + .../entity/vo/app/ApplicationDetailVO.java | 44 + .../ops/entity/vo/app/ApplicationEnvVO.java | 43 + .../ops/entity/vo/app/ApplicationInfoVO.java | 46 + .../entity/vo/app/ApplicationMachineVO.java | 37 + .../vo/app/ApplicationPipelineDetailVO.java | 40 + .../app/ApplicationPipelineStageConfigVO.java | 34 + ...ApplicationPipelineStatisticsDetailVO.java | 35 + ...ApplicationPipelineTaskDetailStatusVO.java | 48 + .../app/ApplicationPipelineTaskDetailVO.java | 66 + .../vo/app/ApplicationPipelineTaskListVO.java | 70 + .../vo/app/ApplicationPipelineTaskLogVO.java | 45 + ...licationPipelineTaskStatisticsChartVO.java | 24 + ...icationPipelineTaskStatisticsDetailVO.java | 40 + ...cationPipelineTaskStatisticsMetricsVO.java | 27 + ...ipelineTaskStatisticsMetricsWrapperVO.java | 18 + ...plicationPipelineTaskStatisticsTaskVO.java | 40 + ...plicationPipelineTaskStatisticsViewVO.java | 26 + .../app/ApplicationPipelineTaskStatusVO.java | 46 + .../vo/app/ApplicationPipelineTaskVO.java | 106 + .../entity/vo/app/ApplicationPipelineVO.java | 36 + .../vo/app/ApplicationProfileFastVO.java | 21 + .../entity/vo/app/ApplicationProfileVO.java | 32 + .../vo/app/ApplicationReleaseDetailVO.java | 139 + .../vo/app/ApplicationReleaseListVO.java | 103 + .../ApplicationReleaseMachineStatusVO.java | 46 + .../vo/app/ApplicationReleaseMachineVO.java | 61 + .../ApplicationReleaseStatisticsChartVO.java | 24 + ...ApplicationReleaseStatisticsMachineVO.java | 39 + ...ApplicationReleaseStatisticsMetricsVO.java | 27 + ...tionReleaseStatisticsMetricsWrapperVO.java | 18 + .../ApplicationReleaseStatisticsRecordVO.java | 40 + .../ApplicationReleaseStatisticsViewVO.java | 26 + .../vo/app/ApplicationReleaseStatusVO.java | 46 + .../vo/app/ApplicationRepositoryBranchVO.java | 22 + .../vo/app/ApplicationRepositoryCommitVO.java | 29 + .../vo/app/ApplicationRepositoryInfoVO.java | 20 + .../vo/app/ApplicationRepositoryVO.java | 60 + .../entity/vo/data/DataImportCheckRowVO.java | 27 + .../ops/entity/vo/data/DataImportCheckVO.java | 26 + .../entity/vo/exec/CommandExecStatusVO.java | 27 + .../ops/entity/vo/exec/CommandExecVO.java | 81 + .../entity/vo/exec/CommandTaskSubmitVO.java | 24 + .../ops/entity/vo/history/HistoryValueVO.java | 42 + .../entity/vo/home/HomeStatisticsCountVO.java | 24 + .../ops/entity/vo/home/HomeStatisticsVO.java | 15 + .../vo/machine/MachineAlarmConfigVO.java | 28 + .../machine/MachineAlarmConfigWrapperVO.java | 20 + .../vo/machine/MachineAlarmHistoryVO.java | 33 + .../ops/entity/vo/machine/MachineEnvVO.java | 44 + .../entity/vo/machine/MachineGroupTreeVO.java | 33 + .../ops/entity/vo/machine/MachineInfoVO.java | 77 + .../entity/vo/machine/MachineMonitorVO.java | 43 + .../ops/entity/vo/machine/MachineProxyVO.java | 35 + .../entity/vo/machine/MachineSecretKeyVO.java | 30 + .../vo/machine/MachineTerminalLogVO.java | 60 + .../vo/machine/MachineTerminalManagerVO.java | 44 + .../entity/vo/machine/MachineTerminalVO.java | 64 + .../entity/vo/machine/TerminalAccessVO.java | 59 + .../entity/vo/machine/TerminalWatcherVO.java | 34 + .../vo/message/WebSideMessagePollVO.java | 23 + .../entity/vo/message/WebSideMessageVO.java | 54 + .../ops/entity/vo/scheduler/CronNextVO.java | 21 + ...chedulerTaskMachineRecordStatisticsVO.java | 33 + .../SchedulerTaskMachineRecordStatusVO.java | 48 + .../SchedulerTaskMachineRecordVO.java | 63 + .../SchedulerTaskRecordStatisticsChartVO.java | 24 + .../SchedulerTaskRecordStatisticsVO.java | 35 + .../SchedulerTaskRecordStatusVO.java | 46 + .../vo/scheduler/SchedulerTaskRecordVO.java | 52 + .../entity/vo/scheduler/SchedulerTaskVO.java | 68 + .../ops/entity/vo/sftp/FileDetailVO.java | 47 + .../ops/entity/vo/sftp/FileListVO.java | 20 + .../ops/entity/vo/sftp/FileOpenVO.java | 23 + .../ops/entity/vo/sftp/FileTransferLogVO.java | 46 + .../ops/entity/vo/system/IpListConfigVO.java | 30 + .../ops/entity/vo/system/SystemAboutVO.java | 37 + .../entity/vo/system/SystemAnalysisVO.java | 54 + .../ops/entity/vo/system/SystemEnvVO.java | 40 + .../ops/entity/vo/system/SystemOptionVO.java | 91 + .../entity/vo/system/ThreadPoolMetricsVO.java | 31 + .../ops/entity/vo/tail/FileTailConfigVO.java | 31 + .../ops/entity/vo/tail/FileTailVO.java | 73 + .../entity/vo/template/CommandTemplateVO.java | 50 + .../vo/upload/BatchUploadCheckFileVO.java | 26 + .../vo/upload/BatchUploadCheckMachineVO.java | 21 + .../entity/vo/upload/BatchUploadCheckVO.java | 27 + .../entity/vo/upload/BatchUploadTokenVO.java | 18 + .../ops/entity/vo/user/UserEventLogVO.java | 55 + .../ops/entity/vo/user/UserInfoVO.java | 59 + .../ops/entity/vo/user/UserLoginVO.java | 31 + .../entity/vo/webhook/WebhookConfigVO.java | 31 + orion-ops-api/orion-ops-runner/pom.xml | 23 + .../ops/runner/AppBuildStatusCleanRunner.java | 54 + .../ops/runner/AppPipelineTaskRunner.java | 105 + .../AppReleaseTimedTaskRestoreRunner.java | 44 + .../AppRepositoryStatusCleanRunner.java | 81 + .../runner/ApplicationStartArgsRunner.java | 89 + .../ops/runner/CacheKeyCleanRunner.java | 64 + .../runner/CommandExecStatusCleanRunner.java | 37 + .../runner/HostMachineInitializeRunner.java | 67 + .../ops/runner/LoadIpFilterRunner.java | 60 + .../runner/MachineMonitorStatusRunner.java | 88 + .../ops/runner/ReleaseStatusCleanRunner.java | 88 + .../ops/runner/SchedulerTaskRunner.java | 127 + .../runner/SftpTransferStatusCleanRunner.java | 48 + .../ops/runner/SystemEnvInitializeRunner.java | 152 + .../ops/runner/SystemSpaceAnalysisRunner.java | 34 + orion-ops-api/orion-ops-service/pom.xml | 87 + .../orionsec/ops/config/DataSourceConfig.java | 20 + .../ops/config/JsonSerializerConfig.java | 35 + .../ops/config/RedisSerializeConfig.java | 25 + .../orionsec/ops/config/SchedulerConfig.java | 23 + .../handler/alarm/MachineAlarmContext.java | 51 + .../handler/alarm/MachineAlarmExecutor.java | 103 + .../alarm/push/AlarmWebSideMessagePusher.java | 40 + .../alarm/push/AlarmWebhookPusher.java | 81 + .../ops/handler/alarm/push/IAlarmPusher.java | 10 + .../app/action/AbstractActionHandler.java | 309 + .../app/action/CheckoutActionHandler.java | 106 + .../app/action/CommandActionHandler.java | 58 + .../handler/app/action/IActionHandler.java | 79 + .../app/action/MachineActionStore.java | 108 + .../app/action/ScpTransferActionHandler.java | 104 + .../app/action/SftpTransferActionHandler.java | 140 + .../handler/app/build/BuildSessionHolder.java | 46 + .../app/machine/AbstractMachineProcessor.java | 292 + .../app/machine/BuildMachineProcessor.java | 299 + .../app/machine/IMachineProcessor.java | 26 + .../app/machine/MachineProcessorStatus.java | 38 + .../app/machine/ReleaseMachineProcessor.java | 194 + .../app/pipeline/IPipelineProcessor.java | 44 + .../app/pipeline/PipelineProcessor.java | 247 + .../app/pipeline/PipelineSessionHolder.java | 45 + .../pipeline/stage/AbstractStageHandler.java | 250 + .../app/pipeline/stage/BuildStageHandler.java | 93 + .../app/pipeline/stage/IStageHandler.java | 49 + .../pipeline/stage/ReleaseStageHandler.java | 183 + .../app/release/AbstractReleaseProcessor.java | 219 + .../app/release/IReleaseProcessor.java | 62 + .../app/release/ParallelReleaseProcessor.java | 51 + .../app/release/ReleaseSessionHolder.java | 45 + .../app/release/SerialReleaseProcessor.java | 64 + .../ops/handler/exec/CommandExecHandler.java | 357 + .../ops/handler/exec/ExecSessionHolder.java | 48 + .../ops/handler/exec/IExecHandler.java | 31 + .../ops/handler/http/HttpApiDefined.java | 22 + .../ops/handler/http/HttpApiRequest.java | 52 + .../ops/handler/http/HttpApiRequester.java | 72 + .../handler/http/MachineMonitorHttpApi.java | 79 + .../http/MachineMonitorHttpApiRequester.java | 30 + .../monitor/MonitorAgentInstallTask.java | 240 + .../scheduler/AbstractTaskProcessor.java | 189 + .../ops/handler/scheduler/ITaskProcessor.java | 54 + .../scheduler/ParallelTaskProcessor.java | 52 + .../scheduler/SerialTaskProcessor.java | 64 + .../handler/scheduler/TaskSessionHolder.java | 48 + .../machine/ITaskMachineHandler.java | 33 + .../scheduler/machine/TaskMachineHandler.java | 309 + .../handler/sftp/FileTransferProcessor.java | 222 + .../handler/sftp/IFileTransferProcessor.java | 36 + .../handler/sftp/SftpBasicExecutorHolder.java | 129 + .../ops/handler/sftp/SftpSupport.java | 73 + .../sftp/TransferProcessorManager.java | 232 + .../handler/sftp/direct/DirectDownloader.java | 105 + .../handler/sftp/hint/FilePackageHint.java | 26 + .../handler/sftp/hint/FileTransferHint.java | 49 + .../sftp/impl/DownloadFileProcessor.java | 42 + .../sftp/impl/PackageFileProcessor.java | 276 + .../sftp/impl/UploadFileProcessor.java | 42 + .../notify/FileTransferNotifyHandler.java | 119 + .../ops/handler/tail/ITailHandler.java | 66 + .../ops/handler/tail/TailFileHandler.java | 94 + .../ops/handler/tail/TailFileHint.java | 44 + .../ops/handler/tail/TailSessionHolder.java | 105 + .../tail/impl/ExecTailFileHandler.java | 166 + .../tail/impl/TrackerTailFileHandler.java | 112 + .../ops/handler/terminal/IOperateHandler.java | 59 + .../terminal/TerminalMessageHandler.java | 192 + .../terminal/TerminalOperateHandler.java | 290 + .../ops/handler/terminal/TerminalUtils.java | 60 + .../manager/TerminalManagementHandler.java | 25 + .../manager/TerminalSessionManager.java | 167 + .../terminal/screen/TerminalScreenEnv.java | 37 + .../terminal/screen/TerminalScreenHeader.java | 48 + .../watcher/ITerminalWatcherProcessor.java | 31 + .../watcher/TerminalWatcherHandler.java | 133 + .../watcher/TerminalWatcherProcessor.java | 73 + .../ops/handler/webhook/DingRobotPusher.java | 69 + .../ops/handler/webhook/IWebhookPusher.java | 11 + .../interceptor/AuthenticateInterceptor.java | 79 + .../ops/interceptor/DemoDisableApiAspect.java | 28 + .../ExposeApiHeaderInterceptor.java | 45 + .../FileTransferNotifyInterceptor.java | 39 + .../ops/interceptor/IpFilterInterceptor.java | 107 + .../ops/interceptor/LogPrintInterceptor.java | 149 + .../ops/interceptor/RoleInterceptor.java | 52 + .../ops/interceptor/TailFileInterceptor.java | 49 + .../TerminalAccessInterceptor.java | 49 + .../TerminalWatcherInterceptor.java | 51 + .../interceptor/UserActiveInterceptor.java | 118 + .../ops/interceptor/UserEventLogAspect.java | 44 + .../service/api/AlarmGroupNotifyService.java | 42 + .../ops/service/api/AlarmGroupService.java | 50 + .../service/api/AlarmGroupUserService.java | 42 + .../api/ApplicationActionLogService.java | 96 + .../service/api/ApplicationActionService.java | 75 + .../service/api/ApplicationBuildService.java | 128 + .../service/api/ApplicationEnvService.java | 153 + .../service/api/ApplicationInfoService.java | 113 + .../api/ApplicationMachineService.java | 137 + .../api/ApplicationPipelineDetailService.java | 67 + .../api/ApplicationPipelineService.java | 60 + .../ApplicationPipelineTaskDetailService.java | 51 + .../ApplicationPipelineTaskLogService.java | 26 + .../api/ApplicationPipelineTaskService.java | 126 + .../api/ApplicationProfileService.java | 65 + .../api/ApplicationReleaseMachineService.java | 50 + .../api/ApplicationReleaseService.java | 172 + .../api/ApplicationRepositoryService.java | 129 + .../ops/service/api/BatchUploadService.java | 37 + .../ops/service/api/CommandExecService.java | 87 + .../service/api/CommandTemplateService.java | 52 + .../ops/service/api/CommonService.java | 7 + .../ops/service/api/FileDownloadService.java | 38 + .../ops/service/api/FileTailService.java | 102 + .../ops/service/api/HistoryValueService.java | 38 + .../api/MachineAlarmConfigService.java | 76 + .../service/api/MachineAlarmGroupService.java | 42 + .../api/MachineAlarmHistoryService.java | 18 + .../ops/service/api/MachineAlarmService.java | 22 + .../ops/service/api/MachineEnvService.java | 176 + .../service/api/MachineGroupRelService.java | 62 + .../ops/service/api/MachineGroupService.java | 50 + .../ops/service/api/MachineInfoService.java | 140 + .../ops/service/api/MachineKeyService.java | 81 + .../api/MachineMonitorEndpointService.java | 82 + .../service/api/MachineMonitorService.java | 98 + .../ops/service/api/MachineProxyService.java | 52 + .../service/api/MachineTerminalService.java | 89 + .../ops/service/api/PassportService.java | 40 + .../SchedulerTaskMachineRecordService.java | 34 + .../api/SchedulerTaskMachineService.java | 34 + .../api/SchedulerTaskRecordService.java | 109 + .../ops/service/api/SchedulerTaskService.java | 66 + .../orionsec/ops/service/api/SftpService.java | 258 + .../ops/service/api/StatisticsService.java | 107 + .../ops/service/api/SystemEnvService.java | 101 + .../ops/service/api/SystemService.java | 72 + .../ops/service/api/UserEventLogService.java | 27 + .../orionsec/ops/service/api/UserService.java | 85 + .../service/api/WebSideMessageService.java | 112 + .../ops/service/api/WebhookConfigService.java | 50 + .../impl/AlarmGroupNotifyServiceImpl.java | 49 + .../service/impl/AlarmGroupServiceImpl.java | 242 + .../impl/AlarmGroupUserServiceImpl.java | 47 + .../impl/ApplicationActionLogServiceImpl.java | 124 + .../impl/ApplicationActionServiceImpl.java | 144 + .../impl/ApplicationBuildServiceImpl.java | 370 + .../impl/ApplicationEnvServiceImpl.java | 432 ++ .../impl/ApplicationInfoServiceImpl.java | 429 + .../impl/ApplicationMachineServiceImpl.java | 215 + .../ApplicationPipelineDetailServiceImpl.java | 95 + .../impl/ApplicationPipelineServiceImpl.java | 220 + ...licationPipelineTaskDetailServiceImpl.java | 120 + ...ApplicationPipelineTaskLogServiceImpl.java | 38 + .../ApplicationPipelineTaskServiceImpl.java | 629 ++ .../impl/ApplicationProfileServiceImpl.java | 224 + .../ApplicationReleaseMachineServiceImpl.java | 66 + .../impl/ApplicationReleaseServiceImpl.java | 856 ++ .../ApplicationRepositoryServiceImpl.java | 437 ++ .../service/impl/BatchUploadServiceImpl.java | 200 + .../service/impl/CommandExecServiceImpl.java | 265 + .../impl/CommandTemplateServiceImpl.java | 132 + .../ops/service/impl/CommonServiceImpl.java | 11 + .../service/impl/FileDownloadServiceImpl.java | 224 + .../ops/service/impl/FileTailServiceImpl.java | 362 + .../service/impl/HistoryValueServiceImpl.java | 116 + .../impl/MachineAlarmConfigServiceImpl.java | 155 + .../impl/MachineAlarmGroupServiceImpl.java | 47 + .../impl/MachineAlarmHistoryServiceImpl.java | 42 + .../service/impl/MachineAlarmServiceImpl.java | 77 + .../service/impl/MachineEnvServiceImpl.java | 368 + .../impl/MachineGroupRelServiceImpl.java | 133 + .../service/impl/MachineGroupServiceImpl.java | 235 + .../service/impl/MachineInfoServiceImpl.java | 590 ++ .../service/impl/MachineKeyServiceImpl.java | 189 + .../MachineMonitorEndpointServiceImpl.java | 115 + .../impl/MachineMonitorServiceImpl.java | 268 + .../service/impl/MachineProxyServiceImpl.java | 116 + .../impl/MachineTerminalServiceImpl.java | 213 + .../ops/service/impl/PassportServiceImpl.java | 214 + ...SchedulerTaskMachineRecordServiceImpl.java | 46 + .../impl/SchedulerTaskMachineServiceImpl.java | 40 + .../impl/SchedulerTaskRecordServiceImpl.java | 293 + .../impl/SchedulerTaskServiceImpl.java | 234 + .../ops/service/impl/SftpServiceImpl.java | 792 ++ .../service/impl/StatisticsServiceImpl.java | 606 ++ .../service/impl/SystemEnvServiceImpl.java | 224 + .../ops/service/impl/SystemServiceImpl.java | 274 + .../service/impl/UserEventLogServiceImpl.java | 105 + .../ops/service/impl/UserServiceImpl.java | 316 + .../impl/WebSideMessageServiceImpl.java | 200 + .../impl/WebhookConfigServiceImpl.java | 125 + .../cn/orionsec/ops/task/TaskRegister.java | 104 + .../java/cn/orionsec/ops/task/TaskType.java | 67 + .../java/cn/orionsec/ops/task/TimedTask.java | 55 + .../ops/task/fixed/AutoCleanFileTask.java | 26 + .../task/fixed/SystemSpaceAnalysisTask.java | 27 + .../fixed/TerminalHeartbeatDownChecker.java | 33 + .../task/fixed/TerminalHeartbeatPusher.java | 36 + .../ops/task/impl/PipelineTaskImpl.java | 26 + .../ops/task/impl/ReleaseTaskImpl.java | 26 + .../ops/task/impl/SchedulerTaskImpl.java | 41 + .../java/cn/orionsec/ops/utils/Currents.java | 96 + .../orionsec/ops/utils/EventParamsHolder.java | 91 + .../cn/orionsec/ops/utils/RedisUtils.java | 74 + .../cn/orionsec/ops/utils/UserHolder.java | 22 + .../cn/orionsec/ops/utils/WebSockets.java | 149 + .../push/machine-alarm-ding.template | 5 + .../push/machine-alarm-webside.template | 5 + .../templates/script/start-monitor-agent.sh | 28 + orion-ops-api/orion-ops-web/pom.xml | 165 + .../cn/orionsec/ops/OrionApplication.java | 17 + .../orionsec/ops/config/LogPrintConfig.java | 33 + .../cn/orionsec/ops/config/SwaggerConfig.java | 74 + .../cn/orionsec/ops/config/WebMvcConfig.java | 247 + .../orionsec/ops/config/WebSocketConfig.java | 77 + .../ops/config/WrapperResultConfig.java | 69 + .../ops/controller/AlarmGroupController.java | 79 + .../ApplicationActionLogController.java | 43 + .../ApplicationBuildController.java | 115 + .../controller/ApplicationEnvController.java | 126 + .../controller/ApplicationInfoController.java | 211 + .../ApplicationPipelineController.java | 91 + .../ApplicationPipelineTaskController.java | 208 + .../ApplicationProfileController.java | 87 + .../ApplicationReleaseController.java | 230 + .../ApplicationRepositoryController.java | 133 + .../controller/AuthenticateController.java | 77 + .../BatchExecCommandController.java | 90 + .../ops/controller/BatchUploadController.java | 97 + .../controller/CommandTemplateController.java | 74 + .../ops/controller/CommonController.java | 45 + .../ops/controller/DataClearController.java | 91 + .../ops/controller/DataExportController.java | 35 + .../ops/controller/DataImportController.java | 108 + .../ops/controller/EventLogController.java | 33 + .../controller/FileDownloadController.java | 52 + .../ops/controller/FileTailController.java | 168 + .../controller/HistoryValueController.java | 48 + .../MachineAlarmConfigController.java | 84 + .../ops/controller/MachineEnvController.java | 122 + .../controller/MachineGroupController.java | 86 + .../ops/controller/MachineInfoController.java | 154 + .../ops/controller/MachineKeyController.java | 85 + .../controller/MachineMonitorController.java | 75 + .../MachineMonitorEndpointController.java | 95 + .../controller/MachineProxyController.java | 88 + .../controller/MachineTerminalController.java | 142 + .../ops/controller/SchedulerController.java | 118 + .../controller/SchedulerRecordController.java | 116 + .../ops/controller/SftpController.java | 329 + .../ops/controller/StatisticsController.java | 120 + .../ops/controller/SystemController.java | 120 + .../ops/controller/SystemEnvController.java | 107 + .../ops/controller/UserController.java | 121 + .../controller/WebSideMessageController.java | 93 + .../controller/WebhookConfigController.java | 83 + .../expose/MachineAlarmExposeController.java | 46 + .../MachineMonitorExposeController.java | 35 + .../main/resources/application-dev.properties | 10 + .../resources/application-test.properties | 10 + .../src/main/resources/application.properties | 49 + .../src/main/resources/banner.txt | 9 + .../main/resources/config/spring-common.xml | 9 + .../src/main/resources/menu/menu-admin.json | 187 + .../src/main/resources/menu/menu-dev.json | 171 + .../src/main/resources/menu/menu-opt.json | 171 + orion-ops-api/pom.xml | 187 + orion-ops-vue/.editorconfig | 5 + orion-ops-vue/.env | 3 + orion-ops-vue/.env.dev | 3 + orion-ops-vue/.env.production | 3 + orion-ops-vue/.gitignore | 23 + orion-ops-vue/babel.config.js | 5 + orion-ops-vue/jsconfig.json | 19 + orion-ops-vue/package.json | 60 + orion-ops-vue/public/favicon.ico | Bin 0 -> 41662 bytes orion-ops-vue/public/index.html | 13 + orion-ops-vue/src/App.vue | 25 + orion-ops-vue/src/assets/left-top-bg.png | Bin 0 -> 37305 bytes orion-ops-vue/src/assets/login-btn.png | Bin 0 -> 71586 bytes orion-ops-vue/src/assets/logo.svg | 20 + orion-ops-vue/src/assets/logo_100.png | Bin 0 -> 4121 bytes orion-ops-vue/src/assets/logo_horizontal.png | Bin 0 -> 50857 bytes orion-ops-vue/src/assets/right-bottom-bg.png | Bin 0 -> 23598 bytes .../src/components/app/AddAppEnvModal.vue | 154 + .../src/components/app/AddAppForm.vue | 176 + .../src/components/app/AddAppModal.vue | 60 + .../src/components/app/AddAppProfileModal.vue | 162 + .../src/components/app/AddPipelineModal.vue | 269 + .../src/components/app/AddRepositoryModal.vue | 261 + .../src/components/app/AppAutoComplete.vue | 90 + .../src/components/app/AppBuildConfigForm.vue | 328 + .../components/app/AppBuildDetailDrawer.vue | 256 + .../src/components/app/AppBuildModal.vue | 393 + .../app/AppBuildStatisticsCharts.vue | 96 + .../app/AppBuildStatisticsMetrics.vue | 124 + .../app/AppBuildStatisticsViews.vue | 338 + .../app/AppPipelineDetailViewDrawer.vue | 83 + .../app/AppPipelineExecAuditModal.vue | 83 + .../app/AppPipelineExecBuildModal.vue | 239 + .../components/app/AppPipelineExecModal.vue | 415 + .../app/AppPipelineExecReleaseModal.vue | 226 + .../app/AppPipelineExecTimedModal.vue | 74 + .../components/app/AppPipelineSelector.vue | 73 + .../app/AppPipelineStatisticsCharts.vue | 95 + .../app/AppPipelineStatisticsMetrics.vue | 123 + .../app/AppPipelineStatisticsViews.vue | 347 + .../app/AppPipelineTaskDetailDrawer.vue | 226 + .../src/components/app/AppProfileChecker.vue | 162 + .../components/app/AppReleaseAuditModal.vue | 83 + .../components/app/AppReleaseConfigForm.vue | 457 ++ .../components/app/AppReleaseDetailDrawer.vue | 253 + .../app/AppReleaseMachineDetailDrawer.vue | 226 + .../src/components/app/AppReleaseModal.vue | 414 + .../app/AppReleaseStatisticsCharts.vue | 96 + .../app/AppReleaseStatisticsMetrics.vue | 124 + .../app/AppReleaseStatisticsViews.vue | 466 ++ .../components/app/AppReleaseTimedModal.vue | 74 + .../src/components/app/AppSelector.vue | 64 + .../components/app/PipelineAutoComplete.vue | 93 + .../components/app/ProfileAutoComplete.vue | 90 + .../components/clear/AppBuildClearModal.vue | 181 + .../clear/AppPipelineClearModal.vue | 197 + .../components/clear/AppReleaseClearModal.vue | 197 + .../components/clear/BatchExecClearModal.vue | 173 + .../components/clear/EventLogClearModal.vue | 164 + .../clear/MachineAlarmHistoryClearModal.vue | 151 + .../clear/SchedulerRecordClearModal.vue | 151 + .../clear/TerminalLogClearModal.vue | 161 + .../src/components/common/RightClickMenu.vue | 51 + .../components/content/AddTemplateModal.vue | 153 + .../components/content/AddWebhookModal.vue | 159 + .../components/content/EnvHistoryModal.vue | 233 + .../components/content/TemplateSelector.vue | 167 + .../src/components/editor/Editor.vue | 108 + .../components/exec/ExecTaskDetailModal.vue | 110 + .../export/AppProfileExportModal.vue | 92 + .../export/AppRepositoryExportModal.vue | 100 + .../export/ApplicationExportModal.vue | 92 + .../export/CommandTemplateExportModal.vue | 92 + .../export/EventLogExportExportModal.vue | 131 + .../export/MachineAlarmHistoryExportModal.vue | 104 + .../components/export/MachineExportModal.vue | 100 + .../export/MachineProxyExportModal.vue | 100 + .../components/export/TailFileExportModal.vue | 104 + .../export/TerminalLogExportModal.vue | 104 + .../components/export/WebhookExportModal.vue | 107 + .../src/components/import/DataImportModal.vue | 241 + .../src/components/layout/Header.vue | 158 + .../components/layout/HeaderProfileSelect.vue | 76 + .../src/components/layout/HeaderUser.vue | 100 + .../src/components/layout/Layout.vue | 129 + orion-ops-vue/src/components/layout/Menu.vue | 97 + .../layout/WebSideMessageDrawer.vue | 530 ++ .../src/components/log/AddLogFileModal.vue | 263 + .../components/log/AppActionLogAppender.vue | 99 + .../log/AppActionLogAppenderModal.vue | 47 + .../components/log/AppBuildLogAppender.vue | 238 + .../log/AppBuildLogAppenderModal.vue | 47 + .../components/log/AppReleaseLogAppender.vue | 335 + .../log/AppReleaseLogAppenderModal.vue | 48 + .../log/AppReleaseMachineLogAppender.vue | 205 + .../log/AppReleaseMachineLogAppenderModal.vue | 48 + .../src/components/log/ExecLoggerAppender.vue | 181 + .../log/ExecLoggerAppenderModal.vue | 50 + .../src/components/log/FileAnsiCleanModal.vue | 98 + .../src/components/log/LogAppender.vue | 389 + .../src/components/log/LoggerViewModal.vue | 164 + .../log/SchedulerTaskLogAppender.vue | 301 + .../log/SchedulerTaskLogAppenderModal.vue | 48 + .../log/SchedulerTaskMachineLogAppender.vue | 184 + .../SchedulerTaskMachineLogAppenderModal.vue | 49 + .../src/components/log/UploadLogFileModal.vue | 102 + .../components/machine/AddMachineEnvModal.vue | 151 + .../components/machine/AddMachineKeyModal.vue | 184 + .../components/machine/AddMachineModal.vue | 449 ++ .../machine/AddMachineProxyModal.vue | 197 + .../machine/MachineAutoComplete.vue | 89 + .../src/components/machine/MachineChecker.vue | 295 + .../components/machine/MachineDetailModal.vue | 125 + .../machine/MachineEditableTree.vue | 546 ++ .../machine/MachineKeyBindModal.vue | 98 + .../components/machine/MachineListMenu.vue | 140 + .../machine/MachineMonitorAlarmConfig.vue | 224 + .../machine/MachineMonitorAlarmHistory.vue | 252 + .../machine/MachineMonitorChart.vue | 1050 +++ .../machine/MachineMonitorConfigModal.vue | 146 + .../machine/MachineMonitorSummary.vue | 697 ++ .../machine/MachineMultiSelector.vue | 67 + .../MachineMultiTableSelectorModal.vue | 184 + .../MachineMultiTreeTableSelectorModal.vue | 243 + .../components/machine/MachineSelector.vue | 74 + .../src/components/preview/EditorPreview.vue | 60 + .../src/components/preview/TextPreview.vue | 48 + .../components/scheduler/AddSchedulerTask.vue | 330 + .../src/components/sftp/FileTransferList.vue | 477 ++ .../src/components/sftp/MachineSftpDrawer.vue | 93 + .../src/components/sftp/MachineSftpMain.vue | 818 ++ .../src/components/sftp/SftpChmodModal.vue | 146 + .../src/components/sftp/SftpFolderTree.vue | 214 + .../src/components/sftp/SftpMoveModal.vue | 113 + .../src/components/sftp/SftpTouchModal.vue | 123 + .../src/components/sftp/SftpUpload.vue | 214 + .../components/system/AddSystemEnvModal.vue | 147 + .../src/components/system/IpConfig.vue | 174 + .../src/components/system/OtherConfig.vue | 146 + .../src/components/system/SecurityConfig.vue | 197 + .../src/components/system/SystemAbout.vue | 80 + .../src/components/system/SystemAnalysis.vue | 362 + .../src/components/system/ThreadMetrics.vue | 112 + .../components/terminal/TerminalBanner.vue | 131 + .../src/components/terminal/TerminalBody.vue | 335 + .../components/terminal/TerminalHeader.vue | 188 + .../src/components/terminal/TerminalModal.vue | 214 + .../terminal/TerminalScreenModal.vue | 185 + .../components/terminal/TerminalSearch.vue | 142 + .../terminal/TerminalSettingModal.vue | 188 + .../src/components/terminal/TerminalView.vue | 99 + .../terminal/TerminalWatcherModal.vue | 350 + .../src/components/user/AddAlarmGroup.vue | 192 + .../src/components/user/AddUserModal.vue | 190 + .../src/components/user/EventLogList.vue | 287 + .../src/components/user/LoginHistory.vue | 145 + .../src/components/user/ResetPassword.vue | 152 + .../src/components/user/UserAutoComplete.vue | 95 + .../src/components/user/UserBasicForm.vue | 206 + .../src/components/user/UserSelector.vue | 55 + orion-ops-vue/src/css/common.less | 218 + orion-ops-vue/src/css/component.less | 49 + orion-ops-vue/src/css/layout.less | 145 + orion-ops-vue/src/css/table.less | 92 + orion-ops-vue/src/lib/api.js | 2493 ++++++ orion-ops-vue/src/lib/chart.js | 56 + orion-ops-vue/src/lib/directive.js | 122 + orion-ops-vue/src/lib/enum.js | 2562 ++++++ orion-ops-vue/src/lib/filters.js | 8 + orion-ops-vue/src/lib/http.js | 239 + orion-ops-vue/src/lib/storage.js | 72 + orion-ops-vue/src/lib/tree.js | 111 + orion-ops-vue/src/lib/utils.js | 428 + orion-ops-vue/src/lib/validate.js | 9 + orion-ops-vue/src/lib/watermark.js | 80 + orion-ops-vue/src/main.js | 89 + orion-ops-vue/src/router/index.js | 537 ++ orion-ops-vue/src/views/404.vue | 19 + orion-ops-vue/src/views/Console.vue | 276 + orion-ops-vue/src/views/Login.vue | 225 + .../src/views/app/AppActionLogView.vue | 20 + orion-ops-vue/src/views/app/AppBuild.vue | 451 ++ .../src/views/app/AppBuildLogView.vue | 20 + .../src/views/app/AppBuildStatistics.vue | 167 + orion-ops-vue/src/views/app/AppConfig.vue | 129 + orion-ops-vue/src/views/app/AppEnv.vue | 515 ++ orion-ops-vue/src/views/app/AppList.vue | 502 ++ orion-ops-vue/src/views/app/AppPipeline.vue | 278 + .../src/views/app/AppPipelineStatistics.vue | 176 + .../src/views/app/AppPipelineTask.vue | 783 ++ orion-ops-vue/src/views/app/AppProfile.vue | 255 + orion-ops-vue/src/views/app/AppRelease.vue | 794 ++ .../src/views/app/AppReleaseLogView.vue | 21 + .../views/app/AppReleaseMachineLogView.vue | 21 + .../src/views/app/AppReleaseStatistics.vue | 167 + orion-ops-vue/src/views/app/AppRepository.vue | 333 + .../src/views/content/TemplateList.vue | 261 + .../src/views/content/WebhookList.vue | 230 + .../src/views/exec/BatchExecList.vue | 559 ++ .../src/views/exec/BatchExecTask.vue | 446 ++ .../src/views/exec/BatchUploadFile.vue | 591 ++ .../src/views/exec/ExecLoggerView.vue | 21 + orion-ops-vue/src/views/log/LoggerList.vue | 353 + orion-ops-vue/src/views/log/LoggerView.vue | 358 + .../src/views/machine/AddMachine.vue | 452 ++ .../src/views/machine/MachineEnv.vue | 502 ++ .../src/views/machine/MachineGroupView.vue | 365 + .../src/views/machine/MachineKey.vue | 268 + .../src/views/machine/MachineList.vue | 184 + .../src/views/machine/MachineListView.vue | 500 ++ .../src/views/machine/MachineMonitorList.vue | 367 + .../views/machine/MachineMonitorMetrics.vue | 169 + .../src/views/machine/MachineProxy.vue | 261 + .../src/views/machine/MachineSftp.vue | 128 + .../src/views/machine/MachineTerminal.vue | 173 + .../src/views/machine/MachineTerminalLogs.vue | 332 + .../views/machine/MachineTerminalSession.vue | 239 + .../src/views/scheduler/SchedulerList.vue | 327 + .../scheduler/SchedulerMachineLogView.vue | 20 + .../src/views/scheduler/SchedulerRecord.vue | 632 ++ .../views/scheduler/SchedulerStatistics.vue | 224 + .../views/scheduler/SchedulerTaskLogView.vue | 20 + orion-ops-vue/src/views/system/SystemEnv.vue | 362 + .../src/views/system/SystemSetting.vue | 56 + .../src/views/user/AlarmGroupList.vue | 198 + orion-ops-vue/src/views/user/UserDetail.vue | 69 + .../src/views/user/UserEventLogList.vue | 374 + orion-ops-vue/src/views/user/UserList.vue | 366 + orion-ops-vue/vue.config.js | 42 + orion-ops-vue/yarn.lock | 6896 +++++++++++++++++ sql/init-1-schema.sql | 940 +++ sql/init-2-data.sql | 20 + 1074 files changed, 119423 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 orion-ops-api/orion-ops-common/pom.xml create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/DemoDisableApi.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/EventLog.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/IgnoreAuth.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/IgnoreCheck.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/IgnoreLog.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/IgnoreWrapper.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/RequireRole.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/RestWrapper.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/CnConst.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/Const.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/KeyConst.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/MessageConst.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/ParamConst.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/PropertiesConst.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/ResultCode.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/SchedulerPools.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/alarm/AlarmGroupNotifyType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/ActionStatus.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/ActionType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/ApplicationEnvAttr.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/BuildStatus.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/PipelineDetailStatus.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/PipelineLogStatus.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/PipelineStatus.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/ReleaseStatus.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/ReleaseType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/RepositoryAuthType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/RepositoryStatus.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/RepositoryTokenType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/RepositoryType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/StageType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/TimedType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/TransferFileType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/TransferMode.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/command/CommandConst.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/command/ExecStatus.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/command/ExecType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/AuditStatus.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/EnableType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/ExceptionHandlerType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/SerialType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/StainCode.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/TreeMoveType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/download/FileDownloadType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/env/EnvConst.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/env/EnvViewType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/event/EventClassify.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/event/EventKeys.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/event/EventType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/history/HistoryOperator.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/history/HistoryValueType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/machine/MachineAlarmType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/machine/MachineAuthType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/machine/MachineConst.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/machine/MachineEnvAttr.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/machine/ProxyType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/message/MessageClassify.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/message/MessageType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/message/ReadStatus.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/monitor/MonitorConst.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/monitor/MonitorStatus.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/scheduler/SchedulerTaskMachineStatus.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/scheduler/SchedulerTaskStatus.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/sftp/SftpNotifyType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/sftp/SftpPackageType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/sftp/SftpTransferStatus.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/sftp/SftpTransferType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/system/SystemCleanType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/system/SystemConfigKey.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/system/SystemEnvAttr.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/system/ThreadPoolMetricsType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/tail/FileTailMode.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/tail/FileTailType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/terminal/TerminalClientOperate.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/terminal/TerminalConst.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/user/RoleType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/webhook/WebhookType.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/ws/WsCloseCode.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/ws/WsCloseReason.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/ws/WsProtocol.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/AttrConverts.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/AvatarPicHolder.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/EventLogUtils.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/FileCleaner.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/PathBuilders.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/ResourceLoader.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/Utils.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/Valid.java create mode 100644 orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/ValueMix.java create mode 100644 orion-ops-api/orion-ops-common/src/test/java/cn/orionsec/ops/constant/event/AddLicenseHeader.java create mode 100644 orion-ops-api/orion-ops-common/src/test/java/cn/orionsec/ops/constant/event/EventClassifyTest.java create mode 100644 orion-ops-api/orion-ops-common/src/test/java/cn/orionsec/ops/constant/event/EventTypeTest.java create mode 100644 orion-ops-api/orion-ops-common/src/test/java/cn/orionsec/ops/constant/event/ReplaceVersion.java create mode 100644 orion-ops-api/orion-ops-dao/pom.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/AlarmGroupDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/AlarmGroupNotifyDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/AlarmGroupUserDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationActionDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationActionLogDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationBuildDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationEnvDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationInfoDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationMachineDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationPipelineDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationPipelineDetailDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationPipelineTaskDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationPipelineTaskDetailDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationPipelineTaskLogDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationProfileDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationReleaseDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationReleaseMachineDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationRepositoryDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/CommandExecDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/CommandTemplateDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/FileTailListDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/FileTransferLogDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/HistoryValueSnapshotDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineAlarmConfigDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineAlarmGroupDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineAlarmHistoryDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineEnvDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineGroupDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineGroupRelDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineInfoDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineMonitorDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineProxyDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineSecretKeyDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineTerminalDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineTerminalLogDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/SchedulerTaskDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/SchedulerTaskMachineDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/SchedulerTaskMachineRecordDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/SchedulerTaskRecordDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/SystemEnvDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/UserEventLogDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/UserInfoDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/WebSideMessageDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/WebhookConfigDAO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/AlarmGroupDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/AlarmGroupNotifyDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/AlarmGroupUserDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationActionDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationActionLogDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationBuildDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationEnvDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationInfoDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationMachineDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationPipelineDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationPipelineDetailDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationPipelineTaskDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationPipelineTaskDetailDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationPipelineTaskLogDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationProfileDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationReleaseDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationReleaseMachineDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationRepositoryDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/CommandExecDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/CommandTemplateDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/FileTailListDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/FileTransferLogDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/HistoryValueSnapshotDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineAlarmConfigDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineAlarmGroupDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineAlarmHistoryDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineEnvDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineGroupDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineGroupRelDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineInfoDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineMonitorDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineProxyDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineSecretKeyDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineTerminalDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineTerminalLogDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/SchedulerTaskDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/SchedulerTaskMachineDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/SchedulerTaskMachineRecordDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/SchedulerTaskRecordDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/SystemEnvDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/UserEventLogDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/UserInfoDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/WebSideMessageDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/WebhookConfigDO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/ApplicationActionConfigDTO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/ApplicationBuildStatisticsDTO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/ApplicationPipelineTaskStatisticsDTO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/ApplicationReleaseStatisticsDTO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/MachineMonitorDTO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/SchedulerTaskRecordStatisticsDTO.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/query/MachineMonitorQuery.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/utils/CodeGenerator.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/utils/DataQuery.java create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/AlarmGroupMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/AlarmGroupNotifyMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/AlarmGroupUserMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationActionLogMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationActionMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationBuildMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationEnvMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationInfoMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationMachineMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationPipelineDetailMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationPipelineMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationPipelineTaskDetailMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationPipelineTaskLogMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationPipelineTaskMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationProfileMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationReleaseMachineMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationReleaseMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationRepositoryMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/CommandExecMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/CommandTemplateMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/FileTailListMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/FileTransferLogMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/HistoryValueSnapshotMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineAlarmConfigMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineAlarmGroupMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineAlarmHistoryMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineEnvMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineGroupMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineGroupRelMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineInfoMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineMonitorMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineProxyMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineSecretKeyMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineTerminalLogMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineTerminalMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/ScheduleTaskMachineMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/ScheduleTaskMachineRecordMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/ScheduleTaskMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/ScheduleTaskRecordMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/SystemEnvMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/UserEventLogMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/UserInfoMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/WebSideMessageMapper.xml create mode 100644 orion-ops-api/orion-ops-dao/src/main/resources/mapper/WebhookConfigMapper.xml create mode 100644 orion-ops-api/orion-ops-data/pom.xml create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/DataModuleConversionProvider.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/constant/DataClearRange.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/constant/DataClearType.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/constant/ExportConst.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/constant/ExportType.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/constant/ImportType.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/ApplicationExportDTO.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/ApplicationProfileExportDTO.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/ApplicationRepositoryExportDTO.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/CommandTemplateExportDTO.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/EventLogExportDTO.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/MachineAlarmHistoryExportDTO.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/MachineInfoExportDTO.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/MachineProxyExportDTO.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/MachineTailFileExportDTO.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/MachineTerminalLogExportDTO.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/WebhookExportDTO.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/ApplicationImportDTO.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/ApplicationProfileImportDTO.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/ApplicationRepositoryImportDTO.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/BaseDataImportDTO.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/CommandTemplateImportDTO.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/DataImportDTO.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/MachineInfoImportDTO.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/MachineProxyImportDTO.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/MachineTailFileImportDTO.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/WebhookImportDTO.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/AbstractDataExporter.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/AppProfileDataExporter.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/AppRepositoryDataExporter.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/ApplicationDataExporter.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/CommandTemplateDataExporter.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/IDataExporter.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/MachineAlarmHistoryDataExporter.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/MachineInfoDataExporter.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/MachineProxyDataExporter.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/TailFileDataExporter.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/TerminalLogDataExporter.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/UserEventLogDataExporter.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/WebhookDataExporter.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/AbstractDataChecker.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/AppProfileDataChecker.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/AppRepositoryDataChecker.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/ApplicationDataChecker.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/CommandTemplateDataChecker.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/IDataChecker.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/MachineInfoDataChecker.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/MachineProxyDataChecker.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/TailFileDataChecker.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/WebhookDataChecker.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/AbstractDataImporter.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/AppProfileDataImporter.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/AppRepositoryDataImporter.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/ApplicationDataImporter.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/CommandTemplateDataImporter.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/IDataImporter.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/MachineInfoDataImporter.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/MachineProxyDataImporter.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/TailFileDataImporter.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/WebhookDataImporter.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/ApplicationProfileValidator.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/ApplicationRepositoryValidator.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/ApplicationValidator.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/CommandTemplateValidator.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/DataValidator.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/FileTailValidator.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/MachineProxyValidator.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/MachineValidator.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/WebhookValidator.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/service/api/DataClearService.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/service/api/DataImportService.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/service/impl/DataClearServiceImpl.java create mode 100644 orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/service/impl/DataImportServiceImpl.java create mode 100644 orion-ops-api/orion-ops-data/src/main/resources/templates/import/app-profile-import-template.xlsx create mode 100644 orion-ops-api/orion-ops-data/src/main/resources/templates/import/app-repository-import-template.xlsx create mode 100644 orion-ops-api/orion-ops-data/src/main/resources/templates/import/application-import-template.xlsx create mode 100644 orion-ops-api/orion-ops-data/src/main/resources/templates/import/command-template-import-template.xlsx create mode 100644 orion-ops-api/orion-ops-data/src/main/resources/templates/import/machine-import-template.xlsx create mode 100644 orion-ops-api/orion-ops-data/src/main/resources/templates/import/machine-proxy-import-template.xlsx create mode 100644 orion-ops-api/orion-ops-data/src/main/resources/templates/import/tail-file-import-template.xlsx create mode 100644 orion-ops-api/orion-ops-data/src/main/resources/templates/import/webhook-import-template.xlsx create mode 100644 orion-ops-api/orion-ops-mapping/pom.xml create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/MappingConversionProvider.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/alarm/AlarmGroupConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationActionConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationBuildConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationEnvConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationMachineConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationPipelineConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationPipelineTaskConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationProfileConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationReleaseConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationRepositoryConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/exec/CommandExecConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/file/FileTailConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/file/FileTransferLogConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/history/HistoryValueConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/home/StatisticsConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineAlarmConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineEnvConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineGroupConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineMonitorConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineProxyConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineSecretKeyConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineTerminalConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/message/WebSideMessageConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/scheduler/SchedulerTaskConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/scheduler/SchedulerTaskRecordConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/sftp/SftpConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/system/SystemAnalysisConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/system/SystemEnvConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/template/CommandTemplateConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/upload/BatchUploadConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/user/UserEventLogConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/user/UserInfoConversion.java create mode 100644 orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/webhook/WebhookConfigConversion.java create mode 100644 orion-ops-api/orion-ops-model/pom.xml create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/config/TerminalConnectConfig.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/app/ApplicationPipelineStageConfigDTO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/app/ApplicationProfileDTO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/file/FileDownloadDTO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/file/FileTailDTO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/sftp/FileTransferNotifyDTO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/sftp/FileTransferNotifyProgressDTO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/sftp/SftpSessionTokenDTO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/sftp/SftpUploadInfoDTO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/statistic/StatisticsCountDTO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/system/SystemSpaceAnalysisDTO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/terminal/TerminalConnectDTO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/terminal/TerminalSizeDTO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/terminal/TerminalWatcherDTO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/user/LoginBindDTO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/user/UserDTO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/alarm/AlarmGroupNotifyRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/alarm/AlarmGroupRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/AppBuildStatisticsRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/AppPipelineTaskStatisticsRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/AppReleaseStatisticsRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationActionLogRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationBuildRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationConfigActionRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationConfigEnvRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationConfigRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationEnvRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationInfoRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationPipelineDetailRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationPipelineRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationPipelineTaskDetailRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationPipelineTaskRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationProfileRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationReleaseAuditRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationReleaseRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationRepositoryRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationSyncConfigRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/data/DataClearRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/data/DataExportRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/data/DataImportRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/exec/CommandExecRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/file/FileDownloadRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/file/FileTailRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/history/HistoryValueRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/home/HomeStatisticsRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineAlarmConfigRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineAlarmHistoryRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineAlarmRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineEnvRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineGroupRelRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineGroupRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineInfoRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineKeyRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineMonitorEndpointRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineMonitorRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineMonitorSyncRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineProxyRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineTerminalLogRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineTerminalManagerRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineTerminalRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/message/WebSideMessageRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/scheduler/SchedulerTaskRecordRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/scheduler/SchedulerTaskRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/scheduler/SchedulerTaskStatisticsRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileBaseRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileChangeGroupRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileChmodRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileChownRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileDownloadRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileListRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileMkdirRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileMoveRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileOpenRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FilePresentCheckRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileRemoveRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileTouchRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileTruncateRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileUploadRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/system/ConfigIpListRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/system/SystemEnvRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/system/SystemFileCleanRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/system/SystemOptionRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/template/CommandTemplateRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/upload/BatchUploadRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/user/EventLogRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/user/UserInfoRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/user/UserLoginRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/user/UserResetRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/webhook/WebhookConfigRequest.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/alarm/AlarmGroupUserVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/alarm/AlarmGroupVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationActionLogStatisticsVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationActionLogVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationActionStatisticsVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationActionStatusVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationActionVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildReleaseListVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatisticsChartVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatisticsMetricsVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatisticsMetricsWrapperVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatisticsRecordVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatisticsViewVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatusVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationConfigEnvVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationDetailVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationEnvVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationInfoVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationMachineVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineDetailVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineStageConfigVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineStatisticsDetailVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskDetailStatusVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskDetailVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskListVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskLogVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsChartVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsDetailVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsMetricsVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsMetricsWrapperVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsTaskVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsViewVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatusVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationProfileFastVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationProfileVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseDetailVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseListVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseMachineStatusVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseMachineVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsChartVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsMachineVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsMetricsVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsMetricsWrapperVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsRecordVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsViewVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatusVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationRepositoryBranchVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationRepositoryCommitVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationRepositoryInfoVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationRepositoryVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/data/DataImportCheckRowVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/data/DataImportCheckVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/exec/CommandExecStatusVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/exec/CommandExecVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/exec/CommandTaskSubmitVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/history/HistoryValueVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/home/HomeStatisticsCountVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/home/HomeStatisticsVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineAlarmConfigVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineAlarmConfigWrapperVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineAlarmHistoryVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineEnvVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineGroupTreeVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineInfoVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineMonitorVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineProxyVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineSecretKeyVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineTerminalLogVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineTerminalManagerVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineTerminalVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/TerminalAccessVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/TerminalWatcherVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/message/WebSideMessagePollVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/message/WebSideMessageVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/CronNextVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskMachineRecordStatisticsVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskMachineRecordStatusVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskMachineRecordVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskRecordStatisticsChartVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskRecordStatisticsVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskRecordStatusVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskRecordVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/sftp/FileDetailVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/sftp/FileListVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/sftp/FileOpenVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/sftp/FileTransferLogVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/IpListConfigVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/SystemAboutVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/SystemAnalysisVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/SystemEnvVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/SystemOptionVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/ThreadPoolMetricsVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/tail/FileTailConfigVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/tail/FileTailVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/template/CommandTemplateVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/upload/BatchUploadCheckFileVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/upload/BatchUploadCheckMachineVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/upload/BatchUploadCheckVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/upload/BatchUploadTokenVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/user/UserEventLogVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/user/UserInfoVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/user/UserLoginVO.java create mode 100644 orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/webhook/WebhookConfigVO.java create mode 100644 orion-ops-api/orion-ops-runner/pom.xml create mode 100644 orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/AppBuildStatusCleanRunner.java create mode 100644 orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/AppPipelineTaskRunner.java create mode 100644 orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/AppReleaseTimedTaskRestoreRunner.java create mode 100644 orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/AppRepositoryStatusCleanRunner.java create mode 100644 orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/ApplicationStartArgsRunner.java create mode 100644 orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/CacheKeyCleanRunner.java create mode 100644 orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/CommandExecStatusCleanRunner.java create mode 100644 orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/HostMachineInitializeRunner.java create mode 100644 orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/LoadIpFilterRunner.java create mode 100644 orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/MachineMonitorStatusRunner.java create mode 100644 orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/ReleaseStatusCleanRunner.java create mode 100644 orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/SchedulerTaskRunner.java create mode 100644 orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/SftpTransferStatusCleanRunner.java create mode 100644 orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/SystemEnvInitializeRunner.java create mode 100644 orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/SystemSpaceAnalysisRunner.java create mode 100644 orion-ops-api/orion-ops-service/pom.xml create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/config/DataSourceConfig.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/config/JsonSerializerConfig.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/config/RedisSerializeConfig.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/config/SchedulerConfig.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/alarm/MachineAlarmContext.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/alarm/MachineAlarmExecutor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/alarm/push/AlarmWebSideMessagePusher.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/alarm/push/AlarmWebhookPusher.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/alarm/push/IAlarmPusher.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/AbstractActionHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/CheckoutActionHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/CommandActionHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/IActionHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/MachineActionStore.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/ScpTransferActionHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/SftpTransferActionHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/build/BuildSessionHolder.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/machine/AbstractMachineProcessor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/machine/BuildMachineProcessor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/machine/IMachineProcessor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/machine/MachineProcessorStatus.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/machine/ReleaseMachineProcessor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/IPipelineProcessor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/PipelineProcessor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/PipelineSessionHolder.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/stage/AbstractStageHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/stage/BuildStageHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/stage/IStageHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/stage/ReleaseStageHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/release/AbstractReleaseProcessor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/release/IReleaseProcessor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/release/ParallelReleaseProcessor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/release/ReleaseSessionHolder.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/release/SerialReleaseProcessor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/exec/CommandExecHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/exec/ExecSessionHolder.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/exec/IExecHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/http/HttpApiDefined.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/http/HttpApiRequest.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/http/HttpApiRequester.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/http/MachineMonitorHttpApi.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/http/MachineMonitorHttpApiRequester.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/monitor/MonitorAgentInstallTask.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/AbstractTaskProcessor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/ITaskProcessor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/ParallelTaskProcessor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/SerialTaskProcessor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/TaskSessionHolder.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/machine/ITaskMachineHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/machine/TaskMachineHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/FileTransferProcessor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/IFileTransferProcessor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/SftpBasicExecutorHolder.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/SftpSupport.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/TransferProcessorManager.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/direct/DirectDownloader.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/hint/FilePackageHint.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/hint/FileTransferHint.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/impl/DownloadFileProcessor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/impl/PackageFileProcessor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/impl/UploadFileProcessor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/notify/FileTransferNotifyHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/ITailHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/TailFileHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/TailFileHint.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/TailSessionHolder.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/impl/ExecTailFileHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/impl/TrackerTailFileHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/IOperateHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/TerminalMessageHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/TerminalOperateHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/TerminalUtils.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/manager/TerminalManagementHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/manager/TerminalSessionManager.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/screen/TerminalScreenEnv.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/screen/TerminalScreenHeader.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/watcher/ITerminalWatcherProcessor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/watcher/TerminalWatcherHandler.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/watcher/TerminalWatcherProcessor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/webhook/DingRobotPusher.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/webhook/IWebhookPusher.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/AuthenticateInterceptor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/DemoDisableApiAspect.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/ExposeApiHeaderInterceptor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/FileTransferNotifyInterceptor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/IpFilterInterceptor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/LogPrintInterceptor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/RoleInterceptor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/TailFileInterceptor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/TerminalAccessInterceptor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/TerminalWatcherInterceptor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/UserActiveInterceptor.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/UserEventLogAspect.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/AlarmGroupNotifyService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/AlarmGroupService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/AlarmGroupUserService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationActionLogService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationActionService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationBuildService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationEnvService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationInfoService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationMachineService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationPipelineDetailService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationPipelineService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationPipelineTaskDetailService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationPipelineTaskLogService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationPipelineTaskService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationProfileService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationReleaseMachineService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationReleaseService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationRepositoryService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/BatchUploadService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/CommandExecService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/CommandTemplateService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/CommonService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/FileDownloadService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/FileTailService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/HistoryValueService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineAlarmConfigService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineAlarmGroupService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineAlarmHistoryService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineAlarmService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineEnvService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineGroupRelService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineGroupService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineInfoService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineKeyService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineMonitorEndpointService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineMonitorService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineProxyService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineTerminalService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/PassportService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SchedulerTaskMachineRecordService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SchedulerTaskMachineService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SchedulerTaskRecordService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SchedulerTaskService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SftpService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/StatisticsService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SystemEnvService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SystemService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/UserEventLogService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/UserService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/WebSideMessageService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/WebhookConfigService.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/AlarmGroupNotifyServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/AlarmGroupServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/AlarmGroupUserServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationActionLogServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationActionServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationBuildServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationEnvServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationInfoServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationMachineServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationPipelineDetailServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationPipelineServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationPipelineTaskDetailServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationPipelineTaskLogServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationPipelineTaskServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationProfileServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationReleaseMachineServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationReleaseServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationRepositoryServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/BatchUploadServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/CommandExecServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/CommandTemplateServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/CommonServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/FileDownloadServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/FileTailServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/HistoryValueServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineAlarmConfigServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineAlarmGroupServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineAlarmHistoryServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineAlarmServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineEnvServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineGroupRelServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineGroupServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineInfoServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineKeyServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineMonitorEndpointServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineMonitorServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineProxyServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineTerminalServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/PassportServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SchedulerTaskMachineRecordServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SchedulerTaskMachineServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SchedulerTaskRecordServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SchedulerTaskServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SftpServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/StatisticsServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SystemEnvServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SystemServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/UserEventLogServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/UserServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/WebSideMessageServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/WebhookConfigServiceImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/TaskRegister.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/TaskType.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/TimedTask.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/fixed/AutoCleanFileTask.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/fixed/SystemSpaceAnalysisTask.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/fixed/TerminalHeartbeatDownChecker.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/fixed/TerminalHeartbeatPusher.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/impl/PipelineTaskImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/impl/ReleaseTaskImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/impl/SchedulerTaskImpl.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/utils/Currents.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/utils/EventParamsHolder.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/utils/RedisUtils.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/utils/UserHolder.java create mode 100644 orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/utils/WebSockets.java create mode 100644 orion-ops-api/orion-ops-service/src/main/resources/templates/push/machine-alarm-ding.template create mode 100644 orion-ops-api/orion-ops-service/src/main/resources/templates/push/machine-alarm-webside.template create mode 100644 orion-ops-api/orion-ops-service/src/main/resources/templates/script/start-monitor-agent.sh create mode 100644 orion-ops-api/orion-ops-web/pom.xml create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/OrionApplication.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/config/LogPrintConfig.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/config/SwaggerConfig.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/config/WebMvcConfig.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/config/WebSocketConfig.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/config/WrapperResultConfig.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/AlarmGroupController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationActionLogController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationBuildController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationEnvController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationInfoController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationPipelineController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationPipelineTaskController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationProfileController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationReleaseController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationRepositoryController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/AuthenticateController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/BatchExecCommandController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/BatchUploadController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/CommandTemplateController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/CommonController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/DataClearController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/DataExportController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/DataImportController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/EventLogController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/FileDownloadController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/FileTailController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/HistoryValueController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineAlarmConfigController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineEnvController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineGroupController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineInfoController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineKeyController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineMonitorController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineMonitorEndpointController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineProxyController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineTerminalController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/SchedulerController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/SchedulerRecordController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/SftpController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/StatisticsController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/SystemController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/SystemEnvController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/UserController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/WebSideMessageController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/WebhookConfigController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/expose/MachineAlarmExposeController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/expose/MachineMonitorExposeController.java create mode 100644 orion-ops-api/orion-ops-web/src/main/resources/application-dev.properties create mode 100644 orion-ops-api/orion-ops-web/src/main/resources/application-test.properties create mode 100644 orion-ops-api/orion-ops-web/src/main/resources/application.properties create mode 100644 orion-ops-api/orion-ops-web/src/main/resources/banner.txt create mode 100644 orion-ops-api/orion-ops-web/src/main/resources/config/spring-common.xml create mode 100644 orion-ops-api/orion-ops-web/src/main/resources/menu/menu-admin.json create mode 100644 orion-ops-api/orion-ops-web/src/main/resources/menu/menu-dev.json create mode 100644 orion-ops-api/orion-ops-web/src/main/resources/menu/menu-opt.json create mode 100644 orion-ops-api/pom.xml create mode 100644 orion-ops-vue/.editorconfig create mode 100644 orion-ops-vue/.env create mode 100644 orion-ops-vue/.env.dev create mode 100644 orion-ops-vue/.env.production create mode 100644 orion-ops-vue/.gitignore create mode 100644 orion-ops-vue/babel.config.js create mode 100644 orion-ops-vue/jsconfig.json create mode 100644 orion-ops-vue/package.json create mode 100644 orion-ops-vue/public/favicon.ico create mode 100644 orion-ops-vue/public/index.html create mode 100644 orion-ops-vue/src/App.vue create mode 100644 orion-ops-vue/src/assets/left-top-bg.png create mode 100644 orion-ops-vue/src/assets/login-btn.png create mode 100644 orion-ops-vue/src/assets/logo.svg create mode 100644 orion-ops-vue/src/assets/logo_100.png create mode 100644 orion-ops-vue/src/assets/logo_horizontal.png create mode 100644 orion-ops-vue/src/assets/right-bottom-bg.png create mode 100644 orion-ops-vue/src/components/app/AddAppEnvModal.vue create mode 100644 orion-ops-vue/src/components/app/AddAppForm.vue create mode 100644 orion-ops-vue/src/components/app/AddAppModal.vue create mode 100644 orion-ops-vue/src/components/app/AddAppProfileModal.vue create mode 100644 orion-ops-vue/src/components/app/AddPipelineModal.vue create mode 100644 orion-ops-vue/src/components/app/AddRepositoryModal.vue create mode 100644 orion-ops-vue/src/components/app/AppAutoComplete.vue create mode 100644 orion-ops-vue/src/components/app/AppBuildConfigForm.vue create mode 100644 orion-ops-vue/src/components/app/AppBuildDetailDrawer.vue create mode 100644 orion-ops-vue/src/components/app/AppBuildModal.vue create mode 100644 orion-ops-vue/src/components/app/AppBuildStatisticsCharts.vue create mode 100644 orion-ops-vue/src/components/app/AppBuildStatisticsMetrics.vue create mode 100644 orion-ops-vue/src/components/app/AppBuildStatisticsViews.vue create mode 100644 orion-ops-vue/src/components/app/AppPipelineDetailViewDrawer.vue create mode 100644 orion-ops-vue/src/components/app/AppPipelineExecAuditModal.vue create mode 100644 orion-ops-vue/src/components/app/AppPipelineExecBuildModal.vue create mode 100644 orion-ops-vue/src/components/app/AppPipelineExecModal.vue create mode 100644 orion-ops-vue/src/components/app/AppPipelineExecReleaseModal.vue create mode 100644 orion-ops-vue/src/components/app/AppPipelineExecTimedModal.vue create mode 100644 orion-ops-vue/src/components/app/AppPipelineSelector.vue create mode 100644 orion-ops-vue/src/components/app/AppPipelineStatisticsCharts.vue create mode 100644 orion-ops-vue/src/components/app/AppPipelineStatisticsMetrics.vue create mode 100644 orion-ops-vue/src/components/app/AppPipelineStatisticsViews.vue create mode 100644 orion-ops-vue/src/components/app/AppPipelineTaskDetailDrawer.vue create mode 100644 orion-ops-vue/src/components/app/AppProfileChecker.vue create mode 100644 orion-ops-vue/src/components/app/AppReleaseAuditModal.vue create mode 100644 orion-ops-vue/src/components/app/AppReleaseConfigForm.vue create mode 100644 orion-ops-vue/src/components/app/AppReleaseDetailDrawer.vue create mode 100644 orion-ops-vue/src/components/app/AppReleaseMachineDetailDrawer.vue create mode 100644 orion-ops-vue/src/components/app/AppReleaseModal.vue create mode 100644 orion-ops-vue/src/components/app/AppReleaseStatisticsCharts.vue create mode 100644 orion-ops-vue/src/components/app/AppReleaseStatisticsMetrics.vue create mode 100644 orion-ops-vue/src/components/app/AppReleaseStatisticsViews.vue create mode 100644 orion-ops-vue/src/components/app/AppReleaseTimedModal.vue create mode 100644 orion-ops-vue/src/components/app/AppSelector.vue create mode 100644 orion-ops-vue/src/components/app/PipelineAutoComplete.vue create mode 100644 orion-ops-vue/src/components/app/ProfileAutoComplete.vue create mode 100644 orion-ops-vue/src/components/clear/AppBuildClearModal.vue create mode 100644 orion-ops-vue/src/components/clear/AppPipelineClearModal.vue create mode 100644 orion-ops-vue/src/components/clear/AppReleaseClearModal.vue create mode 100644 orion-ops-vue/src/components/clear/BatchExecClearModal.vue create mode 100644 orion-ops-vue/src/components/clear/EventLogClearModal.vue create mode 100644 orion-ops-vue/src/components/clear/MachineAlarmHistoryClearModal.vue create mode 100644 orion-ops-vue/src/components/clear/SchedulerRecordClearModal.vue create mode 100644 orion-ops-vue/src/components/clear/TerminalLogClearModal.vue create mode 100644 orion-ops-vue/src/components/common/RightClickMenu.vue create mode 100644 orion-ops-vue/src/components/content/AddTemplateModal.vue create mode 100644 orion-ops-vue/src/components/content/AddWebhookModal.vue create mode 100644 orion-ops-vue/src/components/content/EnvHistoryModal.vue create mode 100644 orion-ops-vue/src/components/content/TemplateSelector.vue create mode 100644 orion-ops-vue/src/components/editor/Editor.vue create mode 100644 orion-ops-vue/src/components/exec/ExecTaskDetailModal.vue create mode 100644 orion-ops-vue/src/components/export/AppProfileExportModal.vue create mode 100644 orion-ops-vue/src/components/export/AppRepositoryExportModal.vue create mode 100644 orion-ops-vue/src/components/export/ApplicationExportModal.vue create mode 100644 orion-ops-vue/src/components/export/CommandTemplateExportModal.vue create mode 100644 orion-ops-vue/src/components/export/EventLogExportExportModal.vue create mode 100644 orion-ops-vue/src/components/export/MachineAlarmHistoryExportModal.vue create mode 100644 orion-ops-vue/src/components/export/MachineExportModal.vue create mode 100644 orion-ops-vue/src/components/export/MachineProxyExportModal.vue create mode 100644 orion-ops-vue/src/components/export/TailFileExportModal.vue create mode 100644 orion-ops-vue/src/components/export/TerminalLogExportModal.vue create mode 100644 orion-ops-vue/src/components/export/WebhookExportModal.vue create mode 100644 orion-ops-vue/src/components/import/DataImportModal.vue create mode 100644 orion-ops-vue/src/components/layout/Header.vue create mode 100644 orion-ops-vue/src/components/layout/HeaderProfileSelect.vue create mode 100644 orion-ops-vue/src/components/layout/HeaderUser.vue create mode 100644 orion-ops-vue/src/components/layout/Layout.vue create mode 100644 orion-ops-vue/src/components/layout/Menu.vue create mode 100644 orion-ops-vue/src/components/layout/WebSideMessageDrawer.vue create mode 100644 orion-ops-vue/src/components/log/AddLogFileModal.vue create mode 100644 orion-ops-vue/src/components/log/AppActionLogAppender.vue create mode 100644 orion-ops-vue/src/components/log/AppActionLogAppenderModal.vue create mode 100644 orion-ops-vue/src/components/log/AppBuildLogAppender.vue create mode 100644 orion-ops-vue/src/components/log/AppBuildLogAppenderModal.vue create mode 100644 orion-ops-vue/src/components/log/AppReleaseLogAppender.vue create mode 100644 orion-ops-vue/src/components/log/AppReleaseLogAppenderModal.vue create mode 100644 orion-ops-vue/src/components/log/AppReleaseMachineLogAppender.vue create mode 100644 orion-ops-vue/src/components/log/AppReleaseMachineLogAppenderModal.vue create mode 100644 orion-ops-vue/src/components/log/ExecLoggerAppender.vue create mode 100644 orion-ops-vue/src/components/log/ExecLoggerAppenderModal.vue create mode 100644 orion-ops-vue/src/components/log/FileAnsiCleanModal.vue create mode 100644 orion-ops-vue/src/components/log/LogAppender.vue create mode 100644 orion-ops-vue/src/components/log/LoggerViewModal.vue create mode 100644 orion-ops-vue/src/components/log/SchedulerTaskLogAppender.vue create mode 100644 orion-ops-vue/src/components/log/SchedulerTaskLogAppenderModal.vue create mode 100644 orion-ops-vue/src/components/log/SchedulerTaskMachineLogAppender.vue create mode 100644 orion-ops-vue/src/components/log/SchedulerTaskMachineLogAppenderModal.vue create mode 100644 orion-ops-vue/src/components/log/UploadLogFileModal.vue create mode 100644 orion-ops-vue/src/components/machine/AddMachineEnvModal.vue create mode 100644 orion-ops-vue/src/components/machine/AddMachineKeyModal.vue create mode 100644 orion-ops-vue/src/components/machine/AddMachineModal.vue create mode 100644 orion-ops-vue/src/components/machine/AddMachineProxyModal.vue create mode 100644 orion-ops-vue/src/components/machine/MachineAutoComplete.vue create mode 100644 orion-ops-vue/src/components/machine/MachineChecker.vue create mode 100644 orion-ops-vue/src/components/machine/MachineDetailModal.vue create mode 100644 orion-ops-vue/src/components/machine/MachineEditableTree.vue create mode 100644 orion-ops-vue/src/components/machine/MachineKeyBindModal.vue create mode 100644 orion-ops-vue/src/components/machine/MachineListMenu.vue create mode 100644 orion-ops-vue/src/components/machine/MachineMonitorAlarmConfig.vue create mode 100644 orion-ops-vue/src/components/machine/MachineMonitorAlarmHistory.vue create mode 100644 orion-ops-vue/src/components/machine/MachineMonitorChart.vue create mode 100644 orion-ops-vue/src/components/machine/MachineMonitorConfigModal.vue create mode 100644 orion-ops-vue/src/components/machine/MachineMonitorSummary.vue create mode 100644 orion-ops-vue/src/components/machine/MachineMultiSelector.vue create mode 100644 orion-ops-vue/src/components/machine/MachineMultiTableSelectorModal.vue create mode 100644 orion-ops-vue/src/components/machine/MachineMultiTreeTableSelectorModal.vue create mode 100644 orion-ops-vue/src/components/machine/MachineSelector.vue create mode 100644 orion-ops-vue/src/components/preview/EditorPreview.vue create mode 100644 orion-ops-vue/src/components/preview/TextPreview.vue create mode 100644 orion-ops-vue/src/components/scheduler/AddSchedulerTask.vue create mode 100644 orion-ops-vue/src/components/sftp/FileTransferList.vue create mode 100644 orion-ops-vue/src/components/sftp/MachineSftpDrawer.vue create mode 100644 orion-ops-vue/src/components/sftp/MachineSftpMain.vue create mode 100644 orion-ops-vue/src/components/sftp/SftpChmodModal.vue create mode 100644 orion-ops-vue/src/components/sftp/SftpFolderTree.vue create mode 100644 orion-ops-vue/src/components/sftp/SftpMoveModal.vue create mode 100644 orion-ops-vue/src/components/sftp/SftpTouchModal.vue create mode 100644 orion-ops-vue/src/components/sftp/SftpUpload.vue create mode 100644 orion-ops-vue/src/components/system/AddSystemEnvModal.vue create mode 100644 orion-ops-vue/src/components/system/IpConfig.vue create mode 100644 orion-ops-vue/src/components/system/OtherConfig.vue create mode 100644 orion-ops-vue/src/components/system/SecurityConfig.vue create mode 100644 orion-ops-vue/src/components/system/SystemAbout.vue create mode 100644 orion-ops-vue/src/components/system/SystemAnalysis.vue create mode 100644 orion-ops-vue/src/components/system/ThreadMetrics.vue create mode 100644 orion-ops-vue/src/components/terminal/TerminalBanner.vue create mode 100644 orion-ops-vue/src/components/terminal/TerminalBody.vue create mode 100644 orion-ops-vue/src/components/terminal/TerminalHeader.vue create mode 100644 orion-ops-vue/src/components/terminal/TerminalModal.vue create mode 100644 orion-ops-vue/src/components/terminal/TerminalScreenModal.vue create mode 100644 orion-ops-vue/src/components/terminal/TerminalSearch.vue create mode 100644 orion-ops-vue/src/components/terminal/TerminalSettingModal.vue create mode 100644 orion-ops-vue/src/components/terminal/TerminalView.vue create mode 100644 orion-ops-vue/src/components/terminal/TerminalWatcherModal.vue create mode 100644 orion-ops-vue/src/components/user/AddAlarmGroup.vue create mode 100644 orion-ops-vue/src/components/user/AddUserModal.vue create mode 100644 orion-ops-vue/src/components/user/EventLogList.vue create mode 100644 orion-ops-vue/src/components/user/LoginHistory.vue create mode 100644 orion-ops-vue/src/components/user/ResetPassword.vue create mode 100644 orion-ops-vue/src/components/user/UserAutoComplete.vue create mode 100644 orion-ops-vue/src/components/user/UserBasicForm.vue create mode 100644 orion-ops-vue/src/components/user/UserSelector.vue create mode 100644 orion-ops-vue/src/css/common.less create mode 100644 orion-ops-vue/src/css/component.less create mode 100644 orion-ops-vue/src/css/layout.less create mode 100644 orion-ops-vue/src/css/table.less create mode 100644 orion-ops-vue/src/lib/api.js create mode 100644 orion-ops-vue/src/lib/chart.js create mode 100644 orion-ops-vue/src/lib/directive.js create mode 100644 orion-ops-vue/src/lib/enum.js create mode 100644 orion-ops-vue/src/lib/filters.js create mode 100644 orion-ops-vue/src/lib/http.js create mode 100644 orion-ops-vue/src/lib/storage.js create mode 100644 orion-ops-vue/src/lib/tree.js create mode 100644 orion-ops-vue/src/lib/utils.js create mode 100644 orion-ops-vue/src/lib/validate.js create mode 100644 orion-ops-vue/src/lib/watermark.js create mode 100644 orion-ops-vue/src/main.js create mode 100644 orion-ops-vue/src/router/index.js create mode 100644 orion-ops-vue/src/views/404.vue create mode 100644 orion-ops-vue/src/views/Console.vue create mode 100644 orion-ops-vue/src/views/Login.vue create mode 100644 orion-ops-vue/src/views/app/AppActionLogView.vue create mode 100644 orion-ops-vue/src/views/app/AppBuild.vue create mode 100644 orion-ops-vue/src/views/app/AppBuildLogView.vue create mode 100644 orion-ops-vue/src/views/app/AppBuildStatistics.vue create mode 100644 orion-ops-vue/src/views/app/AppConfig.vue create mode 100644 orion-ops-vue/src/views/app/AppEnv.vue create mode 100644 orion-ops-vue/src/views/app/AppList.vue create mode 100644 orion-ops-vue/src/views/app/AppPipeline.vue create mode 100644 orion-ops-vue/src/views/app/AppPipelineStatistics.vue create mode 100644 orion-ops-vue/src/views/app/AppPipelineTask.vue create mode 100644 orion-ops-vue/src/views/app/AppProfile.vue create mode 100644 orion-ops-vue/src/views/app/AppRelease.vue create mode 100644 orion-ops-vue/src/views/app/AppReleaseLogView.vue create mode 100644 orion-ops-vue/src/views/app/AppReleaseMachineLogView.vue create mode 100644 orion-ops-vue/src/views/app/AppReleaseStatistics.vue create mode 100644 orion-ops-vue/src/views/app/AppRepository.vue create mode 100644 orion-ops-vue/src/views/content/TemplateList.vue create mode 100644 orion-ops-vue/src/views/content/WebhookList.vue create mode 100644 orion-ops-vue/src/views/exec/BatchExecList.vue create mode 100644 orion-ops-vue/src/views/exec/BatchExecTask.vue create mode 100644 orion-ops-vue/src/views/exec/BatchUploadFile.vue create mode 100644 orion-ops-vue/src/views/exec/ExecLoggerView.vue create mode 100644 orion-ops-vue/src/views/log/LoggerList.vue create mode 100644 orion-ops-vue/src/views/log/LoggerView.vue create mode 100644 orion-ops-vue/src/views/machine/AddMachine.vue create mode 100644 orion-ops-vue/src/views/machine/MachineEnv.vue create mode 100644 orion-ops-vue/src/views/machine/MachineGroupView.vue create mode 100644 orion-ops-vue/src/views/machine/MachineKey.vue create mode 100644 orion-ops-vue/src/views/machine/MachineList.vue create mode 100644 orion-ops-vue/src/views/machine/MachineListView.vue create mode 100644 orion-ops-vue/src/views/machine/MachineMonitorList.vue create mode 100644 orion-ops-vue/src/views/machine/MachineMonitorMetrics.vue create mode 100644 orion-ops-vue/src/views/machine/MachineProxy.vue create mode 100644 orion-ops-vue/src/views/machine/MachineSftp.vue create mode 100644 orion-ops-vue/src/views/machine/MachineTerminal.vue create mode 100644 orion-ops-vue/src/views/machine/MachineTerminalLogs.vue create mode 100644 orion-ops-vue/src/views/machine/MachineTerminalSession.vue create mode 100644 orion-ops-vue/src/views/scheduler/SchedulerList.vue create mode 100644 orion-ops-vue/src/views/scheduler/SchedulerMachineLogView.vue create mode 100644 orion-ops-vue/src/views/scheduler/SchedulerRecord.vue create mode 100644 orion-ops-vue/src/views/scheduler/SchedulerStatistics.vue create mode 100644 orion-ops-vue/src/views/scheduler/SchedulerTaskLogView.vue create mode 100644 orion-ops-vue/src/views/system/SystemEnv.vue create mode 100644 orion-ops-vue/src/views/system/SystemSetting.vue create mode 100644 orion-ops-vue/src/views/user/AlarmGroupList.vue create mode 100644 orion-ops-vue/src/views/user/UserDetail.vue create mode 100644 orion-ops-vue/src/views/user/UserEventLogList.vue create mode 100644 orion-ops-vue/src/views/user/UserList.vue create mode 100644 orion-ops-vue/vue.config.js create mode 100644 orion-ops-vue/yarn.lock create mode 100644 sql/init-1-schema.sql create mode 100644 sql/init-2-data.sql diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea5bf11 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ +.log + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +application-prod.properties + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..9a9222c --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# 毕业设计 +一站式自动化运维及自动化部署平台, 使用多环境的概念, 提供了机器管理、机器监控报警、Web终端、WebSftp、机器批量执行、机器批量上传、在线查看日志、定时调度任务、应用环境维护、应用构建及发布任务、操作流水线等功能, 帮助企业实现一站式轻量化运维治理, 致力于企业级应用的智能运维。 + +## 特性 + +* 易用方便: 极简配置, 开箱即用, 支持 docker 部署 +* 在线终端: 支持在线 Web 终端, 记录操作日志, 管理员可强制下线, 录屏回放, 终端监视等 +* 文件管理: 支持远程机器文件批量上传、批量下载、暂停断点续传、实时传输速率、实时进度、打包传输等功能 +* 机器监控: 支持维护机器的监控以及报警, 支持采集 agent 的一键安装以及升级 +* 批量操作: 支持远程机器批量执行命令 以及 批量执行上传文件 +* 调度任务: 维护 cron 表达式, 定时执行机器命令 +* 环境隔离: 不同应用环境的配置及环境变量是相互隔离的 +* 环境变量: 命令执行时使用占位符自动替换, 支持 properties, json, yml, xml多种格式维护 +* 高兼容性: 自定义构建发布操作, 不论是什么项目都是配置 SSH 执行命令, 灵活操作 +* 功能强大: 命令批量执行, 任务定时调度, 远程日志查看, 操作日志全记录等 +* 高扩展性: 前后端代码规范统一, 代码健壮质量高, 写法优雅, 易读好拓展 +* 免费开源: 前后端代码完全开源, 方便二次开发 + +## 技术栈 + +* SpringBoot 2.4+ +* Mysql 8.+ +* Redis 5+ +* Vue 2.6+ +* Ant Design 1.7.8 + +## 演示环境 +* 🔗 演示地址: https://bishe.youdu.xin +* 🔏 演示账号: wxy/wxy +* 注: 演示环境运行在宿舍电脑的虚拟机中, 每日早晨重置一次,半夜不提供演示服务 + +## 功能预览 + +### 控制台 +![控制台](https://img.youdu.xin/i/2025/10/16/qohmrb.png "控制台") + +### 机器管理 +#### 机器列表 +![机器列表](https://img.youdu.xin/i/2025/10/20/113nucq.png "机器列表") + +#### 在线终端-连接演示 +Linux服务器 +![在线终端](https://img.youdu.xin/i/2025/10/20/11639pk.png "服务器") +H3C防火墙 +![在线终端](https://img.youdu.xin/i/2025/10/20/114c5ik.png "H3C交换机") +H3C交换机 +![在线终端](https://img.youdu.xin/i/2025/10/20/116kztw.png "H3C防火墙") + +#### 终端日志-可回放审查 +终端日志总揽 +![终端日志](https://img.youdu.xin/i/2025/10/20/11alob2.png "终端日志总揽") +终端日志回放 +![终端日志](https://img.youdu.xin/i/2025/10/20/11asa4i.png "终端日志回放") \ No newline at end of file diff --git a/orion-ops-api/orion-ops-common/pom.xml b/orion-ops-api/orion-ops-common/pom.xml new file mode 100644 index 0000000..6df724e --- /dev/null +++ b/orion-ops-api/orion-ops-common/pom.xml @@ -0,0 +1,36 @@ + + + + + cn.orionsec.ops + orion-ops-api + 1.3.1 + ../pom.xml + + + orion-ops-common + orion-ops-common + 4.0.0 + + + + + org.projectlombok + lombok + + + + + cn.orionsec.kit + orion-all + + + + + org.springframework.boot + spring-boot-starter-web + + + + diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/DemoDisableApi.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/DemoDisableApi.java new file mode 100644 index 0000000..f357227 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/DemoDisableApi.java @@ -0,0 +1,10 @@ +package cn.orionsec.ops.annotation; + +import java.lang.annotation.*; + + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DemoDisableApi { +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/EventLog.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/EventLog.java new file mode 100644 index 0000000..10d1c8d --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/EventLog.java @@ -0,0 +1,19 @@ + +package cn.orionsec.ops.annotation; + +import cn.orionsec.ops.constant.event.EventType; + +import java.lang.annotation.*; + + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface EventLog { + + /** + * @return 事件类型 + */ + EventType value(); + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/IgnoreAuth.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/IgnoreAuth.java new file mode 100644 index 0000000..2e3d25a --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/IgnoreAuth.java @@ -0,0 +1,11 @@ + +package cn.orionsec.ops.annotation; + +import java.lang.annotation.*; + + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface IgnoreAuth { +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/IgnoreCheck.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/IgnoreCheck.java new file mode 100644 index 0000000..f278f84 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/IgnoreCheck.java @@ -0,0 +1,11 @@ + +package cn.orionsec.ops.annotation; + +import java.lang.annotation.*; + + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface IgnoreCheck { +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/IgnoreLog.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/IgnoreLog.java new file mode 100644 index 0000000..e3e175f --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/IgnoreLog.java @@ -0,0 +1,12 @@ + +package cn.orionsec.ops.annotation; + +import java.lang.annotation.*; + + +@SuppressWarnings("ALL") +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface IgnoreLog { +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/IgnoreWrapper.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/IgnoreWrapper.java new file mode 100644 index 0000000..a8ee762 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/IgnoreWrapper.java @@ -0,0 +1,11 @@ + +package cn.orionsec.ops.annotation; + +import java.lang.annotation.*; + + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface IgnoreWrapper { +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/RequireRole.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/RequireRole.java new file mode 100644 index 0000000..8cd2b18 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/RequireRole.java @@ -0,0 +1,21 @@ + +package cn.orionsec.ops.annotation; + +import cn.orionsec.ops.constant.user.RoleType; + +import java.lang.annotation.*; + + +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RequireRole { + + /** + * 所需角色 + *

+ * {@link RoleType} + */ + RoleType[] value(); + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/RestWrapper.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/RestWrapper.java new file mode 100644 index 0000000..807c569 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/annotation/RestWrapper.java @@ -0,0 +1,12 @@ + +package cn.orionsec.ops.annotation; + +import java.lang.annotation.*; + + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RestWrapper { + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/CnConst.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/CnConst.java new file mode 100644 index 0000000..4544a20 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/CnConst.java @@ -0,0 +1,52 @@ +package cn.orionsec.ops.constant; + +public class CnConst { + + private CnConst() { + } + + public static final String ENABLE = "启用"; + + public static final String DISABLE = "停用"; + + public static final String RESOLVE = "通过"; + + public static final String REJECT = "驳回"; + + public static final String SUCCESS = "成功"; + + public static final String FAILURE = "失败"; + + public static final String OPEN = "开启"; + + public static final String CLOSE = "关闭"; + + public static final String YES = "是"; + + public static final String NO = "否"; + + public static final String APP = "应用"; + + public static final String RELEASE = "发布"; + + public static final String PASSWORD = "密码"; + + public static final String TOKEN = "令牌"; + + public static final String SECRET_KEY = "独立密钥"; + + public static final String READ = "已读"; + + public static final String UNREAD = "未读"; + + public static final String ORION_OPS_AUTHOR = "李佳航"; + + public static final String UNKNOWN = "未知"; + + public static final String INTRANET_IP = "内网IP"; + + public static final String INSTALL = "安装"; + + public static final String UPGRADE = "升级"; + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/Const.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/Const.java new file mode 100644 index 0000000..e71eb6c --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/Const.java @@ -0,0 +1,184 @@ +package cn.orionsec.ops.constant; + +import cn.orionsec.kit.lang.utils.collect.Lists; + +import java.util.List; + +public class Const implements cn.orionsec.kit.lang.constant.Const { + + private Const() { + } + + public static final String ORION_OPS = "orion-ops"; + + public static final String KEYS_PATH = ".keys"; + + public static final String PIC_PATH = "pic"; + + public static final String SCREEN_PATH = "screen"; + + public static final String SWAP_PATH = "swap"; + + public static final String LOG_PATH = "logs"; + + public static final String TEMP_PATH = "temp"; + + public static final String REPO_PATH = "repo"; + + public static final String DIST_PATH = "dist"; + + public static final String MACHINE_MONITOR_AGENT_PATH = "/lib/machine-monitor-agent-latest.jar"; + + public static final String TAIL_FILE_PATH = "tail"; + + public static final String TERMINAL_DIR = "/terminal"; + + public static final String BUILD_DIR = "/build"; + + public static final String RELEASE_DIR = "/release"; + + public static final String RELEASE_MACHINE_PREFIX = "/machine"; + + public static final String EXEC_DIR = "/exec"; + + public static final String COMMAND_DIR = "/command"; + + public static final String AVATAR_DIR = "/avatar"; + + public static final String UPLOAD_DIR = "/upload"; + + public static final String DOWNLOAD_DIR = "/download"; + + public static final String PACKAGE_DIR = "/package"; + + public static final String EVENT_DIR = "/event"; + + public static final String TASK_DIR = "/task"; + + public static final String IMPORT_DIR = "/import"; + + public static final String EXPORT_DIR = "/export"; + + public static final String LIB_DIR = "/lib"; + + public static final String INSTALL_DIR = "/install"; + + public static final String BUILD = "build"; + + public static final String RELEASE = "release"; + + public static final String PIPELINE = "pipeline"; + + public static final String TASK = "task"; + + public static final String ACTION = "action"; + + public static final String ROOT = "root"; + + public static final String EVENT = "event"; + + public static final String PACKAGE = "package"; + + public static final String PLUGINS = "plugins"; + + public static final String TIMEOUT = "timeout"; + + public static final String CAST_SUFFIX = "cast"; + + public static final Integer FORBID_DELETE_CAN = 1; + + public static final Integer FORBID_DELETE_NOT = 2; + + public static final int EXEC_COMMAND_OMIT = 80; + + public static final int TEMPLATE_OMIT = 80; + + public static final int TAIL_OFFSET_LINE = 300; + + public static final Long HOST_MACHINE_ID = 1L; + + public static final Long ROOT_TREE_ID = -1L; + + public static final Integer DEFAULT_TREE_SORT = 1; + + public static final String HOST_MACHINE_TAG = "host"; + + public static final String SWAP_FILE_SUFFIX = ".swp"; + + public static final String SECRET_KEY_SUFFIX = "_id_rsa"; + + public static final Integer IS_DEFAULT = 1; + + public static final Integer CONFIGURED = 1; + + public static final Integer IS_SYSTEM = 1; + + public static final Integer NOT_SYSTEM = 2; + + public static final Integer NOT_CONFIGURED = 2; + + public static final Integer COMMIT_LIMIT = 30; + + public static final Integer BUILD_RELEASE_LIMIT = 20; + + public static final String COPY = "Copy"; + + public static final String ROLLBACK = "Rollback"; + + public static final String COMPRESS_LIST_FILE = "压缩清单.txt"; + + public static final int TRACKER_DELAY_MS = 250; + + public static final int MIN_TRACKER_DELAY_MS = 50; + + public static final int DEFAULT_FILE_CLEAN_THRESHOLD = 60; + + public static final int DEFAULT_LOGIN_TOKEN_EXPIRE_HOUR = 24 * 2; + + public static final int SFTP_UPLOAD_THRESHOLD = 512; + + public static final String GITHUB = "github"; + + public static final String GITEE = "gitee"; + + public static final String GITLAB = "gitlab"; + + public static final String OAUTH2 = "oauth2"; + + public static final String SOCKS4 = "socks4"; + + public static final String SOCKS5 = "socks5"; + + public static final String DEFAULT_SHELL = "/bin/bash"; + + public static final String LF_2 = "\n\n"; + + public static final String LF_3 = "\n\n\n"; + + public static final Integer ENABLE = 1; + + public static final Integer DISABLE = 2; + + public static final Integer NOT_DELETED = 1; + + public static final Integer IS_DELETED = 2; + + public static final Integer INCREMENT = 1; + + public static final Integer DECREMENT = 2; + + /** + * 不安全的文件夹 + */ + public static final List UNSAFE_FS_DIR = Lists.of( + "/", "/bin", "/usr", + "/sbin", "/etc", "/tmp", + "/lib", "/var", "/home", + "/opt", "/root", "/run", + "/lib64", "/lost+found", "/media", + "/mnt", "/proc", "/sys", + "/svr", "/dev", "/boot", + "/usr/bin", "/usr/include", "/usr/lib", + "/usr/local", "/usr/sbin", ".tmp"); + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/KeyConst.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/KeyConst.java new file mode 100644 index 0000000..0162a49 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/KeyConst.java @@ -0,0 +1,156 @@ +package cn.orionsec.ops.constant; + +public class KeyConst { + + private KeyConst() { + } + + /** + * 登录 token + *

+ * auth:login:{id} + */ + public static final String LOGIN_TOKEN_KEY = "auth:info:{}"; + + /** + * 登录 token 绑定 token + *

+ * auth:login:{id}:{timestamp} + */ + public static final String LOGIN_TOKEN_BIND_KEY = "auth:bind:{}:{}"; + + /** + * terminal 访问 token + *

+ * terminal:access:{token} + */ + public static final String TERMINAL_ACCESS_TOKEN = "terminal:access:{}"; + + /** + * 3min + */ + public static final int TERMINAL_ACCESS_TOKEN_EXPIRE = 60 * 3; + + /** + * terminal 监视 token + *

+ * terminal:watcher:{token} + */ + public static final String TERMINAL_WATCHER_TOKEN = "terminal:watcher:{}"; + + /** + * 3min + */ + public static final int TERMINAL_WATCHER_TOKEN_EXPIRE = 60 * 3; + + /** + * 文件 tail 访问 token + *

+ * file:tail:access{token} + */ + public static final String FILE_TAIL_ACCESS_TOKEN = "file:tail:access:{}"; + + /** + * 5 min + */ + public static final int FILE_TAIL_ACCESS_EXPIRE = 60 * 5; + + /** + * 文件下载 token + *

+ * file:download:{token} + */ + public static final String FILE_DOWNLOAD_TOKEN = "file:download:{}"; + + /** + * 5 min + */ + public static final int FILE_DOWNLOAD_EXPIRE = 60 * 5; + + /** + * sftp 会话 token + *

+ * sftp:session:{token} + */ + public static final String SFTP_SESSION_TOKEN = "sftp:session:{}"; + + /** + * 12 h + */ + public static final int SFTP_SESSION_EXPIRE = 60 * 60 * 12; + + /** + * sftp 上传请求 token + *

+ * sftp:upload:{token} + */ + public static final String SFTP_UPLOAD_ACCESS_TOKEN = "sftp:upload:{}"; + + /** + * 5 h + */ + public static final int SFTP_UPLOAD_ACCESS_EXPIRE = 60 * 60 * 5; + + /** + * 主页统计数量 key + *

+ * data:statistics:home:count:{profileId} + */ + public static final String HOME_STATISTICS_COUNT_KEY = "data:statistics:home:count:{}"; + + /** + * 调度任务统计 key + *

+ * data:statistics:scheduler:task:{id} + */ + public static final String SCHEDULER_TASK_STATISTICS_KEY = "data:statistics:scheduler:task:{}"; + + /** + * 环境缓存 key + *

+ * data:profile + */ + public static final String DATA_PROFILE_KEY = "data:profile"; + + /** + * 30 min + */ + public static final int DATA_PROFILE_EXPIRE = 60 * 30; + + /** + * 数据导入缓存 key + *

+ * data:import:{userId}:{token} + */ + public static final String DATA_IMPORT_TOKEN = "data:import:{}:{}"; + + /** + * 5 min + */ + public static final int DATA_IMPORT_TOKEN_EXPIRE = 60 * 5; + + /** + * 机器分组数据 key + *

+ * machine:group:data + */ + public static final String MACHINE_GROUP_DATA_KEY = "machine:group:data"; + + /** + * 5 h + */ + public static final int MACHINE_GROUP_DATA_EXPIRE = 60 * 60 * 5; + + /** + * 机器分组引用 key + *

+ * machine:group:rel + */ + public static final String MACHINE_GROUP_REL_KEY = "machine:group:rel"; + + /** + * 5 h + */ + public static final int MACHINE_GROUP_REL_EXPIRE = 60 * 60 * 5; + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/MessageConst.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/MessageConst.java new file mode 100644 index 0000000..9d26264 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/MessageConst.java @@ -0,0 +1,231 @@ +package cn.orionsec.ops.constant; + + +public class MessageConst { + + private MessageConst() { + } + + public static final String ABSENT = "不存在"; + + public static final String UNAUTHORIZED = "会话过期"; + + public static final String NO_PERMISSION = "无操作权限"; + + public static final String FILE_MISSING = "文件不存在"; + + public static final String IP_BAN = "当前IP已被封禁"; + + public static final String ILLEGAL_ACCESS = "非法访问"; + + public static final String ABSENT_PARAM = "参数缺失"; + + public static final String INVALID_PARAM = "非法参数"; + + public static final String PARSE_ERROR = "解析失败"; + + public static final String OPERATOR_ERROR = "操作失败"; + + public static final String DEMO_DISABLE_API = "演示模式不支持此功能"; + + public static final String HTTP_API = "api 调用异常"; + + public static final String NETWORK_FLUCTUATION = "当前环境网路波动"; + + public static final String OPEN_TEMPLATE_ERROR = "模板解析失败 请检查模板和密码"; + + public static final String PARSE_TEMPLATE_DATA_ERROR = "模板解析失败 请检查模板数据"; + + public static final String REPOSITORY_OPERATOR_ERROR = "应用版本仓库操作执行失败"; + + public static final String TASK_ERROR = "任务执行异常"; + + public static final String CONNECT_ERROR = "建立连接失败"; + + public static final String TIMEOUT_ERROR = "处理超时"; + + public static final String INTERRUPT_ERROR = "操作中断"; + + public static final String USERNAME_PASSWORD_ERROR = "用户名或密码错误"; + + public static final String USER_DISABLED = "用户已被禁用"; + + public static final String USER_LOCKED = "用户已被锁定"; + + public static final String UNKNOWN_USER = "未查询到用户信息"; + + public static final String USERNAME_PRESENT = "用户名已存在"; + + public static final String BEFORE_PASSWORD_EMPTY = "原密码为空"; + + public static final String BEFORE_PASSWORD_ERROR = "原密码错误"; + + public static final String UNSAFE_OPERATOR = "不安全的操作"; + + public static final String UNSUPPORTED_OPERATOR = "不支持的操作"; + + public static final String ENCRYPT_ERROR = "数据加密异常"; + + public static final String DECRYPT_ERROR = "数据解密异常"; + + public static final String UNKNOWN_DATA = "未查询到数据"; + + public static final String UNKNOWN_EXPORT_TYPE = "未知的导出类型"; + + public static final String INVALID_MACHINE = "未知的机器"; + + public static final String MACHINE_DISABLE = "机器未启用"; + + public static final String INVALID_PROXY = "未查询到代理信息"; + + public static final String INVALID_PTY = "终端类型不合法"; + + public static final String EXCEPTION_MESSAGE = "系统异常"; + + public static final String AUTH_EXCEPTION_MESSAGE = "认证失败"; + + public static final String TIMEOUT_EXCEPTION_MESSAGE = "连接超时"; + + public static final String IO_EXCEPTION_MESSAGE = "网络异常"; + + public static final String SQL_EXCEPTION_MESSAGE = "数据异常"; + + public static final String UNCONNECTED = "未建立连接"; + + public static final String EXEC_TASK_ABSENT = "未查询到任务信息"; + + public static final String EXEC_TASK_THREAD_ABSENT = "未查询到任务进程"; + + public static final String TEMPLATE_ABSENT = "未查询到模板信息"; + + public static final String HISTORY_VALUE_ABSENT = "未查询到历史值信息"; + + public static final String METADATA_ABSENT = "元数据缺失"; + + public static final String ENV_ABSENT = "环境变量不存在"; + + public static final String TOKEN_EMPTY = "token为空"; + + public static final String TOKEN_EXPIRE = "token过期"; + + public static final String SESSION_EXPIRE = "会话过期"; + + public static final String PATH_NOT_NORMALIZE = "路径不合法"; + + public static final String FILE_NOT_FOUND = "文件 {} 不存在"; + + public static final String TRANSFER_ITEM_EMPTY = "未找到可传输对象"; + + public static final String UNSELECTED_TRANSFER_LOG = "未找到传输对象"; + + public static final String FILE_TOO_LARGE = "文件过大"; + + public static final String NAME_PRESENT = "名称重复"; + + public static final String TAG_PRESENT = "唯一标识重复"; + + public static final String FORBID_DELETE = "禁止删除"; + + public static final String APP_ABSENT = "应用不存在"; + + public static final String PROFILE_ABSENT = "环境不存在"; + + public static final String CONFIG_ABSENT = "配置不存在"; + + public static final String APP_PROFILE_NOT_CONFIGURED = "应用环境还未配置"; + + public static final String CHECKOUT_ACTION_PRESENT = "检出操作只能有一个"; + + public static final String TRANSFER_ACTION_PRESENT = "传输操作只能有一个"; + + public static final String ILLEGAL_MACHINE_SECRET_KEY = "密钥不合法, 请检查密码或使用 ssh-keygen -m PEM -t rsa 重新生成"; + + public static final String AUTO_AUDIT_RESOLVE = "自动审核通过"; + + public static final String AUDIT_NOT_REQUIRED = "无需审核"; + + public static final String RELEASE_ABSENT = "发布任务不存在"; + + public static final String RELEASE_MACHINE_ABSENT = "发布机器不存在"; + + public static final String OPERATOR_NOT_ALL_SUCCESS = "未全部执行成功"; + + public static final String UNKNOWN_RELEASE_MACHINE = "未知的发布机器"; + + public static final String ILLEGAL_STATUS = "当前状态不支持此操作"; + + public static final String FILE_ABSENT_UNABLE_ROLLBACK = "产物丢失无法回滚"; + + public static final String REPO_INIT_ERROR = "仓库初始化失败"; + + public static final String REPO_PATH_ABSENT = "仓库目录不存在"; + + public static final String REPO_UNABLE_CONNECT = "无法连接到远程仓库"; + + public static final String REPO_UNINITIALIZED = "仓库未初始化"; + + public static final String REPO_INITIALIZED = "远程仓库已初始化"; + + public static final String REPO_INITIALIZING = "远程仓库初始化中"; + + public static final String CHECKOUT_ERROR = "git clone 检出失败"; + + public static final String RESET_ERROR = "git reset 操作失败"; + + public static final String BUILD_ABSENT = "构建版本不存在"; + + public static final String BUNDLE_FILE_ABSENT = "构建产物不存在"; + + public static final String BUNDLE_ZIP_FILE_ABSENT = "构建产物压缩文件不存在"; + + public static final String UNABLE_CONFIG_RELEASE_MACHINE = "发布机器未配置"; + + public static final String NO_SUCH_FILE = "未找到文件或目录"; + + public static final String TASK_ABSENT = "任务不存在"; + + public static final String TASK_PRESENT = "任务已存在"; + + public static final String INVALID_CONFIG = "配置不合法"; + + public static final String ERROR_EXPRESSION = "表达式错误"; + + public static final String TIMED_GREATER_THAN_NOW = "定时操作时间不能小于当前时间"; + + public static final String TASK_NOT_ENABLED = "调度任务未启用"; + + public static final String SESSION_PRESENT = "会话不存在"; + + public static final String UPLOAD_TOO_LARGE = "上传文件大小不能大于 {}MB, 当前大小 {}"; + + public static final String PIPELINE_ABSENT = "流水线不存在"; + + public static final String PIPELINE_TASK_ABSENT = "流水线任务不存在"; + + public static final String PIPELINE_DETAIL_EMPTY = "流水线操作为空"; + + public static final String PIPELINE_DETAIL_ABSENT = "未查询到流水线操作"; + + public static final String APP_LAST_BUILD_VERSION_ABSENT = "{} 无构建版本"; + + public static final String EXECUTE_SFTP_ZIP_COMMAND_ERROR = "执行 zip 压缩命令失败"; + + public static final String SFTP_ZIP_FILE_ABSENT = "压缩文件不存在"; + + public static final String OPERATOR_TIMEOUT = "操作超时"; + + public static final String UNKNOWN_MACHINE_TAG = "未查询到机器: {}"; + + public static final String UNKNOWN_MACHINE_KEY = "未查询到机器密钥: {}"; + + public static final String UNKNOWN_APP_REPOSITORY = "未查询到应用仓库: {}"; + + public static final String AGENT_STATUS_IS_STARTING = "插件正在启动中"; + + public static final String AGENT_FILE_NON_EXIST = "插件包不存在 请参考文档并移动到: {}"; + + public static final String WEBHOOK_ABSENT = "webhook 不存在"; + + public static final String ALARM_GROUP_ABSENT = "报警组不存在"; + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/ParamConst.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/ParamConst.java new file mode 100644 index 0000000..fe34b9d --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/ParamConst.java @@ -0,0 +1,14 @@ +package cn.orionsec.ops.constant; + +public class ParamConst { + + private ParamConst() { + } + + public static final String LIMIT = "limit"; + + public static final String NAME = "name"; + + public static final String MACHINE_ID = "machineId"; + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/PropertiesConst.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/PropertiesConst.java new file mode 100644 index 0000000..c6cdd92 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/PropertiesConst.java @@ -0,0 +1,49 @@ +package cn.orionsec.ops.constant; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class PropertiesConst { + + /** + * 当前版本 + */ + public static String ORION_OPS_VERSION; + + /** + * 登录 token 请求头 + */ + public static String LOGIN_TOKEN_HEADER; + + /** + * 加密密钥 + */ + public static String VALUE_MIX_SECRET_KEY; + + /** + * 机器监控插件最新版本 + */ + public static String MACHINE_MONITOR_LATEST_VERSION; + + @Value("${app.version}") + private void setVersion(String version) { + PropertiesConst.ORION_OPS_VERSION = version; + } + + @Value("${login.token.header}") + private void setLoginTokenHeader(String loginTokenHeader) { + PropertiesConst.LOGIN_TOKEN_HEADER = loginTokenHeader; + } + + @Value("${value.mix.secret.key}") + private void setValueMixSecretKey(String valueMixSecretKey) { + PropertiesConst.VALUE_MIX_SECRET_KEY = valueMixSecretKey; + } + + @Value("${machine.monitor.latest.version}") + private void setMachineMonitorLatestVersion(String agentVersion) { + PropertiesConst.MACHINE_MONITOR_LATEST_VERSION = agentVersion; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/ResultCode.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/ResultCode.java new file mode 100644 index 0000000..7fac52f --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/ResultCode.java @@ -0,0 +1,63 @@ +package cn.orionsec.ops.constant; + +import cn.orionsec.kit.lang.define.wrapper.CodeInfo; + +public enum ResultCode implements CodeInfo { + + /** + * 未认证 + */ + UNAUTHORIZED(700, MessageConst.UNAUTHORIZED), + + /** + * 无权限 + */ + NO_PERMISSION(710, MessageConst.NO_PERMISSION), + + /** + * 文件未找到 + */ + FILE_MISSING(720, MessageConst.FILE_MISSING), + + /** + * IP封禁 + */ + IP_BAN(730, MessageConst.IP_BAN), + + /** + * 用户禁用 + */ + USER_DISABLED(740, MessageConst.USER_DISABLED), + + /** + * 非法访问 + */ + ILLEGAL_ACCESS(750, MessageConst.ILLEGAL_ACCESS), + + /** + * 演示模式不支持此功能 + */ + DEMO_DISABLE_API(760, MessageConst.DEMO_DISABLE_API), + + ; + + private final int code; + + private final String message; + + ResultCode(int code, String message) { + this.code = code; + this.message = message; + } + + @Override + public int code() { + return code; + } + + @Override + public String message() { + return message; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/SchedulerPools.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/SchedulerPools.java new file mode 100644 index 0000000..5236ebd --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/SchedulerPools.java @@ -0,0 +1,245 @@ +package cn.orionsec.ops.constant; + +import cn.orionsec.kit.lang.define.thread.ExecutorBuilder; +import cn.orionsec.kit.lang.utils.Systems; +import cn.orionsec.kit.lang.utils.Threads; + +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; + +public class SchedulerPools { + + private SchedulerPools() { + } + + /** + * terminal 调度线程池 + */ + public static final ThreadPoolExecutor TERMINAL_SCHEDULER = ExecutorBuilder.create() + .namedThreadFactory("terminal-thread-") + .corePoolSize(1) + .maxPoolSize(Integer.MAX_VALUE) + .keepAliveTime(Const.MS_S_60) + .workQueue(new SynchronousQueue<>()) + .allowCoreThreadTimeout(true) + .build(); + + /** + * terminal watcher 调度线程池 + */ + public static final ThreadPoolExecutor TERMINAL_WATCHER_SCHEDULER = ExecutorBuilder.create() + .namedThreadFactory("terminal-watcher-thread-") + .corePoolSize(0) + .maxPoolSize(Integer.MAX_VALUE) + .keepAliveTime(Const.MS_S_30) + .workQueue(new SynchronousQueue<>()) + .allowCoreThreadTimeout(true) + .build(); + + /** + * 命令执行 调度线程池 + */ + public static final ThreadPoolExecutor EXEC_SCHEDULER = ExecutorBuilder.create() + .namedThreadFactory("exec-thread-") + .corePoolSize(1) + .maxPoolSize(Integer.MAX_VALUE) + .keepAliveTime(Const.MS_S_30) + .workQueue(new SynchronousQueue<>()) + .allowCoreThreadTimeout(true) + .build(); + + /** + * tail 调度线程池 + */ + public static final ThreadPoolExecutor TAIL_SCHEDULER = ExecutorBuilder.create() + .namedThreadFactory("tail-thread-") + .corePoolSize(1) + .maxPoolSize(Integer.MAX_VALUE) + .keepAliveTime(Const.MS_S_60) + .workQueue(new SynchronousQueue<>()) + .allowCoreThreadTimeout(true) + .build(); + + + /** + * sftp 传输进度线程池 + */ + public static final ThreadPoolExecutor SFTP_TRANSFER_RATE_SCHEDULER = ExecutorBuilder.create() + .namedThreadFactory("sftp-transfer-rate-thread-") + .corePoolSize(1) + .maxPoolSize(Integer.MAX_VALUE) + .keepAliveTime(Const.MS_S_30) + .workQueue(new SynchronousQueue<>()) + .allowCoreThreadTimeout(true) + .build(); + + + /** + * sftp 上传线程池 + */ + public static final ThreadPoolExecutor SFTP_UPLOAD_SCHEDULER = ExecutorBuilder.create() + .namedThreadFactory("sftp-upload-thread-") + .corePoolSize(6) + .maxPoolSize(6) + .keepAliveTime(Const.MS_S_60) + .workQueue(new LinkedBlockingQueue<>()) + .allowCoreThreadTimeout(true) + .build(); + + /** + * sftp 下载线程池 + */ + public static final ThreadPoolExecutor SFTP_DOWNLOAD_SCHEDULER = ExecutorBuilder.create() + .namedThreadFactory("sftp-download-thread-") + .corePoolSize(6) + .maxPoolSize(6) + .keepAliveTime(Const.MS_S_60) + .workQueue(new LinkedBlockingQueue<>()) + .allowCoreThreadTimeout(true) + .build(); + + /** + * sftp 打包线程池 + */ + public static final ThreadPoolExecutor SFTP_PACKAGE_SCHEDULER = ExecutorBuilder.create() + .namedThreadFactory("sftp-package-thread-") + .corePoolSize(4) + .maxPoolSize(4) + .keepAliveTime(Const.MS_S_60) + .workQueue(new LinkedBlockingQueue<>()) + .allowCoreThreadTimeout(true) + .build(); + + /** + * 应用构建线程池 + */ + public static final ThreadPoolExecutor APP_BUILD_SCHEDULER = ExecutorBuilder.create() + .namedThreadFactory("app-build-thread-") + .corePoolSize(1) + .maxPoolSize(Integer.MAX_VALUE) + .keepAliveTime(Const.MS_S_30) + .workQueue(new SynchronousQueue<>()) + .allowCoreThreadTimeout(true) + .build(); + + /** + * 应用发布 主线程操作线程池 + */ + public static final ThreadPoolExecutor RELEASE_MAIN_SCHEDULER = ExecutorBuilder.create() + .namedThreadFactory("release-main-thread-") + .corePoolSize(1) + .maxPoolSize(Integer.MAX_VALUE) + .keepAliveTime(Const.MS_S_30) + .workQueue(new SynchronousQueue<>()) + .allowCoreThreadTimeout(true) + .build(); + + /** + * 应用发布 机器操作线程池 + */ + public static final ThreadPoolExecutor RELEASE_MACHINE_SCHEDULER = ExecutorBuilder.create() + .namedThreadFactory("release-machine-thread-") + .corePoolSize(1) + .maxPoolSize(Integer.MAX_VALUE) + .keepAliveTime(Const.MS_S_30) + .workQueue(new SynchronousQueue<>()) + .allowCoreThreadTimeout(true) + .build(); + + /** + * 调度任务 主进程操作线程池 + */ + public static final ThreadPoolExecutor SCHEDULER_TASK_MAIN_SCHEDULER = ExecutorBuilder.create() + .namedThreadFactory("scheduler-task-main-thread-") + .corePoolSize(1) + .maxPoolSize(Integer.MAX_VALUE) + .keepAliveTime(Const.MS_S_30) + .workQueue(new SynchronousQueue<>()) + .allowCoreThreadTimeout(true) + .build(); + + /** + * 调度任务 机器操作线程池 + */ + public static final ThreadPoolExecutor SCHEDULER_TASK_MACHINE_SCHEDULER = ExecutorBuilder.create() + .namedThreadFactory("scheduler-task-machine-thread-") + .corePoolSize(1) + .maxPoolSize(Integer.MAX_VALUE) + .keepAliveTime(Const.MS_S_30) + .workQueue(new SynchronousQueue<>()) + .allowCoreThreadTimeout(true) + .build(); + + /** + * 应用流水线 线程池 + */ + public static final ThreadPoolExecutor PIPELINE_SCHEDULER = ExecutorBuilder.create() + .namedThreadFactory("pipeline-thread-") + .corePoolSize(1) + .maxPoolSize(Integer.MAX_VALUE) + .keepAliveTime(Const.MS_S_30) + .workQueue(new SynchronousQueue<>()) + .allowCoreThreadTimeout(true) + .build(); + + /** + * 异步导入 线程池 + */ + public static final ThreadPoolExecutor ASYNC_IMPORT_SCHEDULER = ExecutorBuilder.create() + .namedThreadFactory("async-import-thread-") + .corePoolSize(1) + .maxPoolSize(4) + .keepAliveTime(Const.MS_S_30) + .workQueue(new LinkedBlockingQueue<>()) + .allowCoreThreadTimeout(true) + .build(); + + /** + * 插件安装 线程池 + */ + public static final ThreadPoolExecutor AGENT_INSTALL_SCHEDULER = ExecutorBuilder.create() + .namedThreadFactory("agent-install-thread-") + .corePoolSize(4) + .maxPoolSize(4) + .keepAliveTime(Const.MS_S_30) + .workQueue(new LinkedBlockingQueue<>()) + .allowCoreThreadTimeout(true) + .build(); + + /** + * 机器报警 线程池 + */ + public static final ThreadPoolExecutor MACHINE_ALARM_SCHEDULER = ExecutorBuilder.create() + .namedThreadFactory("machine-alarm-thread-") + .corePoolSize(1) + .maxPoolSize(Integer.MAX_VALUE) + .keepAliveTime(Const.MS_S_30) + .workQueue(new SynchronousQueue<>()) + .allowCoreThreadTimeout(true) + .build(); + + + static { + Systems.addShutdownHook(() -> { + Threads.shutdownPoolNow(TERMINAL_SCHEDULER, Const.MS_S_3); + Threads.shutdownPoolNow(TERMINAL_WATCHER_SCHEDULER, Const.MS_S_3); + Threads.shutdownPoolNow(EXEC_SCHEDULER, Const.MS_S_3); + Threads.shutdownPoolNow(TAIL_SCHEDULER, Const.MS_S_3); + Threads.shutdownPoolNow(SFTP_TRANSFER_RATE_SCHEDULER, Const.MS_S_3); + Threads.shutdownPoolNow(SFTP_UPLOAD_SCHEDULER, Const.MS_S_3); + Threads.shutdownPoolNow(SFTP_DOWNLOAD_SCHEDULER, Const.MS_S_3); + Threads.shutdownPoolNow(SFTP_PACKAGE_SCHEDULER, Const.MS_S_3); + Threads.shutdownPoolNow(APP_BUILD_SCHEDULER, Const.MS_S_3); + Threads.shutdownPoolNow(RELEASE_MAIN_SCHEDULER, Const.MS_S_3); + Threads.shutdownPoolNow(RELEASE_MACHINE_SCHEDULER, Const.MS_S_3); + Threads.shutdownPoolNow(SCHEDULER_TASK_MAIN_SCHEDULER, Const.MS_S_3); + Threads.shutdownPoolNow(SCHEDULER_TASK_MACHINE_SCHEDULER, Const.MS_S_3); + Threads.shutdownPoolNow(PIPELINE_SCHEDULER, Const.MS_S_3); + Threads.shutdownPoolNow(ASYNC_IMPORT_SCHEDULER, Const.MS_S_3); + Threads.shutdownPoolNow(AGENT_INSTALL_SCHEDULER, Const.MS_S_3); + Threads.shutdownPoolNow(MACHINE_ALARM_SCHEDULER, Const.MS_S_3); + }); + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/alarm/AlarmGroupNotifyType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/alarm/AlarmGroupNotifyType.java new file mode 100644 index 0000000..8ca9ab5 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/alarm/AlarmGroupNotifyType.java @@ -0,0 +1,33 @@ + +package cn.orionsec.ops.constant.alarm; + +import lombok.AllArgsConstructor; +import lombok.Getter; + + +@Getter +@AllArgsConstructor +public enum AlarmGroupNotifyType { + + /** + * webhook 通知 + */ + WEBHOOK(10), + + ; + + private final Integer type; + + public static AlarmGroupNotifyType of(Integer type) { + if (type == null) { + return null; + } + for (AlarmGroupNotifyType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/ActionStatus.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/ActionStatus.java new file mode 100644 index 0000000..355e57f --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/ActionStatus.java @@ -0,0 +1,58 @@ + +package cn.orionsec.ops.constant.app; + +import lombok.AllArgsConstructor; +import lombok.Getter; + + +@AllArgsConstructor +@Getter +public enum ActionStatus { + + /** + * 未开始 + */ + WAIT(10), + + /** + * 进行中 + */ + RUNNABLE(20), + + /** + * 已完成 + */ + FINISH(30), + + /** + * 执行失败 + */ + FAILURE(40), + + /** + * 已跳过 + */ + SKIPPED(50), + + /** + * 已终止 + */ + TERMINATED(60), + + ; + + private final Integer status; + + public static ActionStatus of(Integer status) { + if (status == null) { + return null; + } + for (ActionStatus value : values()) { + if (value.status.equals(status)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/ActionType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/ActionType.java new file mode 100644 index 0000000..26f882c --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/ActionType.java @@ -0,0 +1,104 @@ + +package cn.orionsec.ops.constant.app; + +import lombok.Getter; + + +@Getter +public enum ActionType { + + /** + * 构建-检出代码 + */ + BUILD_CHECKOUT(110, StageType.BUILD), + + /** + * 构建-机器命令 + */ + BUILD_COMMAND(120, StageType.BUILD), + + /** + * 发布-传输产物 + */ + RELEASE_TRANSFER(210, StageType.RELEASE), + + /** + * 发布-机器命令 + */ + RELEASE_COMMAND(220, StageType.RELEASE), + + ; + + private final Integer type; + + private final StageType stage; + + private final Integer stageType; + + ActionType(Integer type, StageType stage) { + this.type = type; + this.stage = stage; + this.stageType = stage.getType(); + } + + public static ActionType of(Integer type) { + if (type == null) { + return null; + } + for (ActionType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + + public static ActionType of(Integer type, Integer stageType) { + if (type == null) { + return null; + } + for (ActionType value : values()) { + if (value.type.equals(type) && value.stageType.equals(stageType)) { + return value; + } + } + return null; + } + + /** + * 是否是构建action + * + * @param type type + * @return res + */ + public static boolean isBuildAction(Integer type) { + if (type == null) { + return false; + } + for (ActionType value : values()) { + if (value.type.equals(type)) { + return StageType.BUILD.equals(value.stage); + } + } + return false; + } + + /** + * 是否是发布action + * + * @param type type + * @return res + */ + public static boolean isReleaseAction(Integer type) { + if (type == null) { + return false; + } + for (ActionType value : values()) { + if (value.type.equals(type)) { + return StageType.RELEASE.equals(value.stage); + } + } + return false; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/ApplicationEnvAttr.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/ApplicationEnvAttr.java new file mode 100644 index 0000000..f34e5c7 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/ApplicationEnvAttr.java @@ -0,0 +1,85 @@ + +package cn.orionsec.ops.constant.app; + +import cn.orionsec.ops.constant.common.ExceptionHandlerType; +import cn.orionsec.ops.constant.common.SerialType; +import lombok.Getter; + +import java.util.Arrays; + + +@Getter +public enum ApplicationEnvAttr { + + /** + * 构建产物路径 + */ + BUNDLE_PATH("宿主机构建产物路径 (绝对路径/基于版本仓库的相对路径)"), + + /** + * 产物传输路径 + */ + TRANSFER_PATH("产物传输目标机器绝对路径"), + + /** + * 产物传输方式 (sftp/scp) + * + * @see TransferMode + */ + TRANSFER_MODE("产物传输方式 (sftp/scp)"), + + /** + * 产物传输文件类型 (normal/zip) + * + * @see TransferFileType + */ + TRANSFER_FILE_TYPE("产物传输文件类型 (normal/zip)"), + + /** + * 发布序列方式 + * + * @see SerialType + */ + RELEASE_SERIAL("发布序列方式 (serial/parallel)"), + + /** + * 异常处理类型 + * + * @see SerialType#SERIAL + * @see ExceptionHandlerType + */ + EXCEPTION_HANDLER("异常处理类型 (skip_all/skip_error)"), + + /** + * 构建序列号 + */ + BUILD_SEQ("构建序列号 (自增)"), + + ; + + /** + * key + */ + private final String key; + + /** + * 描述 + */ + private final String description; + + ApplicationEnvAttr(String description) { + this.description = description; + this.key = this.name().toLowerCase(); + } + + public static ApplicationEnvAttr of(String key) { + if (key == null) { + return null; + } + return Arrays.stream(values()) + .filter(a -> a.key.equals(key)) + .findFirst() + .orElse(null); + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/BuildStatus.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/BuildStatus.java new file mode 100644 index 0000000..da1bbbf --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/BuildStatus.java @@ -0,0 +1,52 @@ + +package cn.orionsec.ops.constant.app; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum BuildStatus { + + /** + * 未开始 + */ + WAIT(10), + + /** + * 进行中 + */ + RUNNABLE(20), + + /** + * 已完成 + */ + FINISH(30), + + /** + * 执行失败 + */ + FAILURE(40), + + /** + * 已终止 + */ + TERMINATED(50), + + ; + + private final Integer status; + + public static BuildStatus of(Integer status) { + if (status == null) { + return null; + } + for (BuildStatus value : values()) { + if (value.status.equals(status)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/PipelineDetailStatus.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/PipelineDetailStatus.java new file mode 100644 index 0000000..9267a6c --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/PipelineDetailStatus.java @@ -0,0 +1,58 @@ + +package cn.orionsec.ops.constant.app; + +import lombok.AllArgsConstructor; +import lombok.Getter; + + +@AllArgsConstructor +@Getter +public enum PipelineDetailStatus { + + /** + * 未开始 + */ + WAIT(10), + + /** + * 进行中 + */ + RUNNABLE(20), + + /** + * 已完成 + */ + FINISH(30), + + /** + * 执行失败 + */ + FAILURE(40), + + /** + * 已跳过 + */ + SKIPPED(50), + + /** + * 已终止 + */ + TERMINATED(60), + + ; + + private final Integer status; + + public static PipelineDetailStatus of(Integer status) { + if (status == null) { + return null; + } + for (PipelineDetailStatus value : values()) { + if (value.status.equals(status)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/PipelineLogStatus.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/PipelineLogStatus.java new file mode 100644 index 0000000..99f60f3 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/PipelineLogStatus.java @@ -0,0 +1,84 @@ + +package cn.orionsec.ops.constant.app; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.ops.utils.Valid; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum PipelineLogStatus { + + /** + * 创建 + */ + CREATE(10, "已创建应用 {} 构建任务 #{}", + "已创建应用 {} 发布任务 #{}"), + + /** + * 开始执行 + */ + EXEC(20, "开始执行应用 {} 构建任务", + "开始执行应用 {} 发布任务"), + + /** + * 执行成功 + */ + SUCCESS(30, "应用 {} 构建任务执行成功", + "应用 {} 发布任务执行成功"), + + /** + * 执行失败 + */ + FAILURE(40, "应用 {} 构建任务执行失败", + "应用 {} 发布任务执行失败"), + + /** + * 停止执行 + */ + TERMINATED(50, "应用 {} 构建任务已停止执行", + "应用 {} 发布任务已停止执行"), + + /** + * 跳过执行 + */ + SKIP(60, "应用 {} 构建任务已跳过执行", + "应用 {} 发布任务已跳过执行"), + + ; + + /** + * 状态 + */ + private final Integer status; + + /** + * 构建模板 + */ + private final String buildTemplate; + + /** + * 发布模板 + */ + private final String releaseTemplate; + + /** + * 格式日志 + * + * @param stage stage + * @param args 参数 + * @return log + */ + public String format(StageType stage, Object... args) { + Valid.notNull(stage); + if (StageType.BUILD.equals(stage)) { + return Strings.format(buildTemplate, args); + } else if (StageType.RELEASE.equals(stage)) { + return Strings.format(releaseTemplate, args); + } else { + return Strings.EMPTY; + } + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/PipelineStatus.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/PipelineStatus.java new file mode 100644 index 0000000..9469a34 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/PipelineStatus.java @@ -0,0 +1,68 @@ + +package cn.orionsec.ops.constant.app; + +import lombok.AllArgsConstructor; +import lombok.Getter; + + +@AllArgsConstructor +@Getter +public enum PipelineStatus { + + /** + * 待审核 + */ + WAIT_AUDIT(10), + + /** + * 审核驳回 + */ + AUDIT_REJECT(20), + + /** + * 待执行 + */ + WAIT_RUNNABLE(30), + + /** + * 待调度 + */ + WAIT_SCHEDULE(35), + + /** + * 执行中 + */ + RUNNABLE(40), + + /** + * 执行完成 + */ + FINISH(50), + + /** + * 执行停止 + */ + TERMINATED(60), + + /** + * 执行失败 + */ + FAILURE(70), + + ; + + private final Integer status; + + public static PipelineStatus of(Integer status) { + if (status == null) { + return null; + } + for (PipelineStatus value : values()) { + if (value.status.equals(status)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/ReleaseStatus.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/ReleaseStatus.java new file mode 100644 index 0000000..ce523b5 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/ReleaseStatus.java @@ -0,0 +1,68 @@ + +package cn.orionsec.ops.constant.app; + +import lombok.AllArgsConstructor; +import lombok.Getter; + + +@AllArgsConstructor +@Getter +public enum ReleaseStatus { + + /** + * 待审核 + */ + WAIT_AUDIT(10), + + /** + * 审核驳回 + */ + AUDIT_REJECT(20), + + /** + * 待发布 + */ + WAIT_RUNNABLE(30), + + /** + * 待调度 + */ + WAIT_SCHEDULE(35), + + /** + * 发布中 + */ + RUNNABLE(40), + + /** + * 发布完成 + */ + FINISH(50), + + /** + * 发布停止 + */ + TERMINATED(60), + + /** + * 发布失败 + */ + FAILURE(70), + + ; + + private final Integer status; + + public static ReleaseStatus of(Integer status) { + if (status == null) { + return null; + } + for (ReleaseStatus value : values()) { + if (value.status.equals(status)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/ReleaseType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/ReleaseType.java new file mode 100644 index 0000000..495796e --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/ReleaseType.java @@ -0,0 +1,37 @@ + +package cn.orionsec.ops.constant.app; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum ReleaseType { + + /** + * 正常发布 + */ + NORMAL(10), + + /** + * 回滚发布 + */ + ROLLBACK(20), + + ; + + private final Integer type; + + public static ReleaseType of(Integer type) { + if (type == null) { + return null; + } + for (ReleaseType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/RepositoryAuthType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/RepositoryAuthType.java new file mode 100644 index 0000000..4d84e01 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/RepositoryAuthType.java @@ -0,0 +1,52 @@ + +package cn.orionsec.ops.constant.app; + +import cn.orionsec.ops.constant.CnConst; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum RepositoryAuthType { + + /** + * 密码 + */ + PASSWORD(10, CnConst.PASSWORD), + + /** + * 私人令牌 + */ + TOKEN(20, CnConst.TOKEN), + + ; + + private final Integer type; + + private final String label; + + public static RepositoryAuthType of(Integer type) { + if (type == null) { + return null; + } + for (RepositoryAuthType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + + public static RepositoryAuthType of(String label) { + if (label == null) { + return null; + } + for (RepositoryAuthType value : values()) { + if (value.label.equals(label)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/RepositoryStatus.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/RepositoryStatus.java new file mode 100644 index 0000000..44e5e14 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/RepositoryStatus.java @@ -0,0 +1,51 @@ + +package cn.orionsec.ops.constant.app; + +import lombok.AllArgsConstructor; +import lombok.Getter; + + +@AllArgsConstructor +@Getter +public enum RepositoryStatus { + + /** + * 未初始化 + */ + UNINITIALIZED(10), + + /** + * 初始化中 + */ + INITIALIZING(20), + + /** + * 正常 + */ + OK(30), + + /** + * 失败 + */ + ERROR(40), + + ; + + /** + * 状态 + */ + private final Integer status; + + public static RepositoryStatus of(Integer status) { + if (status == null) { + return null; + } + for (RepositoryStatus value : values()) { + if (value.status.equals(status)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/RepositoryTokenType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/RepositoryTokenType.java new file mode 100644 index 0000000..f305092 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/RepositoryTokenType.java @@ -0,0 +1,64 @@ + +package cn.orionsec.ops.constant.app; + +import cn.orionsec.ops.constant.Const; +import lombok.AllArgsConstructor; +import lombok.Getter; + + +@AllArgsConstructor +@Getter +public enum RepositoryTokenType { + + /** + * github + *

+ * username: '' + */ + GITHUB(10, Const.GITHUB), + + /** + * gitee + *

+ * username: username + */ + GITEE(20, Const.GITEE), + + /** + * gitlab + *

+ * username oauth + */ + GITLAB(30, Const.GITLAB), + + ; + + private final Integer type; + + private final String label; + + public static RepositoryTokenType of(Integer type) { + if (type == null) { + return null; + } + for (RepositoryTokenType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + + public static RepositoryTokenType of(String label) { + if (label == null) { + return null; + } + for (RepositoryTokenType value : values()) { + if (value.label.equals(label.toLowerCase())) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/RepositoryType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/RepositoryType.java new file mode 100644 index 0000000..f32644a --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/RepositoryType.java @@ -0,0 +1,33 @@ + +package cn.orionsec.ops.constant.app; + +import lombok.AllArgsConstructor; +import lombok.Getter; + + +@AllArgsConstructor +@Getter +public enum RepositoryType { + + /** + * git + */ + GIT(1), + + ; + + private final Integer type; + + public static RepositoryType of(Integer type) { + if (type == null) { + return null; + } + for (RepositoryType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/StageType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/StageType.java new file mode 100644 index 0000000..0798174 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/StageType.java @@ -0,0 +1,39 @@ + +package cn.orionsec.ops.constant.app; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum StageType { + + /** + * 构建 + */ + BUILD(10, "构建"), + + /** + * 发布 + */ + RELEASE(20, "发布"), + + ; + + private final Integer type; + + private final String label; + + public static StageType of(Integer type) { + if (type == null) { + return null; + } + for (StageType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/TimedType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/TimedType.java new file mode 100644 index 0000000..a41b979 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/TimedType.java @@ -0,0 +1,38 @@ + +package cn.orionsec.ops.constant.app; + +import lombok.AllArgsConstructor; +import lombok.Getter; + + +@AllArgsConstructor +@Getter +public enum TimedType { + + /** + * 普通 + */ + NORMAL(10), + + /** + * 定时 + */ + TIMED(20), + + ; + + private final Integer type; + + public static TimedType of(Integer type) { + if (type == null) { + return null; + } + for (TimedType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/TransferFileType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/TransferFileType.java new file mode 100644 index 0000000..6cf5cb4 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/TransferFileType.java @@ -0,0 +1,39 @@ + +package cn.orionsec.ops.constant.app; + +import lombok.Getter; + +@Getter +public enum TransferFileType { + + /** + * 文件 / 文件夹 + */ + NORMAL, + + /** + * 文件夹 zip 文件 + */ + ZIP, + + ; + + TransferFileType() { + this.value = name().toLowerCase(); + } + + private final String value; + + public static TransferFileType of(String value) { + if (value == null) { + return NORMAL; + } + for (TransferFileType type : values()) { + if (type.value.equals(value)) { + return type; + } + } + return NORMAL; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/TransferMode.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/TransferMode.java new file mode 100644 index 0000000..442eba6 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/app/TransferMode.java @@ -0,0 +1,39 @@ + +package cn.orionsec.ops.constant.app; + +import lombok.Getter; + +@Getter +public enum TransferMode { + + /** + * scp + */ + SCP, + + /** + * sftp + */ + SFTP, + + ; + + TransferMode() { + this.value = name().toLowerCase(); + } + + private final String value; + + public static TransferMode of(String value) { + if (value == null) { + return SCP; + } + for (TransferMode type : values()) { + if (type.value.equals(value)) { + return type; + } + } + return SCP; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/command/CommandConst.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/command/CommandConst.java new file mode 100644 index 0000000..856542a --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/command/CommandConst.java @@ -0,0 +1,24 @@ + +package cn.orionsec.ops.constant.command; + +import cn.orionsec.ops.constant.env.EnvConst; + + +public class CommandConst { + + private CommandConst() { + } + + public static final String TAIL_FILE_DEFAULT = "tail -f -n " + + EnvConst.getReplaceVariable(EnvConst.OFFSET) + + " '" + EnvConst.getReplaceVariable(EnvConst.FILE) + + "'"; + + public static final String SCP_TRANSFER_DEFAULT = "scp \"" + + EnvConst.getReplaceVariable(EnvConst.BUNDLE_PATH) + + "\" " + EnvConst.getReplaceVariable(EnvConst.TARGET_USERNAME) + + "@" + EnvConst.getReplaceVariable(EnvConst.TARGET_HOST) + + ":\"" + EnvConst.getReplaceVariable(EnvConst.TRANSFER_PATH) + + "\""; + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/command/ExecStatus.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/command/ExecStatus.java new file mode 100644 index 0000000..c99ae31 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/command/ExecStatus.java @@ -0,0 +1,53 @@ + +package cn.orionsec.ops.constant.command; + +import lombok.AllArgsConstructor; +import lombok.Getter; + + +@AllArgsConstructor +@Getter +public enum ExecStatus { + + /** + * 10未开始 + */ + WAITING(10), + + /** + * 20执行中 + */ + RUNNABLE(20), + + /** + * 30执行完成 + */ + COMPLETE(30), + + /** + * 40执行异常 + */ + EXCEPTION(40), + + /** + * 50执行终止 + */ + TERMINATED(50), + + ; + + private final Integer status; + + public static ExecStatus of(Integer status) { + if (status == null) { + return null; + } + for (ExecStatus value : values()) { + if (value.status.equals(status)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/command/ExecType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/command/ExecType.java new file mode 100644 index 0000000..f195aa7 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/command/ExecType.java @@ -0,0 +1,32 @@ + +package cn.orionsec.ops.constant.command; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum ExecType { + + /** + * 批量执行 + */ + BATCH_EXEC(10), + + ; + + /** + * 类型 + */ + private final Integer type; + + public static ExecType of(Integer type) { + for (ExecType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/AuditStatus.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/AuditStatus.java new file mode 100644 index 0000000..1b876db --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/AuditStatus.java @@ -0,0 +1,37 @@ + +package cn.orionsec.ops.constant.common; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum AuditStatus { + + /** + * 通过 + */ + RESOLVE(10), + + /** + * 驳回 + */ + REJECT(20), + + ; + + private final Integer status; + + public static AuditStatus of(Integer status) { + if (status == null) { + return null; + } + for (AuditStatus value : values()) { + if (value.status.equals(status)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/EnableType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/EnableType.java new file mode 100644 index 0000000..af3d4bb --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/EnableType.java @@ -0,0 +1,51 @@ + +package cn.orionsec.ops.constant.common; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum EnableType { + + /** + * 启用 + */ + ENABLED(Boolean.TRUE, "enabled"), + + /** + * 停用 + */ + DISABLED(Boolean.FALSE, "disabled"), + + ; + + private final Boolean value; + + private final String label; + + public static EnableType of(String label) { + if (label == null) { + return DISABLED; + } + for (EnableType value : values()) { + if (value.label.equals(label)) { + return value; + } + } + return DISABLED; + } + + public static EnableType of(Boolean value) { + if (value == null) { + return DISABLED; + } + for (EnableType type : values()) { + if (type.value.equals(value)) { + return type; + } + } + return DISABLED; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/ExceptionHandlerType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/ExceptionHandlerType.java new file mode 100644 index 0000000..175f04f --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/ExceptionHandlerType.java @@ -0,0 +1,52 @@ + +package cn.orionsec.ops.constant.common; + +import lombok.AllArgsConstructor; +import lombok.Getter; + + +@Getter +@AllArgsConstructor +public enum ExceptionHandlerType { + + /** + * 跳过所有 中断执行 + */ + SKIP_ALL(10, "skip_all"), + + /** + * 跳过错误 继续执行 + */ + SKIP_ERROR(20, "skip_error"), + + ; + + private final Integer type; + + private final String value; + + public static ExceptionHandlerType of(Integer type) { + if (type == null) { + return ExceptionHandlerType.SKIP_ALL; + } + for (ExceptionHandlerType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return ExceptionHandlerType.SKIP_ALL; + } + + public static ExceptionHandlerType of(String value) { + if (value == null) { + return ExceptionHandlerType.SKIP_ALL; + } + for (ExceptionHandlerType type : values()) { + if (type.value.equals(value)) { + return type; + } + } + return ExceptionHandlerType.SKIP_ALL; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/SerialType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/SerialType.java new file mode 100644 index 0000000..6597b78 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/SerialType.java @@ -0,0 +1,50 @@ +package cn.orionsec.ops.constant.common; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum SerialType { + + /** + * 串行 + */ + SERIAL(10, "serial"), + + /** + * 并行 + */ + PARALLEL(20, "parallel"), + + ; + + private final Integer type; + + private final String value; + + public static SerialType of(Integer type) { + if (type == null) { + return PARALLEL; + } + for (SerialType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return PARALLEL; + } + + public static SerialType of(String value) { + if (value == null) { + return PARALLEL; + } + for (SerialType type : values()) { + if (type.value.equalsIgnoreCase(value)) { + return type; + } + } + return PARALLEL; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/StainCode.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/StainCode.java new file mode 100644 index 0000000..6786652 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/StainCode.java @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2021 - present Jiahang Li All rights reserved. + * + * https://ops.orionsec.cn + * + * Members: + * Jiahang Li - ljh1553488six@139.com - author + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cn.orionsec.ops.constant.common; + +/** + * ANSI 高亮颜色转义码 + *

+ * \u001B = \x1b = 27 = esc + *

+ * 基本8色 基本高对比色 xterm 256 色 + * 30 ~ 37 90 ~ 97 0 ~ 256 + *

+ * \033[0m 关闭所有属性 + * \033[1m 设置高亮度 + * \033[4m 下划线 + * \033[5m 闪烁 + * \033[7m 反显 + * \033[8m 消隐 + * \033[30m 至 \33[37m 设置前景色 + * \033[40m 至 \33[47m 设置背景色 + * \033[nA 光标上移n行 + * \033[nB 光标下移n行 + * \033[nC 光标右移n行 + * \033[nD 光标左移n行 + * \033[y;xH 设置光标位置 + * \033[2J 清屏 + * \033[K 清除从光标到行尾的内容 + * \033[s 保存光标位置 + * \033[u 恢复光标位置 + * \033[?25l 隐藏光标 + * \033[?25h 显示光标 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2022/4/20 23:16 + */ +public class StainCode { + + private StainCode() { + } + + /** + * 结束 + * \x1b[0m + */ + public static String SUFFIX = (char) 27 + "[0m"; + + // -------------------- 颜色 -------------------- + + /** + * 黑色 + */ + public static final int BLACK = 30; + + /** + * 红色 + */ + public static final int RED = 31; + + /** + * 绿色 + */ + public static final int GREEN = 32; + + /** + * 黄色 + */ + public static final int YELLOW = 33; + + /** + * 蓝色 + */ + public static final int BLUE = 34; + + /** + * 紫色 + */ + public static final int PURPLE = 35; + + /** + * 青色 + */ + public static final int CYAN = 36; + + /** + * 白色 + */ + public static final int WHITE = 37; + + // -------------------- 背景色 -------------------- + + /** + * 黑色 背景色 + */ + public static final int BG_BLACK = 40; + + /** + * 红色 背景色 + */ + public static final int BG_RED = 41; + + /** + * 绿色 背景色 + */ + public static final int BG_GREEN = 42; + + /** + * 黄色 背景色 + */ + public static final int BG_YELLOW = 43; + + /** + * 蓝色 背景色 + */ + public static final int BG_BLUE = 44; + + /** + * 紫色 背景色 + */ + public static final int BG_PURPLE = 45; + + /** + * 青色 背景色 + */ + public static final int BG_CYAN = 46; + + /** + * 白色 背景色 + */ + public static final int BG_WHITE = 47; + + // -------------------- 亮色 -------------------- + + /** + * 亮黑色 (灰) + */ + public static final int GLOSS_BLACK = 90; + + /** + * 亮红色 + */ + public static final int GLOSS_RED = 91; + + /** + * 亮绿色 + */ + public static final int GLOSS_GREEN = 92; + + /** + * 亮黄色 + */ + public static final int GLOSS_YELLOW = 93; + + /** + * 亮蓝色 + */ + public static final int GLOSS_BLUE = 94; + + /** + * 亮紫色 + */ + public static final int GLOSS_PURPLE = 95; + + /** + * 亮青色 + */ + public static final int GLOSS_CYAN = 96; + + /** + * 亮白色 + */ + public static final int GLOSS_WHITE = 97; + + // -------------------- 亮背景色 -------------------- + + /** + * 亮黑色 (灰) 背景色 + */ + public static final int BG_GLOSS_BLACK = 100; + + /** + * 亮红色 背景色 + */ + public static final int BG_GLOSS_RED = 101; + + /** + * 亮绿色 背景色 + */ + public static final int BG_GLOSS_GREEN = 102; + + /** + * 亮黄色 背景色 + */ + public static final int BG_GLOSS_YELLOW = 103; + + /** + * 亮蓝色 背景色 + */ + public static final int BG_GLOSS_BLUE = 104; + + /** + * 亮紫色 背景色 + */ + public static final int BG_GLOSS_PURPLE = 105; + + /** + * 亮青色 背景色 + */ + public static final int BG_GLOSS_CYAN = 106; + + /** + * 亮白色 背景色 + */ + public static final int BG_GLOSS_WHITE = 107; + + /** + * 获取颜色前缀 + * .e.g \x1b[31m + * + * @param code code + * @return 前缀 + */ + public static String prefix(int code) { + return (char) 27 + "[" + code + "m"; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/TreeMoveType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/TreeMoveType.java new file mode 100644 index 0000000..bf06194 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/common/TreeMoveType.java @@ -0,0 +1,46 @@ +package cn.orionsec.ops.constant.common; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum TreeMoveType { + + /** + * 移动到内层 上面 + */ + IN_TOP(1), + + /** + * 移动到内层 下面 + */ + IN_BOTTOM(2), + + /** + * 移动到节点 上面 + */ + PREV(3), + + /** + * 移动到节点 下面 + */ + NEXT(4), + + ; + + private final Integer type; + + public static TreeMoveType of(Integer type) { + if (type == null) { + return null; + } + for (TreeMoveType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/download/FileDownloadType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/download/FileDownloadType.java new file mode 100644 index 0000000..3c0e58f --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/download/FileDownloadType.java @@ -0,0 +1,98 @@ +package cn.orionsec.ops.constant.download; + +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum FileDownloadType { + + /** + * 密钥 + * + * @see SystemEnvAttr#KEY_PATH + */ + SECRET_KEY(10), + + /** + * terminal 录屏 + * + * @see SystemEnvAttr#LOG_PATH + */ + TERMINAL_SCREEN(20), + + /** + * 命令 执行日志 + * + * @see SystemEnvAttr#LOG_PATH + */ + EXEC_LOG(30), + + /** + * sftp 下载文件 + * + * @see SystemEnvAttr#SWAP_PATH + */ + SFTP_DOWNLOAD(40), + + /** + * tail 列表文件 + */ + TAIL_LIST_FILE(50), + + /** + * 应用构建日志 + * + * @see SystemEnvAttr#LOG_PATH + */ + APP_BUILD_LOG(60), + + /** + * 应用构建操作日志 + * + * @see SystemEnvAttr#LOG_PATH + */ + APP_ACTION_LOG(70), + + /** + * 应用构建 产物文件 + * + * @see SystemEnvAttr#DIST_PATH + */ + APP_BUILD_BUNDLE(80), + + /** + * 应用发布 机器日志 + * + * @see SystemEnvAttr#LOG_PATH + */ + APP_RELEASE_MACHINE_LOG(90), + + /** + * 调度任务机器日志 + * + * @see SystemEnvAttr#LOG_PATH + */ + SCHEDULER_TASK_MACHINE_LOG(110), + + ; + + /** + * 类型 + */ + private final Integer type; + + public static FileDownloadType of(Integer type) { + if (type == null) { + return null; + } + for (FileDownloadType value : values()) { + if (type.equals(value.type)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/env/EnvConst.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/env/EnvConst.java new file mode 100644 index 0000000..ef44d43 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/env/EnvConst.java @@ -0,0 +1,99 @@ + +package cn.orionsec.ops.constant.env; + +public class EnvConst { + + private EnvConst() { + } + + // -------------------- symbol -------------------- + + public static final String SYMBOL = "@"; + + // -------------------- prefix -------------------- + + public static final String MACHINE_PREFIX = "machine."; + + public static final String APP_PREFIX = "app."; + + public static final String BUILD_PREFIX = "build."; + + public static final String RELEASE_PREFIX = "release."; + + public static final String SYSTEM_PREFIX = "system."; + + // -------------------- machine -------------------- + + public static final String MACHINE_ID = "machine_id"; + + public static final String MACHINE_NAME = "machine_name"; + + public static final String MACHINE_TAG = "machine_tag"; + + public static final String MACHINE_HOST = "machine_host"; + + public static final String MACHINE_PORT = "machine_port"; + + public static final String MACHINE_USERNAME = "machine_username"; + + // -------------------- app -------------------- + + public static final String APP_ID = "app_id"; + + public static final String APP_NAME = "app_name"; + + public static final String APP_TAG = "app_tag"; + + public static final String PROFILE_ID = "profile_id"; + + public static final String PROFILE_NAME = "profile_name"; + + public static final String PROFILE_TAG = "profile_tag"; + + // -------------------- build -------------------- + + public static final String BUILD_ID = "build_id"; + + public static final String BUILD_SEQ = "build_seq"; + + public static final String REPO_HOME = "repo_home"; + + public static final String REPO_EVENT_HOME = "repo_event_home"; + + public static final String BRANCH = "branch"; + + public static final String COMMIT = "commit"; + + public static final String BUNDLE_PATH = "bundle_path"; + + // -------------------- release -------------------- + + public static final String RELEASE_ID = "release_id"; + + public static final String RELEASE_TITLE = "release_title"; + + public static final String TRANSFER_PATH = "transfer_path"; + + // -------------------- tail -------------------- + + public static final String OFFSET = "offset"; + + public static final String FILE = "file"; + + // -------------------- scp -------------------- + + public static final String TARGET_USERNAME = "target_username"; + + public static final String TARGET_HOST = "target_host"; + + /** + * 获取替换变量 + * + * @param name name + * @return command + */ + public static String getReplaceVariable(String name) { + return SYMBOL + "{" + name + "}"; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/env/EnvViewType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/env/EnvViewType.java new file mode 100644 index 0000000..2323b6a --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/env/EnvViewType.java @@ -0,0 +1,92 @@ +package cn.orionsec.ops.constant.env; + +import cn.orionsec.kit.lang.define.collect.MutableLinkedHashMap; +import cn.orionsec.ops.utils.AttrConverts; +import lombok.AllArgsConstructor; + +import java.util.Map; + +@AllArgsConstructor +public enum EnvViewType { + + /** + * json + */ + JSON(10) { + @Override + public String toValue(Map value) { + return AttrConverts.toJson(value); + } + + @Override + public MutableLinkedHashMap toMap(String value) { + return AttrConverts.fromJson(value); + } + }, + + /** + * xml + */ + XML(20) { + @Override + public String toValue(Map value) { + return AttrConverts.toXml(value); + } + + @Override + public MutableLinkedHashMap toMap(String value) { + return AttrConverts.fromXml(value); + } + }, + + /** + * yml + */ + YML(30) { + @Override + public String toValue(Map value) { + return AttrConverts.toYml(value); + } + + @Override + public MutableLinkedHashMap toMap(String value) { + return AttrConverts.fromYml(value); + } + }, + + /** + * properties + */ + PROPERTIES(40) { + @Override + public String toValue(Map value) { + return AttrConverts.toProperties(value); + } + + @Override + public MutableLinkedHashMap toMap(String value) { + return AttrConverts.fromProperties(value); + } + }, + + ; + + private final Integer type; + + public abstract String toValue(Map value); + + public abstract MutableLinkedHashMap toMap(String value); + + public static EnvViewType of(Integer type) { + if (type == null) { + return null; + } + for (EnvViewType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/event/EventClassify.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/event/EventClassify.java new file mode 100644 index 0000000..929481d --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/event/EventClassify.java @@ -0,0 +1,171 @@ +package cn.orionsec.ops.constant.event; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum EventClassify { + + /** + * 认证操作 + */ + AUTHENTICATION(100, "认证操作"), + + /** + * 用户操作 + */ + USER(110, "用户操作"), + + /** + * 报警组操作 + */ + ALARM_GROUP(120, "报警组操作"), + + /** + * 机器操作 + */ + MACHINE(200, "机器操作"), + + /** + * 机器环境变量操作 + */ + MACHINE_ENV(210, "机器环境变量操作"), + + /** + * 密钥操作 + */ + MACHINE_KEY(220, "密钥操作"), + + /** + * 代理操作 + */ + MACHINE_PROXY(230, "代理操作"), + + /** + * 机器监控 + */ + MACHINE_MONITOR(240, "机器监控"), + + /** + * 机器报警 + */ + MACHINE_ALARM(250, "机器报警"), + + /** + * 终端操作 + */ + TERMINAL(260, "终端操作"), + + /** + * sftp 操作 + */ + SFTP(270, "sftp 操作"), + + /** + * 批量执行操作 + */ + EXEC(300, "批量执行操作"), + + /** + * 日志追踪操作 + */ + TAIL(310, "日志追踪操作"), + + /** + * 调度操作 + */ + SCHEDULER(320, "调度操作"), + + /** + * 应用操作 + */ + APP(400, "应用操作"), + + /** + * 环境操作 + */ + PROFILE(410, "环境操作"), + + /** + * 应用环境变量操作 + */ + APP_ENV(420, "应用环境变量操作"), + + /** + * 应用仓库操作 + */ + REPOSITORY(430, "应用仓库操作"), + + /** + * 应用构建操作 + */ + BUILD(440, "应用构建操作"), + + /** + * 应用发布操作 + */ + RELEASE(450, "应用发布操作"), + + /** + * 应用流水线 + */ + PIPELINE(460, "应用流水线"), + + /** + * 模板操作 + */ + TEMPLATE(500, "模板操作"), + + /** + * webhook 操作 + */ + WEBHOOK(510, "webhook操作"), + + /** + * 系统操作 + */ + SYSTEM(600, "系统操作"), + + /** + * 系统环境变量操作 + */ + SYSTEM_ENV(610, "系统环境变量操作"), + + /** + * 数据清理 + */ + DATA_CLEAR(620, "数据清理"), + + /** + * 数据导入 + */ + DATA_IMPORT(630, "数据导入"), + + /** + * 数据导出 + */ + DATA_EXPORT(640, "数据导出"), + + ; + + /** + * 分类 + */ + private final Integer classify; + + private final String label; + + public static EventClassify of(Integer classify) { + if (classify == null) { + return null; + } + for (EventClassify value : values()) { + if (value.classify.equals(classify)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/event/EventKeys.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/event/EventKeys.java new file mode 100644 index 0000000..9ed2203 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/event/EventKeys.java @@ -0,0 +1,268 @@ +package cn.orionsec.ops.constant.event; + +public class EventKeys { + + private EventKeys() { + } + + /** + * 内置用户id + */ + public static final String INNER_USER_ID = "_USER_ID_"; + + /** + * 内置用户名称 + */ + public static final String INNER_USER_NAME = "_USER_NAME_"; + + /** + * 内置是否保存 + */ + public static final String INNER_SAVE = "_SAVE_"; + + /** + * 内置模板 + */ + public static final String INNER_TEMPLATE = "_TEMPLATE_"; + + /** + * 请求序列 + */ + public static final String INNER_REQUEST_SEQ = "_REQUEST_SEQ_"; + + /** + * 请求 UA + */ + public static final String INNER_REQUEST_USER_AGENT = "userAgent"; + + /** + * 请求 ip + */ + public static final String INNER_REQUEST_IP = "ip"; + + /** + * 请求时间 + */ + public static final String INNER_REQUEST_TIME = "requestTime"; + + /** + * id + */ + public static final String ID = "id"; + + /** + * id list + */ + public static final String ID_LIST = "idList"; + + /** + * machine id + */ + public static final String MACHINE_ID = "machineId"; + + /** + * machine id list + */ + public static final String MACHINE_ID_LIST = "machineIdList"; + + /** + * app id + */ + public static final String APP_ID = "appId"; + + /** + * profile id + */ + public static final String PROFILE_ID = "profileId"; + + /** + * detail id + */ + public static final String DETAIL_ID = "detailId"; + + /** + * type + */ + public static final String TYPE = "type"; + + /** + * value + */ + public static final String VALUE = "value"; + + /** + * token + */ + public static final String TOKEN = "token"; + + /** + * 名称 + */ + public static final String NAME = "name"; + + /** + * 主机 + */ + public static final String HOST = "host"; + + /** + * 用户名 + */ + public static final String USERNAME = "username"; + + /** + * 源 + */ + public static final String SOURCE = "source"; + + /** + * 目标 + */ + public static final String TARGET = "target"; + + /** + * 数量 + */ + public static final String COUNT = "count"; + + /** + * 环境数量 + */ + public static final String ENV_COUNT = "envCount"; + + /** + * 机器数量 + */ + public static final String MACHINE_COUNT = "machineCount"; + + /** + * 机器名称 + */ + public static final String MACHINE_NAME = "machineName"; + + /** + * 环境数量 + */ + public static final String PROFILE_COUNT = "profileCount"; + + /** + * 路径 + */ + public static final String PATH = "path"; + + /** + * 路径 + */ + public static final String PATHS = "paths"; + + /** + * 操作 + */ + public static final String OPERATOR = "operator"; + + /** + * 序列 + */ + public static final String SEQ = "seq"; + + /** + * 构建序列 + */ + public static final String BUILD_SEQ = "buildSeq"; + + /** + * 环境名称 + */ + public static final String PROFILE_NAME = "profileName"; + + /** + * 应用名称 + */ + public static final String APP_NAME = "appName"; + + /** + * 标题 + */ + public static final String TITLE = "title"; + + /** + * 阶段 + */ + public static final String STAGE = "stage"; + + /** + * 系统 + */ + public static final String SYSTEM = "system"; + + /** + * before + */ + public static final String BEFORE = "before"; + + /** + * after + */ + public static final String AFTER = "after"; + + /** + * key + */ + public static final String KEY = "key"; + + /** + * label + */ + public static final String LABEL = "label"; + + /** + * time + */ + public static final String TIME = "time"; + + /** + * details + */ + public static final String DETAILS = "details"; + + /** + * 流水线id + */ + public static final String PIPELINE_ID = "pipelineId"; + + /** + * 是否导出密码 + */ + public static final String EXPORT_PASSWORD = "exportPassword"; + + /** + * 保护密码 + */ + public static final String PROTECT = "protect"; + + /** + * 分类 + */ + public static final String CLASSIFY = "classify"; + + /** + * 用户id + */ + public static final String USER_ID = "userId"; + + /** + * 状态 + */ + public static final String STATUS = "status"; + + /** + * 是否为自动续签登录 + */ + public static final String REFRESH_LOGIN = "refreshLogin"; + + /** + * 导出类型 + */ + public static final String EXPORT_TYPE = "exportType"; + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/event/EventType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/event/EventType.java new file mode 100644 index 0000000..cdde98c --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/event/EventType.java @@ -0,0 +1,720 @@ +package cn.orionsec.ops.constant.event; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum EventType { + + // -------------------- 认证操作 -------------------- + + /** + * 登录 + */ + LOGIN(100010, EventClassify.AUTHENTICATION, "登录系统", "登录系统"), + + /** + * 登出 + */ + LOGOUT(100020, EventClassify.AUTHENTICATION, "退出系统", "退出系统"), + + /** + * 重置密码 + */ + RESET_PASSWORD(100030, EventClassify.AUTHENTICATION, "重置密码", "重置用户 ${username} 密码"), + + // -------------------- 用户操作 -------------------- + + /** + * 添加用户 + */ + ADD_USER(110010, EventClassify.USER, "添加用户", "添加用户 ${username}"), + + /** + * 修改用户信息 + */ + UPDATE_USER(110020, EventClassify.USER, "修改用户信息", "修改用户信息 ${username}"), + + /** + * 删除用户 + */ + DELETE_USER(110030, EventClassify.USER, "删除用户", "删除用户 ${username}"), + + /** + * 修改用户状态 + */ + CHANGE_USER_STATUS(110040, EventClassify.USER, "修改用户状态", "${operator}用户 ${username}"), + + /** + * 解锁用户 + */ + UNLOCK_USER(110050, EventClassify.USER, "解锁用户", "解锁用户 ${username}"), + + // -------------------- 报警组操作 -------------------- + + /** + * 添加报警组 + */ + ADD_ALARM_GROUP(120010, EventClassify.ALARM_GROUP, "添加报警组", "添加报警组 ${name}"), + + /** + * 修改报警组 + */ + UPDATE_ALARM_GROUP(120020, EventClassify.ALARM_GROUP, "修改报警组", "修改报警组 ${before}"), + + /** + * 删除报警组 + */ + DELETE_ALARM_GROUP(120030, EventClassify.ALARM_GROUP, "删除报警组", "删除报警组 ${name}"), + + // -------------------- 机器操作 -------------------- + + /** + * 添加机器 + */ + ADD_MACHINE(200010, EventClassify.MACHINE, "添加机器", "新建机器 ${machineName}"), + + /** + * 修改机器 + */ + UPDATE_MACHINE(200020, EventClassify.MACHINE, "修改机器", "修改机器 ${machineName}"), + + /** + * 删除机器 + */ + DELETE_MACHINE(200030, EventClassify.MACHINE, "删除机器", "删除机器 ${count}台"), + + /** + * 修改机器状态 + */ + CHANGE_MACHINE_STATUS(200040, EventClassify.MACHINE, "修改机器状态", "${operator}机器 ${count}台"), + + /** + * 复制机器 + */ + COPY_MACHINE(200050, EventClassify.MACHINE, "复制机器", "复制机器 ${source} -> ${target}"), + + // -------------------- 机器环境变量操作 -------------------- + + /** + * 删除机器环境变量 + */ + DELETE_MACHINE_ENV(210010, EventClassify.MACHINE_ENV, "删除机器环境变量", "删除机器环境变量 ${count}个"), + + /** + * 同步机器环境变量 + */ + SYNC_MACHINE_ENV(210020, EventClassify.MACHINE_ENV, "同步机器环境变量", "同步 ${envCount}个环境变量 到 ${machineCount}台机器"), + + // -------------------- 密钥操作 -------------------- + + /** + * 新增密钥 + */ + ADD_MACHINE_KEY(220010, EventClassify.MACHINE_KEY, "新增密钥", "新建密钥 ${keyName}"), + + /** + * 修改密钥 + */ + UPDATE_MACHINE_KEY(220020, EventClassify.MACHINE_KEY, "修改密钥", "修改密钥 ${name}"), + + /** + * 删除密钥 + */ + DELETE_MACHINE_KEY(220030, EventClassify.MACHINE_KEY, "删除密钥", "删除密钥 ${count}个"), + + /** + * 绑定密钥 + */ + BIND_MACHINE_KEY(220040, EventClassify.MACHINE_KEY, "绑定密钥", "绑定密钥 ${name}${count}台机器"), + + // -------------------- 代理操作 -------------------- + + /** + * 新增代理 + */ + ADD_MACHINE_PROXY(230010, EventClassify.MACHINE_PROXY, "新建代理", "新建代理 ${proxyHost}"), + + /** + * 修改代理 + */ + UPDATE_MACHINE_PROXY(230020, EventClassify.MACHINE_PROXY, "修改代理", "修改代理 ${host}"), + + /** + * 删除代理 + */ + DELETE_MACHINE_PROXY(230030, EventClassify.MACHINE_PROXY, "删除代理", "删除代理 ${count}个"), + + // -------------------- 机器监控 -------------------- + + /** + * 修改机器监控配置 + */ + UPDATE_MACHINE_MONITOR_CONFIG(240010, EventClassify.MACHINE_MONITOR, "修改配置", "修改 ${name} 机器监控插件配置"), + + /** + * 安装或升级机器监控插件 + */ + INSTALL_UPGRADE_MACHINE_MONITOR(240020, EventClassify.MACHINE_MONITOR, "安装/升级插件", "${operator} ${name} 机器监控插件"), + + // -------------------- 报警配置 -------------------- + + /** + * 修改报警配置 + */ + SET_MACHINE_ALARM_CONFIG(250010, EventClassify.MACHINE_ALARM, "修改报警配置", "修改机器 ${name} ${label}报警配置"), + + /** + * 修改报警联系组 + */ + SET_MACHINE_ALARM_GROUP(250020, EventClassify.MACHINE_ALARM, "修改报警联系组", "修改机器 ${name} 报警联系组"), + + /** + * 重新发送报警通知 + */ + RENOTIFY_MACHINE_ALARM_GROUP(250030, EventClassify.MACHINE_ALARM, "重新发送报警通知", "重新发送机器 ${name} 报警通知"), + + // -------------------- 终端操作 -------------------- + + /** + * 打开机器终端 + */ + OPEN_TERMINAL(260010, EventClassify.TERMINAL, "打开机器终端", "打开机器终端 ${machineName}"), + + /** + * 强制下线终端 + */ + FORCE_OFFLINE_TERMINAL(260020, EventClassify.TERMINAL, "强制下线终端", "强制下线终端 ${username} ${name}"), + + /** + * 修改终端配置 + */ + UPDATE_TERMINAL_CONFIG(260030, EventClassify.TERMINAL, "修改终端配置", "修改机器终端配置 ${name}"), + + /** + * 删除终端日志 + */ + DELETE_TERMINAL_LOG(260040, EventClassify.TERMINAL, "删除终端日志", "删除终端操作日志 ${count}个"), + + // -------------------- sftp 操作 -------------------- + + /** + * 打开机器 SFTP + */ + OPEN_SFTP(270010, EventClassify.SFTP, "打开机器 SFTP", "打开机器 SFTP ${machineName}"), + + /** + * 创建文件夹 + */ + SFTP_MKDIR(270020, EventClassify.SFTP, "创建文件夹", "创建文件夹 ${path}"), + + /** + * 创建文件 + */ + SFTP_TOUCH(270030, EventClassify.SFTP, "创建文件", "创建文件 ${path}"), + + /** + * 截断文件 + */ + SFTP_TRUNCATE(270040, EventClassify.SFTP, "截断文件", "截断文件 ${path}"), + + /** + * 移动文件 + */ + SFTP_MOVE(270050, EventClassify.SFTP, "移动文件", "移动文件 ${source} -> ${target}"), + + /** + * 删除文件 + */ + SFTP_REMOVE(270060, EventClassify.SFTP, "删除文件", "删除文件 ${count}个"), + + /** + * 修改文件权限 + */ + SFTP_CHMOD(270070, EventClassify.SFTP, "修改文件权限", "修改文件权限 ${path} >>> ${permission}"), + + /** + * 修改文件所有者 + */ + SFTP_CHOWN(270080, EventClassify.SFTP, "修改文件所有者", "修改文件所有者 ${path} >>> ${uid}"), + + /** + * 修改文件所有组 + */ + SFTP_CHGRP(270090, EventClassify.SFTP, "修改文件所有组", "修改文件所有组 ${path} >>> ${gid}"), + + /** + * 上传文件 + */ + SFTP_UPLOAD(270100, EventClassify.SFTP, "上传文件", "上传 ${count}个文件"), + + /** + * 下载文件 + */ + SFTP_DOWNLOAD(270110, EventClassify.SFTP, "下载文件", "下载 ${count}个文件"), + + /** + * 打包文件 + */ + SFTP_PACKAGE(270120, EventClassify.SFTP, "打包文件", "打包 ${count}个文件"), + + // -------------------- 批量执行操作 -------------------- + + /** + * 批量执行 + */ + EXEC_SUBMIT(300010, EventClassify.EXEC, "批量执行", "批量执行机器命令 ${count}台"), + + /** + * 删除执行 + */ + EXEC_DELETE(300020, EventClassify.EXEC, "删除执行", "删除机器执行命令记录 ${count}个"), + + /** + * 终止执行 + */ + EXEC_TERMINATE(300030, EventClassify.EXEC, "终止执行", "终止执行机器命令"), + + // -------------------- 日志操作 -------------------- + + /** + * 添加日志文件 + */ + ADD_TAIL_FILE(310010, EventClassify.TAIL, "添加日志文件", "添加日志文件 ${aliasName}"), + + /** + * 修改日志文件 + */ + UPDATE_TAIL_FILE(310020, EventClassify.TAIL, "修改日志文件", "修改日志文件 ${name}"), + + /** + * 删除日志文件 + */ + DELETE_TAIL_FILE(310030, EventClassify.TAIL, "删除日志文件", "删除日志文件 ${count}个"), + + /** + * 上传日志文件 + */ + UPLOAD_TAIL_FILE(310040, EventClassify.TAIL, "上传日志文件", "上传日志文件 ${count}个"), + + // -------------------- 调度操作 -------------------- + + /** + * 添加调度任务 + */ + ADD_SCHEDULER_TASK(320010, EventClassify.SCHEDULER, "添加调度任务", "添加调度任务 ${taskName}"), + + /** + * 修改调度任务 + */ + UPDATE_SCHEDULER_TASK(320020, EventClassify.SCHEDULER, "修改调度任务", "修改调度任务 ${name}"), + + /** + * 更新调度任务状态 + */ + UPDATE_SCHEDULER_TASK_STATUS(320030, EventClassify.SCHEDULER, "更新任务状态", "${operator}调度任务 ${name}"), + + /** + * 删除调度任务 + */ + DELETE_SCHEDULER_TASK(320040, EventClassify.SCHEDULER, "删除调度任务", "删除调度任务 ${name}"), + + /** + * 手动触发调度任务 + */ + MANUAL_TRIGGER_SCHEDULER_TASK(320050, EventClassify.SCHEDULER, "手动触发任务", "手动触发调度任务 ${name}"), + + /** + * 停止调度任务 + */ + TERMINATE_ALL_SCHEDULER_TASK(320060, EventClassify.SCHEDULER, "停止任务", "停止调度任务 ${name}"), + + /** + * 停止调度任务机器操作 + */ + TERMINATE_SCHEDULER_TASK_MACHINE(320070, EventClassify.SCHEDULER, "停止机器操作", "停止调度任务机器操作 ${name} ${machineName}"), + + /** + * 跳过调度任务机器操作 + */ + SKIP_SCHEDULER_TASK_MACHINE(320080, EventClassify.SCHEDULER, "跳过机器操作", "跳过调度任务机器操作 ${name} ${machineName}"), + + /** + * 删除任务调度明细 + */ + DELETE_TASK_RECORD(320090, EventClassify.SCHEDULER, "删除调度明细", "删除调度任务明细 ${count}个"), + + // -------------------- 应用操作 -------------------- + + /** + * 添加应用 + */ + ADD_APP(400010, EventClassify.APP, "添加应用", "添加应用 ${appName}"), + + /** + * 修改应用 + */ + UPDATE_APP(400020, EventClassify.APP, "修改应用", "修改应用 ${name}"), + + /** + * 删除应用 + */ + DELETE_APP(400030, EventClassify.APP, "删除应用", "删除应用 ${name}"), + + /** + * 配置应用 + */ + CONFIG_APP(400040, EventClassify.APP, "配置应用", "配置应用 ${profileName} ${appName}"), + + /** + * 同步应用 + */ + SYNC_APP(400050, EventClassify.APP, "同步应用", "同步应用 ${name}${count}个环境"), + + /** + * 复制应用 + */ + COPY_APP(400060, EventClassify.APP, "复制应用", "复制应用 ${name}"), + + // -------------------- 环境操作 -------------------- + + /** + * 添加应用环境 + */ + ADD_PROFILE(410010, EventClassify.PROFILE, "添加应用环境", "添加应用环境 ${profileName}"), + + /** + * 修改应用环境 + */ + UPDATE_PROFILE(410020, EventClassify.PROFILE, "修改应用环境", "修改应用环境 ${name}"), + + /** + * 删除应用环境 + */ + DELETE_PROFILE(410030, EventClassify.PROFILE, "删除应用环境", "删除应用环境 ${name}"), + + // -------------------- 应用环境变量操作 -------------------- + + /** + * 删除应用环境变量 + */ + DELETE_APP_ENV(420010, EventClassify.APP_ENV, "删除应用环境变量", "删除应用环境变量 ${count}个"), + + /** + * 同步应用环境变量 + */ + SYNC_APP_ENV(420020, EventClassify.APP_ENV, "同步应用环境变量", "同步 ${envCount}个环境变量 到 ${profileCount}个环境"), + + // -------------------- 版本仓库操作 -------------------- + + /** + * 添加版本仓库 + */ + ADD_REPOSITORY(430010, EventClassify.REPOSITORY, "添加版本仓库", "添加版本仓库 ${repoName}"), + + /** + * 初始化版本仓库 + */ + INIT_REPOSITORY(430020, EventClassify.REPOSITORY, "初始化版本仓库", "初始化版本仓库 ${name}"), + + /** + * 重新初始化版本仓库 + */ + RE_INIT_REPOSITORY(430030, EventClassify.REPOSITORY, "重新初始化版本仓库", "重新初始化版本仓库 ${name}"), + + /** + * 更新版本仓库 + */ + UPDATE_REPOSITORY(430040, EventClassify.REPOSITORY, "更新版本仓库", "更新版本仓库 ${name}"), + + /** + * 删除版本仓库 + */ + DELETE_REPOSITORY(430050, EventClassify.REPOSITORY, "删除版本仓库", "删除版本仓库 ${name}"), + + /** + * 清空版本仓库 + */ + CLEAN_REPOSITORY(430060, EventClassify.REPOSITORY, "清空版本仓库", "清空版本仓库 ${name}"), + + // -------------------- 构建操作 -------------------- + + /** + * 提交应用构建 + */ + SUBMIT_BUILD(440010, EventClassify.BUILD, "提交应用构建", "提交应用构建 #${buildSeq} ${profileName} ${appName}"), + + /** + * 停止应用构建 + */ + BUILD_TERMINATE(440020, EventClassify.BUILD, "停止应用构建", "停止应用构建 #${buildSeq} ${profileName} ${appName}"), + + /** + * 删除应用构建 + */ + DELETE_BUILD(440030, EventClassify.BUILD, "删除应用构建", "删除应用构建记录 ${count}个"), + + /** + * 重新构建应用 + */ + SUBMIT_REBUILD(440040, EventClassify.BUILD, "重新构建应用", "重新构建应用 #${buildSeq} ${profileName} ${appName}"), + + // -------------------- 发布操作 -------------------- + + /** + * 提交应用发布 + */ + SUBMIT_RELEASE(450010, EventClassify.RELEASE, "提交应用发布", "提交应用发布 ${releaseTitle}"), + + /** + * 应用发布审核 + */ + AUDIT_RELEASE(450020, EventClassify.RELEASE, "应用发布审核", "应用发布审核${operator} ${title}"), + + /** + * 执行应用发布 + */ + RUNNABLE_RELEASE(450030, EventClassify.RELEASE, "执行应用发布", "执行应用发布 ${title}"), + + /** + * 应用回滚发布 + */ + ROLLBACK_RELEASE(450040, EventClassify.RELEASE, "应用回滚发布", "应用回滚发布 ${title}"), + + /** + * 停止应用发布 + */ + TERMINATE_RELEASE(450050, EventClassify.RELEASE, "停止应用发布", "停止应用发布 ${title}"), + + /** + * 删除应用发布 + */ + DELETE_RELEASE(450060, EventClassify.RELEASE, "删除应用发布", "删除应用发布记录 ${count}个"), + + /** + * 复制应用发布 + */ + COPY_RELEASE(450070, EventClassify.RELEASE, "复制应用发布", "复制应用发布 ${releaseTitle}"), + + /** + * 取消应用定时发布 + */ + CANCEL_TIMED_RELEASE(450080, EventClassify.RELEASE, "取消定时发布", "取消应用定时发布 ${title}"), + + /** + * 设置应用定时发布 + */ + SET_TIMED_RELEASE(450090, EventClassify.RELEASE, "设置定时发布", "设置应用定时发布 ${title} -> ${time}"), + + /** + * 停止机器发布操作 + */ + TERMINATE_MACHINE_RELEASE(450100, EventClassify.RELEASE, "停止机器操作", "停止机器发布操作 ${title} ${machineName}"), + + /** + * 跳过机器发布操作 + */ + SKIP_MACHINE_RELEASE(450110, EventClassify.RELEASE, "跳过机器操作", "跳过机器发布操作 ${title} ${machineName}"), + + // -------------------- 应用流水线 -------------------- + + /** + * 添加应用流水线 + */ + ADD_PIPELINE(460010, EventClassify.PIPELINE, "添加流水线", "添加应用流水线 ${pipelineName}"), + + /** + * 修改应用流水线 + */ + UPDATE_PIPELINE(460020, EventClassify.PIPELINE, "修改流水线", "修改应用流水线 ${pipelineName}"), + + /** + * 删除应用流水线 + */ + DELETE_PIPELINE(460030, EventClassify.PIPELINE, "删除流水线", "删除应用流水线 ${count}个"), + + /** + * 提交应用流水线任务 + */ + SUBMIT_PIPELINE_TASK(460040, EventClassify.PIPELINE, "提交执行任务", "提交应用流水线任务 ${pipelineName} ${execTitle}"), + + /** + * 审核应用流水线任务 + */ + AUDIT_PIPELINE_TASK(460050, EventClassify.PIPELINE, "审核任务", "审核应用流水线任务${operator} ${name} ${title}"), + + /** + * 复制应用流水线任务 + */ + COPY_PIPELINE_TASK(460060, EventClassify.PIPELINE, "复制任务", "复制应用流水线任务 ${pipelineName} ${execTitle}"), + + /** + * 执行应用流水线任务 + */ + EXEC_PIPELINE_TASK(460070, EventClassify.PIPELINE, "执行任务", "执行应用流水线任务 ${name} ${title}"), + + /** + * 删除应用流水线任务 + */ + DELETE_PIPELINE_TASK(460080, EventClassify.PIPELINE, "删除任务", "删除应用流水线任务 ${count}个"), + + /** + * 设置定时执行应用流水线任务 + */ + SET_PIPELINE_TIMED_TASK(460090, EventClassify.PIPELINE, "设置定时执行", "设置定时执行应用流水线任务 ${name} ${title} -> ${time}"), + + /** + * 取消定时执行应用流水线任务 + */ + CANCEL_PIPELINE_TIMED_TASK(460100, EventClassify.PIPELINE, "取消定时执行", "取消定时执行应用流水线任务 ${name} ${title}"), + + /** + * 停止执行应用流水线任务 + */ + TERMINATE_PIPELINE_TASK(460110, EventClassify.PIPELINE, "停止执行任务", "停止执行应用流水线任务 ${name} ${title}"), + + /** + * 停止执行应用流水线任务操作 + */ + TERMINATE_PIPELINE_TASK_DETAIL(460120, EventClassify.PIPELINE, "停止执行操作", "停止执行应用流水线任务部分操作 ${name} ${title} (${stage} ${appName})"), + + /** + * 跳过执行应用流水线任务操作 + */ + SKIP_PIPELINE_TASK_DETAIL(460130, EventClassify.PIPELINE, "跳过执行操作", "跳过执行应用流水线任务部分操作 ${name} ${title} (${stage} ${appName})"), + + // -------------------- 模板操作 -------------------- + + /** + * 添加模板 + */ + ADD_TEMPLATE(500010, EventClassify.TEMPLATE, "添加模板", "添加模板 ${templateName}"), + + /** + * 修改模板 + */ + UPDATE_TEMPLATE(500020, EventClassify.TEMPLATE, "修改模板", "修改模板 ${name}"), + + /** + * 删除模板 + */ + DELETE_TEMPLATE(500030, EventClassify.TEMPLATE, "删除模板", "删除模板 ${count}个"), + + // -------------------- webhook 操作 -------------------- + + /** + * 添加 webhook 配置 + */ + ADD_WEBHOOK(510010, EventClassify.WEBHOOK, "添加配置", "添加 webhook ${webhookName}"), + + /** + * 修改 webhook 配置 + */ + UPDATE_WEBHOOK(510020, EventClassify.WEBHOOK, "修改配置", "修改 webhook ${name}"), + + /** + * 删除 webhook 配置 + */ + DELETE_WEBHOOK(510030, EventClassify.WEBHOOK, "删除配置", "删除 webhook ${name}个"), + + // -------------------- 系统操作 -------------------- + + /** + * 配置 ip 过滤器 + */ + CONFIG_IP_LIST(600010, EventClassify.SYSTEM, "配置IP过滤器", "配置 IP 过滤器"), + + /** + * 重新进行系统统计分析 + */ + RE_ANALYSIS_SYSTEM(600020, EventClassify.SYSTEM, "系统统计分析", "重新进行系统统计分析"), + + /** + * 清理系统文件 + */ + CLEAN_SYSTEM_FILE(600030, EventClassify.SYSTEM, "清理系统文件", "清理系统 ${label}"), + + /** + * 修改系统配置 + */ + UPDATE_SYSTEM_OPTION(600040, EventClassify.SYSTEM, "修改系统配置", "修改系统配置项 ${label} 原始值:${before} 新值:${after}"), + + // -------------------- 系统环境变量操作 -------------------- + + /** + * 添加系统环境变量 + */ + ADD_SYSTEM_ENV(610010, EventClassify.SYSTEM_ENV, "添加系统环境变量", "添加系统环境变量 ${attrKey}"), + + /** + * 修改系统环境变量 + */ + UPDATE_SYSTEM_ENV(610020, EventClassify.SYSTEM_ENV, "修改系统环境变量", "修改系统环境变量 ${attrKey}"), + + /** + * 删除系统环境变量 + */ + DELETE_SYSTEM_ENV(610030, EventClassify.SYSTEM_ENV, "删除系统环境变量", "删除系统环境变量 ${count}个"), + + /** + * 保存系统环境变量 + */ + SAVE_SYSTEM_ENV(610040, EventClassify.SYSTEM_ENV, "保存系统环境变量", "保存系统环境变量 ${count}个"), + + // -------------------- 数据清理 -------------------- + + /** + * 清理数据 + */ + DATA_CLEAR(620010, EventClassify.DATA_CLEAR, "清理数据", "清理数据 ${label} ${count}条"), + + // -------------------- 数据导入 -------------------- + + /** + * 导入数据 + */ + DATA_IMPORT(630010, EventClassify.DATA_IMPORT, "导入数据", "批量导入 ${label}"), + + // -------------------- 数据导出 -------------------- + + /** + * 导出数据 + */ + DATA_EXPORT(640010, EventClassify.DATA_EXPORT, "导出数据", "导出 ${label} ${count}条"), + + ; + + /** + * 类型 + */ + private final Integer type; + + /** + * 分类 + */ + private final EventClassify classify; + + /** + * label + */ + private final String label; + + /** + * 模板 + */ + private final String template; + + public static EventType of(Integer type) { + if (type == null) { + return null; + } + for (EventType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/history/HistoryOperator.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/history/HistoryOperator.java new file mode 100644 index 0000000..97e5da4 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/history/HistoryOperator.java @@ -0,0 +1,41 @@ +package cn.orionsec.ops.constant.history; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum HistoryOperator { + + /** + * 添加 + */ + ADD(1), + + /** + * 更新 + */ + UPDATE(2), + + /** + * 删除 + */ + DELETE(3), + + ; + + private final Integer type; + + public static HistoryOperator of(Integer type) { + if (type == null) { + return null; + } + for (HistoryOperator value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/history/HistoryValueType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/history/HistoryValueType.java new file mode 100644 index 0000000..8a3d880 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/history/HistoryValueType.java @@ -0,0 +1,41 @@ +package cn.orionsec.ops.constant.history; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum HistoryValueType { + + /** + * 机器环境变量 + */ + MACHINE_ENV(10), + + /** + * 应用环境变量 + */ + APP_ENV(20), + + /** + * 系统环境变量 + */ + SYSTEM_ENV(30), + + ; + + private final Integer type; + + public static HistoryValueType of(Integer type) { + if (type == null) { + return null; + } + for (HistoryValueType value : values()) { + if (type.equals(value.type)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/machine/MachineAlarmType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/machine/MachineAlarmType.java new file mode 100644 index 0000000..ef16e57 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/machine/MachineAlarmType.java @@ -0,0 +1,38 @@ +package cn.orionsec.ops.constant.machine; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum MachineAlarmType { + + /** + * cpu 使用率 + */ + CPU_USAGE(10, "CPU使用率"), + + /** + * 内存使用率 + */ + MEMORY_USAGE(20, "内存使用率"), + + ; + + private final Integer type; + + private final String label; + + public static MachineAlarmType of(Integer type) { + if (type == null) { + return null; + } + for (MachineAlarmType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/machine/MachineAuthType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/machine/MachineAuthType.java new file mode 100644 index 0000000..e670555 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/machine/MachineAuthType.java @@ -0,0 +1,51 @@ +package cn.orionsec.ops.constant.machine; + +import cn.orionsec.ops.constant.CnConst; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum MachineAuthType { + + /** + * 1 密码认证 + */ + PASSWORD(1, CnConst.PASSWORD), + + /** + * 2 独立密钥 + */ + SECRET_KEY(2, CnConst.SECRET_KEY), + + ; + + private final Integer type; + + private final String label; + + public static MachineAuthType of(Integer type) { + if (type == null) { + return null; + } + for (MachineAuthType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + + public static MachineAuthType of(String label) { + if (label == null) { + return null; + } + for (MachineAuthType value : values()) { + if (value.label.equals(label)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/machine/MachineConst.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/machine/MachineConst.java new file mode 100644 index 0000000..5e8424b --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/machine/MachineConst.java @@ -0,0 +1,20 @@ +package cn.orionsec.ops.constant.machine; + +import cn.orionsec.ops.constant.Const; + +public class MachineConst { + + private MachineConst() { + } + + /** + * 远程连接尝试次数 + */ + public static final int CONNECT_RETRY_TIMES = 1; + + /** + * 远程连接超时时间 + */ + public static final int CONNECT_TIMEOUT = Const.MS_S_30; + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/machine/MachineEnvAttr.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/machine/MachineEnvAttr.java new file mode 100644 index 0000000..c8b845f --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/machine/MachineEnvAttr.java @@ -0,0 +1,89 @@ +package cn.orionsec.ops.constant.machine; + +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.command.CommandConst; +import lombok.Getter; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +@Getter +public enum MachineEnvAttr { + + /** + * sftp 文件名称编码格式 + * + * @see Const#UTF_8 + */ + SFTP_CHARSET("SFTP 文件名称编码格式"), + + /** + * 文件追踪偏移量 + * + * @see Const#TAIL_OFFSET_LINE + */ + TAIL_OFFSET("文件追踪偏移量(行)"), + + /** + * 文件追踪编码格式 + * + * @see Const#UTF_8 + */ + TAIL_CHARSET("文件追踪编码格式"), + + /** + * 文件追踪默认命令 + * + * @see CommandConst#TAIL_FILE_DEFAULT + */ + TAIL_DEFAULT_COMMAND("文件追踪默认命令"), + + /** + * 连接超时时间 (ms) + * + * @see MachineConst#CONNECT_TIMEOUT + */ + CONNECT_TIMEOUT("连接超时时间 (ms)"), + + /** + * 连接失败重试次数 + * + * @see MachineConst#CONNECT_RETRY_TIMES + */ + CONNECT_RETRY_TIMES("连接失败重试次数"), + + ; + + /** + * key + */ + private final String key; + + /** + * 描述 + */ + private final String description; + + MachineEnvAttr(String description) { + this.description = description; + this.key = this.name().toLowerCase(); + } + + public static List getKeys() { + return Arrays.stream(values()) + .map(MachineEnvAttr::getKey) + .collect(Collectors.toList()); + } + + public static MachineEnvAttr of(String key) { + if (key == null) { + return null; + } + return Arrays.stream(values()) + .filter(a -> a.key.equals(key)) + .findFirst() + .orElse(null); + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/machine/ProxyType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/machine/ProxyType.java new file mode 100644 index 0000000..d96df1e --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/machine/ProxyType.java @@ -0,0 +1,56 @@ +package cn.orionsec.ops.constant.machine; + +import cn.orionsec.ops.constant.Const; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum ProxyType { + + /** + * HTTP 代理 + */ + HTTP(1, Const.PROTOCOL_HTTP), + + /** + * SOCKS4 代理 + */ + SOCKS4(2, Const.SOCKS4), + + /** + * SOCKS5 代理 + */ + SOCKS5(3, Const.SOCKS5), + + ; + + private final Integer type; + + private final String label; + + public static ProxyType of(Integer type) { + if (type == null) { + return null; + } + for (ProxyType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + + public static ProxyType of(String label) { + if (label == null) { + return null; + } + for (ProxyType value : values()) { + if (value.label.equals(label)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/message/MessageClassify.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/message/MessageClassify.java new file mode 100644 index 0000000..8d18d9e --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/message/MessageClassify.java @@ -0,0 +1,43 @@ +package cn.orionsec.ops.constant.message; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum MessageClassify { + + /** + * 系统消息 + */ + SYSTEM(10, "系统消息"), + + /** + * 数据导入 + */ + IMPORT(20, "数据导入"), + + /** + * 系统报警 + */ + ALARM(30, "系统报警"), + + ; + + private final Integer classify; + + private final String label; + + public static MessageClassify of(Integer classify) { + if (classify == null) { + return null; + } + for (MessageClassify value : values()) { + if (value.classify.equals(classify)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/message/MessageType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/message/MessageType.java new file mode 100644 index 0000000..ce27bb0 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/message/MessageType.java @@ -0,0 +1,133 @@ +package cn.orionsec.ops.constant.message; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum MessageType { + + // -------------------- 系统消息 -------------------- + + /** + * 命令执行完成 + */ + EXEC_SUCCESS(1010, "命令执行完成", MessageClassify.SYSTEM, "机器 ${name} 命令执行完成"), + + /** + * 命令执行失败 + */ + EXEC_FAILURE(1020, "命令执行失败", MessageClassify.SYSTEM, "机器 ${name} 命令执行失败"), + + /** + * 版本仓库初始化成功 + */ + REPOSITORY_INIT_SUCCESS(1030, "版本仓库初始化成功", MessageClassify.SYSTEM, "仓库 ${name} 初始化成功"), + + /** + * 版本仓库初始化失败 + */ + REPOSITORY_INIT_FAILURE(1040, "版本仓库初始化失败", MessageClassify.SYSTEM, "仓库 ${name} 初始化失败"), + + /** + * 构建执行成功 + */ + BUILD_SUCCESS(1050, "构建执行成功", MessageClassify.SYSTEM, "${appName} #${seq} 构建成功"), + + /** + * 构建执行失败 + */ + BUILD_FAILURE(1060, "构建执行失败", MessageClassify.SYSTEM, "${appName} #${seq} 构建失败"), + + /** + * 发布审批通过 + */ + RELEASE_AUDIT_RESOLVE(1070, "发布审批通过", MessageClassify.SYSTEM, "发布任务 ${title} 审核已通过"), + + /** + * 发布审批驳回 + */ + RELEASE_AUDIT_REJECT(1080, "发布审批驳回", MessageClassify.SYSTEM, "发布任务 ${title} 审核已被驳回"), + + /** + * 发布执行成功 + */ + RELEASE_SUCCESS(1090, "发布执行成功", MessageClassify.SYSTEM, "发布任务 ${title} 发布成功"), + + /** + * 发布执行失败 + */ + RELEASE_FAILURE(1100, "发布执行失败", MessageClassify.SYSTEM, "发布任务 ${title} 发布失败"), + + /** + * 应用流水线审批通过 + */ + PIPELINE_AUDIT_RESOLVE(1110, "应用流水线审批通过", MessageClassify.SYSTEM, "应用流水线 ${name} ${title} 审核已通过"), + + /** + * 应用流水线审批驳回 + */ + PIPELINE_AUDIT_REJECT(1120, "应用流水线审批驳回", MessageClassify.SYSTEM, "应用流水线 ${name} ${title} 审核已被驳回"), + + /** + * 应用流水线执行成功 + */ + PIPELINE_EXEC_SUCCESS(1130, "应用流水线执行成功", MessageClassify.SYSTEM, "应用流水线 ${name} ${title} 执行成功"), + + /** + * 应用流水线执行失败 + */ + PIPELINE_EXEC_FAILURE(1140, "应用流水线执行失败", MessageClassify.SYSTEM, "应用流水线 ${name} ${title} 执行失败"), + + /** + * 机器监控插件安装成功 + */ + MACHINE_AGENT_INSTALL_SUCCESS(1150, "机器监控插件安装成功", MessageClassify.SYSTEM, "机器 ${name} 监控插件安装成功"), + + /** + * 机器监控插件安装失败 + */ + MACHINE_AGENT_INSTALL_FAILURE(1160, "机器监控插件安装失败", MessageClassify.SYSTEM, "机器 ${name} 监控插件安装失败, 请手动安装或检查插件配置"), + + // -------------------- 数据导入 -------------------- + + /** + * 数据导入成功 + */ + DATA_IMPORT_SUCCESS(2010, "数据导入成功", MessageClassify.IMPORT, "您在 ${time} 进行的${label}导入操作执行完成"), + + /** + * 数据导入失败 + */ + DATA_IMPORT_FAILURE(2020, "数据导入失败", MessageClassify.IMPORT, "您在 ${time} 进行的${label}导入操作执行失败"), + + // -------------------- 系统报警 -------------------- + + /** + * 机器发生报警 + */ + MACHINE_ALARM(3010, "机器发生报警", MessageClassify.ALARM, "机器 ${name}(${host}) ${time} ${type}达到${value}%, 请及时排查!"), + + ; + + private final Integer type; + + private final String label; + + private final MessageClassify classify; + + private final String template; + + public static MessageType of(Integer type) { + if (type == null) { + return null; + } + for (MessageType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/message/ReadStatus.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/message/ReadStatus.java new file mode 100644 index 0000000..1749a41 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/message/ReadStatus.java @@ -0,0 +1,39 @@ +package cn.orionsec.ops.constant.message; + +import cn.orionsec.ops.constant.CnConst; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum ReadStatus { + + /** + * 未读 + */ + UNREAD(1, CnConst.UNREAD), + + /** + * 已读 + */ + READ(2, CnConst.READ), + + ; + + private final Integer status; + + private final String label; + + public static ReadStatus of(Integer status) { + if (status == null) { + return null; + } + for (ReadStatus value : values()) { + if (value.status.equals(status)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/monitor/MonitorConst.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/monitor/MonitorConst.java new file mode 100644 index 0000000..a385221 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/monitor/MonitorConst.java @@ -0,0 +1,86 @@ +package cn.orionsec.ops.constant.monitor; + +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.PropertiesConst; +import cn.orionsec.ops.utils.ResourceLoader; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class MonitorConst { + + private MonitorConst() { + } + + /** + * 最新版本 + */ + public static String LATEST_VERSION; + + /** + * 默认 url + */ + public static String DEFAULT_URL_FORMAT; + + /** + * 默认请求头 + */ + public static String DEFAULT_ACCESS_HEADER; + + /** + * 默认 accessToken + */ + public static String DEFAULT_ACCESS_TOKEN; + + /** + * agent 文件名称前缀 + */ + public static final String AGENT_FILE_NAME_PREFIX = "machine-monitor-agent"; + + /** + * 启动脚本资源路径 + */ + public static final String START_SCRIPT_PATH = "/templates/script/start-monitor-agent.sh"; + + /** + * 启动脚本资源内容 + */ + public static final String START_SCRIPT_VALUE = ResourceLoader.get(START_SCRIPT_PATH, MonitorConst.class); + + /** + * 启动脚本文件名称 + */ + public static final String START_SCRIPT_FILE_NAME = "start-machine-monitor-agent.sh"; + + @Value("${machine.monitor.latest.version}") + private void setLatestVersion(String latestVersion) { + MonitorConst.LATEST_VERSION = latestVersion; + } + + @Value("${machine.monitor.default.url}") + private void setDefaultUrlFormat(String defaultUrlFormat) { + MonitorConst.DEFAULT_URL_FORMAT = defaultUrlFormat; + } + + @Value("${machine.monitor.default.access.header}") + private void setDefaultAccessHeader(String defaultAccessHeader) { + MonitorConst.DEFAULT_ACCESS_HEADER = defaultAccessHeader; + } + + @Value("${machine.monitor.default.access.token}") + private void setDefaultAccessToken(String defaultAccessToken) { + MonitorConst.DEFAULT_ACCESS_TOKEN = defaultAccessToken; + } + + /** + * agent 文件名称 + * + * @return 获取 agent 文件名称 + */ + public static String getAgentFileName() { + return AGENT_FILE_NAME_PREFIX + + "_" + PropertiesConst.MACHINE_MONITOR_LATEST_VERSION + + "." + Const.SUFFIX_JAR; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/monitor/MonitorStatus.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/monitor/MonitorStatus.java new file mode 100644 index 0000000..1d0434a --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/monitor/MonitorStatus.java @@ -0,0 +1,41 @@ +package cn.orionsec.ops.constant.monitor; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum MonitorStatus { + + /** + * 未启动 + */ + NOT_START(1), + + /** + * 启动中 + */ + STARTING(2), + + /** + * 运行中 + */ + RUNNING(3), + + ; + + private final Integer status; + + public static MonitorStatus of(Integer status) { + if (status == null) { + return null; + } + for (MonitorStatus value : values()) { + if (value.status.equals(status)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/scheduler/SchedulerTaskMachineStatus.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/scheduler/SchedulerTaskMachineStatus.java new file mode 100644 index 0000000..f23a48b --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/scheduler/SchedulerTaskMachineStatus.java @@ -0,0 +1,56 @@ +package cn.orionsec.ops.constant.scheduler; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum SchedulerTaskMachineStatus { + + /** + * 待调度 + */ + WAIT(10), + + /** + * 执行中 + */ + RUNNABLE(20), + + /** + * 成功 + */ + SUCCESS(30), + + /** + * 失败 + */ + FAILURE(40), + + /** + * 已跳过 + */ + SKIPPED(50), + + /** + * 已停止 + */ + TERMINATED(60), + + ; + + private final Integer status; + + public static SchedulerTaskMachineStatus of(Integer status) { + if (status == null) { + return null; + } + for (SchedulerTaskMachineStatus value : values()) { + if (value.status.equals(status)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/scheduler/SchedulerTaskStatus.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/scheduler/SchedulerTaskStatus.java new file mode 100644 index 0000000..4f60afe --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/scheduler/SchedulerTaskStatus.java @@ -0,0 +1,51 @@ +package cn.orionsec.ops.constant.scheduler; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum SchedulerTaskStatus { + + /** + * 待调度 + */ + WAIT(10), + + /** + * 执行中 + */ + RUNNABLE(20), + + /** + * 成功 + */ + SUCCESS(30), + + /** + * 失败 + */ + FAILURE(40), + + /** + * 已停止 + */ + TERMINATED(50), + + ; + + private final Integer status; + + public static SchedulerTaskStatus of(Integer status) { + if (status == null) { + return null; + } + for (SchedulerTaskStatus value : values()) { + if (value.status.equals(status)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/sftp/SftpNotifyType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/sftp/SftpNotifyType.java new file mode 100644 index 0000000..b6cb9e3 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/sftp/SftpNotifyType.java @@ -0,0 +1,41 @@ +package cn.orionsec.ops.constant.sftp; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum SftpNotifyType { + + /** + * 添加任务 + */ + ADD(10), + + /** + * 进度以及速率 + */ + PROGRESS(20), + + /** + * 修改状态 + */ + CHANGE_STATUS(30), + + ; + + private final Integer type; + + public static SftpNotifyType of(Integer type) { + if (type == null) { + return null; + } + for (SftpNotifyType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/sftp/SftpPackageType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/sftp/SftpPackageType.java new file mode 100644 index 0000000..e741c43 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/sftp/SftpPackageType.java @@ -0,0 +1,41 @@ +package cn.orionsec.ops.constant.sftp; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum SftpPackageType { + + /** + * 上传文件 + */ + UPLOAD(1), + + /** + * 下载文件 + */ + DOWNLOAD(2), + + /** + * 全部 + */ + ALL(3), + + ; + + private final Integer type; + + public static SftpPackageType of(Integer type) { + if (type == null) { + return null; + } + for (SftpPackageType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/sftp/SftpTransferStatus.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/sftp/SftpTransferStatus.java new file mode 100644 index 0000000..9ce9cc0 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/sftp/SftpTransferStatus.java @@ -0,0 +1,56 @@ +package cn.orionsec.ops.constant.sftp; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum SftpTransferStatus { + + /** + * 10 等待中 + */ + WAIT(10), + + /** + * 20 进行中 + */ + RUNNABLE(20), + + /** + * 30 已暂停 + */ + PAUSE(30), + + /** + * 40 已完成 + */ + FINISH(40), + + /** + * 50 已取消 + */ + CANCEL(50), + + /** + * 60 传输异常 + */ + ERROR(60), + + ; + + private final Integer status; + + public static SftpTransferStatus of(Integer status) { + if (status == null) { + return null; + } + for (SftpTransferStatus value : values()) { + if (value.status.equals(status)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/sftp/SftpTransferType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/sftp/SftpTransferType.java new file mode 100644 index 0000000..c8c4852 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/sftp/SftpTransferType.java @@ -0,0 +1,48 @@ +package cn.orionsec.ops.constant.sftp; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum SftpTransferType { + + /** + * 10 上传 + */ + UPLOAD(10, "上传"), + + /** + * 20 下载 + */ + DOWNLOAD(20, "下载"), + + /** + * 30 传输 + */ + TRANSFER(30, "传输"), + + /** + * 40 打包 + */ + PACKAGE(40, "打包"), + + ; + + private final Integer type; + + private final String label; + + public static SftpTransferType of(Integer type) { + if (type == null) { + return null; + } + for (SftpTransferType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/system/SystemCleanType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/system/SystemCleanType.java new file mode 100644 index 0000000..d655b9e --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/system/SystemCleanType.java @@ -0,0 +1,58 @@ +package cn.orionsec.ops.constant.system; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +@AllArgsConstructor +@Getter +public enum SystemCleanType { + + /** + * 临时文件 + */ + TEMP_FILE(10, "临时文件"), + + /** + * 日志文件 + */ + LOG_FILE(20, "日志文件"), + + /** + * 交换文件 + */ + SWAP_FILE(30, "交换文件"), + + /** + * 历史产物文件 + */ + DIST_FILE(40, "旧版本构建产物"), + + /** + * 版本仓库文件 + */ + REPO_FILE(50, "旧版本应用仓库"), + + /** + * 录屏文件 + */ + SCREEN_FILE(60, "录屏文件"), + + ; + + private final Integer type; + + private final String label; + + public static SystemCleanType of(Integer type) { + if (type == null) { + return null; + } + return Arrays.stream(values()) + .filter(a -> a.type.equals(type)) + .findFirst() + .orElse(null); + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/system/SystemConfigKey.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/system/SystemConfigKey.java new file mode 100644 index 0000000..66aa097 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/system/SystemConfigKey.java @@ -0,0 +1,256 @@ +package cn.orionsec.ops.constant.system; + +import cn.orionsec.kit.lang.exception.argument.InvalidArgumentException; +import cn.orionsec.kit.lang.function.Conversion; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.Valid; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.common.EnableType; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +@AllArgsConstructor +@Getter +public enum SystemConfigKey { + + /** + * 文件清理阈值 (天) + */ + FILE_CLEAN_THRESHOLD(10, SystemEnvAttr.FILE_CLEAN_THRESHOLD) { + @Override + protected boolean valid(String s) { + return Strings.isInteger(s) && Integer.parseInt(s) >= 0 && Integer.parseInt(s) <= 1000; + } + + @Override + protected String validTips() { + return FILE_CLEAN_THRESHOLD_TIPS; + } + }, + + /** + * 是否启用自动清理 + */ + ENABLE_AUTO_CLEAN_FILE(20, SystemEnvAttr.ENABLE_AUTO_CLEAN_FILE) { + @Override + protected String conversionValue(String s) { + return ENABLED_TYPE.apply(s); + } + }, + + /** + * 是否允许多端登录 + */ + ALLOW_MULTIPLE_LOGIN(30, SystemEnvAttr.ALLOW_MULTIPLE_LOGIN) { + @Override + protected String conversionValue(String s) { + return ENABLED_TYPE.apply(s); + } + }, + + /** + * 是否启用登录失败锁定 + */ + LOGIN_FAILURE_LOCK(40, SystemEnvAttr.LOGIN_FAILURE_LOCK) { + @Override + protected String conversionValue(String s) { + return ENABLED_TYPE.apply(s); + } + }, + + /** + * 是否启用登录IP绑定 + */ + LOGIN_IP_BIND(50, SystemEnvAttr.LOGIN_IP_BIND) { + @Override + protected String conversionValue(String s) { + return ENABLED_TYPE.apply(s); + } + }, + + /** + * 是否启用凭证自动续签 + */ + LOGIN_TOKEN_AUTO_RENEW(60, SystemEnvAttr.LOGIN_TOKEN_AUTO_RENEW) { + @Override + protected String conversionValue(String s) { + return ENABLED_TYPE.apply(s); + } + }, + + /** + * 登录凭证有效期 (时) + */ + LOGIN_TOKEN_EXPIRE(70, SystemEnvAttr.LOGIN_TOKEN_EXPIRE) { + @Override + protected boolean valid(String s) { + return Strings.isInteger(s) && Integer.parseInt(s) > 0 && Integer.parseInt(s) <= 720; + } + + @Override + protected String validTips() { + return LOGIN_TOKEN_EXPIRE_TIPS; + } + }, + + /** + * 登录失败锁定阈值 (次) + */ + LOGIN_FAILURE_LOCK_THRESHOLD(80, SystemEnvAttr.LOGIN_FAILURE_LOCK_THRESHOLD) { + @Override + protected boolean valid(String s) { + return Strings.isInteger(s) && Integer.parseInt(s) > 0 && Integer.parseInt(s) <= 100; + } + + @Override + protected String validTips() { + return LOGIN_FAILURE_LOCK_THRESHOLD_TIPS; + } + }, + + /** + * 登录自动续签阈值 (时) + */ + LOGIN_TOKEN_AUTO_RENEW_THRESHOLD(90, SystemEnvAttr.LOGIN_TOKEN_AUTO_RENEW_THRESHOLD) { + @Override + protected boolean valid(String s) { + return Strings.isInteger(s) && Integer.parseInt(s) > 0 && Integer.parseInt(s) <= 720; + } + + @Override + protected String validTips() { + return LOGIN_TOKEN_AUTO_RENEW_THRESHOLD_TIPS; + } + }, + + /** + * 自动恢复启用的调度任务 + */ + RESUME_ENABLE_SCHEDULER_TASK(100, SystemEnvAttr.RESUME_ENABLE_SCHEDULER_TASK) { + @Override + protected String conversionValue(String s) { + return ENABLED_TYPE.apply(s); + } + }, + + /** + * SFTP 上传文件最大阈值 (MB) + */ + SFTP_UPLOAD_THRESHOLD(110, SystemEnvAttr.SFTP_UPLOAD_THRESHOLD) { + @Override + protected boolean valid(String s) { + return Strings.isInteger(s) && Integer.parseInt(s) >= 10 && Integer.parseInt(s) <= 2048; + } + + @Override + protected String validTips() { + return SFTP_UPLOAD_THRESHOLD_TIPS; + } + }, + + /** + * 统计缓存有效时间 (分) + */ + STATISTICS_CACHE_EXPIRE(120, SystemEnvAttr.STATISTICS_CACHE_EXPIRE) { + @Override + protected boolean valid(String s) { + return Strings.isInteger(s) && Integer.parseInt(s) > 0 && Integer.parseInt(s) <= 10080; + } + + @Override + protected String validTips() { + return STATISTICS_CACHE_EXPIRE_TIPS; + } + }, + + /** + * 终端后台主动推送心跳 + */ + TERMINAL_ACTIVE_PUSH_HEARTBEAT(130, SystemEnvAttr.TERMINAL_ACTIVE_PUSH_HEARTBEAT) { + @Override + protected String conversionValue(String s) { + return ENABLED_TYPE.apply(s); + } + }, + + ; + + private static final String FILE_CLEAN_THRESHOLD_TIPS = "文件清理阈值需要在 10 ~ 2048 之间"; + + private static final String LOGIN_TOKEN_EXPIRE_TIPS = "登录凭证有效期需要在 1 ~ 720 之间"; + + private static final String LOGIN_FAILURE_LOCK_THRESHOLD_TIPS = "登录失败锁定阈值需要在 1 ~ 100 之间"; + + private static final String LOGIN_TOKEN_AUTO_RENEW_THRESHOLD_TIPS = "登录自动续签阈值需要在 1 ~ 720 之间"; + + private static final String SFTP_UPLOAD_THRESHOLD_TIPS = "上传文件阈值需要在 10 ~ 2048 之间"; + + private static final String STATISTICS_CACHE_EXPIRE_TIPS = "统计缓存有效时间需要在 1 ~ 10080 之间"; + + private static final Conversion ENABLED_TYPE = s -> EnableType.of(Boolean.valueOf(s)).getLabel(); + + private final Integer type; + + private final SystemEnvAttr env; + + /** + * 验证 + * + * @param s s + * @return value + */ + protected boolean valid(String s) { + return true; + } + + /** + * 验证失败提示 + * + * @return 验证失败提示 + */ + protected String validTips() { + return MessageConst.INVALID_CONFIG; + } + + /** + * 转化值 + * + * @param s s + * @return value + */ + protected String conversionValue(String s) { + return s; + } + + /** + * 获取值 + * + * @return value + */ + public String getValue(String s) { + // 验证 + try { + Valid.isTrue(this.valid(s), this.validTips()); + } catch (InvalidArgumentException e) { + throw e; + } catch (Exception e) { + throw Exceptions.argument(this.validTips()); + } + // 转化 + return this.conversionValue(s); + } + + public static SystemConfigKey of(Integer type) { + if (type == null) { + return null; + } + return Arrays.stream(values()) + .filter(a -> a.type.equals(type)) + .findFirst() + .orElse(null); + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/system/SystemEnvAttr.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/system/SystemEnvAttr.java new file mode 100644 index 0000000..bc96ddf --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/system/SystemEnvAttr.java @@ -0,0 +1,197 @@ +package cn.orionsec.ops.constant.system; + +import lombok.Getter; +import lombok.Setter; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +@Getter +public enum SystemEnvAttr { + + /** + * 密钥存放目录 + */ + KEY_PATH("密钥存放目录", false), + + /** + * 图片存放目录 + */ + PIC_PATH("图片存放目录", false), + + /** + * 交换分区目录 + */ + SWAP_PATH("交换分区目录", false), + + /** + * 录屏存放目录 + */ + SCREEN_PATH("录屏存放目录", false), + + /** + * 日志存放目录 + */ + LOG_PATH("日志存放目录", false), + + /** + * 临时文件目录 + */ + TEMP_PATH("临时文件目录", false), + + /** + * 应用版本仓库目录 + */ + REPO_PATH("应用版本仓库目录", false), + + /** + * 构建产物目录 + */ + DIST_PATH("构建产物目录", false), + + /** + * 机器监控插件绝对路径 + */ + MACHINE_MONITOR_AGENT_PATH("机器监控插件绝对路径", false), + + /** + * 日志文件上传目录 + */ + TAIL_FILE_UPLOAD_PATH("日志文件上传目录", false), + + /** + * 日志文件追踪模式 tracker或tail 默认tracker + */ + TAIL_MODE("日志文件追踪模式 (tracker/tail)", false), + + /** + * 文件追踪器等待时间 (ms) + */ + TRACKER_DELAY_TIME("文件追踪器等待时间 (ms)", false), + + /** + * ip 白名单 + */ + WHITE_IP_LIST("ip 白名单", true), + + /** + * ip 黑名单 + */ + BLACK_IP_LIST("ip 黑名单", true), + + /** + * 是否启用 IP 过滤 + */ + ENABLE_IP_FILTER("是否启用IP过滤", true), + + /** + * 是否启用 IP 白名单 + */ + ENABLE_WHITE_IP_LIST("是否启用IP白名单", true), + + /** + * 文件清理阈值 (天) + */ + FILE_CLEAN_THRESHOLD("文件清理阈值 (天)", true), + + /** + * 是否启用自动清理 + */ + ENABLE_AUTO_CLEAN_FILE("是否启用自动清理", true), + + /** + * 是否允许多端登录 + */ + ALLOW_MULTIPLE_LOGIN("允许多端登录", true), + + /** + * 是否启用登录失败锁定 + */ + LOGIN_FAILURE_LOCK("是否启用登录失败锁定", true), + + /** + * 是否启用登录IP绑定 + */ + LOGIN_IP_BIND("是否启用登录IP绑定", true), + + /** + * 是否启用凭证自动续签 + */ + LOGIN_TOKEN_AUTO_RENEW("是否启用凭证自动续签", true), + + /** + * 登录凭证有效期 (时) + */ + LOGIN_TOKEN_EXPIRE("登录凭证有效期 (时)", true), + + /** + * 登录失败锁定阈值 (次) + */ + LOGIN_FAILURE_LOCK_THRESHOLD("登录失败锁定阈值", true), + + /** + * 登录自动续签阈值 (时) + */ + LOGIN_TOKEN_AUTO_RENEW_THRESHOLD("登录自动续签阈值 (时)", true), + + /** + * 自动恢复启用的调度任务 + */ + RESUME_ENABLE_SCHEDULER_TASK("自动恢复启用的调度任务", true), + + /** + * 终端后台主动推送心跳 + */ + TERMINAL_ACTIVE_PUSH_HEARTBEAT("终端后台主动推送心跳", true), + + /** + * SFTP 上传文件最大阈值 (MB) + */ + SFTP_UPLOAD_THRESHOLD("sftp 上传文件最大阈值 (MB)", true), + + /** + * 统计缓存有效时间 (分) + */ + STATISTICS_CACHE_EXPIRE("统计缓存有效时间 (分)", true), + + ; + + /** + * key + */ + private final String key; + + /** + * 描述 + */ + private final String description; + + private final boolean systemEnv; + + @Setter + private String value; + + SystemEnvAttr(String description, boolean systemEnv) { + this.description = description; + this.systemEnv = systemEnv; + this.key = this.name().toLowerCase(); + } + + public static List getKeys() { + return Arrays.stream(values()) + .map(SystemEnvAttr::getKey) + .collect(Collectors.toList()); + } + + public static SystemEnvAttr of(String key) { + if (key == null) { + return null; + } + return Arrays.stream(values()) + .filter(a -> a.key.equals(key)) + .findFirst() + .orElse(null); + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/system/ThreadPoolMetricsType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/system/ThreadPoolMetricsType.java new file mode 100644 index 0000000..72cf07c --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/system/ThreadPoolMetricsType.java @@ -0,0 +1,89 @@ +package cn.orionsec.ops.constant.system; + +import cn.orionsec.ops.constant.SchedulerPools; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.concurrent.ThreadPoolExecutor; + +@AllArgsConstructor +@Getter +public enum ThreadPoolMetricsType { + + /** + * terminal + */ + TERMINAL(10, SchedulerPools.TERMINAL_SCHEDULER), + + /** + * terminal watcher + */ + TERMINAL_WATCHER(15, SchedulerPools.TERMINAL_WATCHER_SCHEDULER), + + /** + * 命令执行 + */ + EXEC(20, SchedulerPools.EXEC_SCHEDULER), + + /** + * tail + */ + TAIL(30, SchedulerPools.TAIL_SCHEDULER), + + /** + * sftp 传输进度 + */ + SFTP_TRANSFER_RATE(40, SchedulerPools.SFTP_TRANSFER_RATE_SCHEDULER), + + /** + * sftp 上传 + */ + SFTP_UPLOAD(50, SchedulerPools.SFTP_UPLOAD_SCHEDULER), + + /** + * sftp 下载 + */ + SFTP_DOWNLOAD(60, SchedulerPools.SFTP_DOWNLOAD_SCHEDULER), + + /** + * sftp 打包 + */ + SFTP_PACKAGE(70, SchedulerPools.SFTP_PACKAGE_SCHEDULER), + + /** + * 应用构建 + */ + APP_BUILD(80, SchedulerPools.APP_BUILD_SCHEDULER), + + /** + * 应用发布 主线程 + */ + RELEASE_MAIN(90, SchedulerPools.RELEASE_MAIN_SCHEDULER), + + /** + * 应用发布 机器操作 + */ + RELEASE_MACHINE(100, SchedulerPools.RELEASE_MACHINE_SCHEDULER), + + /** + * 调度任务 主线程 + */ + SCHEDULER_TASK_MAIN(110, SchedulerPools.SCHEDULER_TASK_MAIN_SCHEDULER), + + /** + * 调度任务 机器操作 + */ + SCHEDULER_TASK_MACHINE(120, SchedulerPools.SCHEDULER_TASK_MACHINE_SCHEDULER), + + /** + * 应用流水线 + */ + PIPELINE(130, SchedulerPools.PIPELINE_SCHEDULER), + + ; + + private final Integer type; + + private final ThreadPoolExecutor executor; + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/tail/FileTailMode.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/tail/FileTailMode.java new file mode 100644 index 0000000..1515776 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/tail/FileTailMode.java @@ -0,0 +1,59 @@ +package cn.orionsec.ops.constant.tail; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum FileTailMode { + + /** + * 仅宿主机 + * + * @see cn.orionsec.ops.tail.Tracker + * @see cn.orionsec.ops.handler.tail.impl.TrackerTailFileHandler + */ + TRACKER("tracker"), + + /** + * tail 命令 + *

+ * 宿主机 远程机器 + * + * @see cn.orionsec.ops.handler.tail.impl.ExecTailFileHandler + */ + TAIL("tail"), + + ; + + private final String mode; + + public static FileTailMode of(String mode) { + return of(mode, false); + } + + public static FileTailMode of(String mode, boolean hostMachine) { + if (Strings.isBlank(mode)) { + return hostMachine ? TRACKER : TAIL; + } + for (FileTailMode value : values()) { + if (value.mode.equals(mode)) { + return value; + } + } + return hostMachine ? TRACKER : TAIL; + } + + /** + * 获取宿主机 tailMode + * + * @return tailMode + */ + public static String getHostTailMode() { + String mode = SystemEnvAttr.TAIL_MODE.getValue(); + return of(mode, true).getMode(); + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/tail/FileTailType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/tail/FileTailType.java new file mode 100644 index 0000000..8a1a0b8 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/tail/FileTailType.java @@ -0,0 +1,72 @@ +package cn.orionsec.ops.constant.tail; + +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum FileTailType { + + /** + * 命令执行日志 + * + * @see SystemEnvAttr#LOG_PATH + */ + EXEC_LOG(10, true), + + /** + * tail 列表 + */ + TAIL_LIST(20, false), + + /** + * 应用构建日志 + * + * @see SystemEnvAttr#LOG_PATH + */ + APP_BUILD_LOG(30, true), + + /** + * 应用发布日志 + * + * @see SystemEnvAttr#LOG_PATH + */ + APP_RELEASE_LOG(40, true), + + /** + * 调度任务机器日志 + * + * @see SystemEnvAttr#LOG_PATH + */ + SCHEDULER_TASK_MACHINE_LOG(50, true), + + /** + * 应用操作日志 + * + * @see SystemEnvAttr#LOG_PATH + */ + APP_ACTION_LOG(60, true), + + ; + + private final Integer type; + + /** + * 是否为本地文件 + */ + private final boolean isLocal; + + public static FileTailType of(Integer type) { + if (type == null) { + return null; + } + for (FileTailType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/terminal/TerminalClientOperate.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/terminal/TerminalClientOperate.java new file mode 100644 index 0000000..ad0d47b --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/terminal/TerminalClientOperate.java @@ -0,0 +1,65 @@ +package cn.orionsec.ops.constant.terminal; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum TerminalClientOperate { + + /** + * 键入 + */ + KEY("0", true), + + /** + * 连接 + */ + CONNECT("1", true), + + /** + * ping + */ + PING("2", false), + + /** + * pong + */ + PONG("3", false), + + /** + * 更改大小 + */ + RESIZE("4", true), + + /** + * 键入命令 + */ + COMMAND("5", true), + + /** + * ctrl + l + */ + CLEAR("6", false), + + ; + + /** + * 前缀长度 + */ + public static final int PREFIX_SIZE = 1; + + private final String operate; + + private final boolean hasBody; + + public static TerminalClientOperate of(String operate) { + for (TerminalClientOperate value : values()) { + if (value.operate.equals(operate)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/terminal/TerminalConst.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/terminal/TerminalConst.java new file mode 100644 index 0000000..c2440ef --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/terminal/TerminalConst.java @@ -0,0 +1,40 @@ +package cn.orionsec.ops.constant.terminal; + +import cn.orionsec.ops.constant.Const; + +public class TerminalConst { + + private TerminalConst() { + } + + /** + * 判断终端心跳断开的阀值 + */ + public static final int TERMINAL_CONNECT_DOWN = Const.MS_S_60 + Const.MS_S_15; + + /** + * terminal symbol + */ + public static final String TERMINAL = "terminal"; + + /** + * 默认背景色 + */ + public static final String BACKGROUND_COLOR = "#212529"; + + /** + * 默认字体颜色 + */ + public static final String FONT_COLOR = "#FFFFFF"; + + /** + * 默认字体大小 + */ + public static final int FONT_SIZE = 14; + + /** + * 默认字体名称 + */ + public static final String FONT_FAMILY = "courier-new, courier, monospace"; + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/user/RoleType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/user/RoleType.java new file mode 100644 index 0000000..af6ab82 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/user/RoleType.java @@ -0,0 +1,56 @@ +package cn.orionsec.ops.constant.user; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum RoleType { + + /** + * 管理员 + */ + ADMINISTRATOR(10, "/menu/menu-admin.json"), + + /** + * 开发 + */ + DEVELOPER(20, "/menu/menu-dev.json"), + + /** + * 运维 + */ + OPERATION(30, "/menu/menu-opt.json"), + + ; + + private final Integer type; + + private final String menuPath; + + public static RoleType of(Integer type) { + if (type == null) { + return null; + } + for (RoleType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + + /** + * 是否为 管理员 + * + * @return true 管理员 + */ + public static boolean isAdministrator(Integer type) { + RoleType role = of(type); + if (role == null) { + return false; + } + return ADMINISTRATOR.equals(role); + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/webhook/WebhookType.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/webhook/WebhookType.java new file mode 100644 index 0000000..c018c67 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/webhook/WebhookType.java @@ -0,0 +1,45 @@ +package cn.orionsec.ops.constant.webhook; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum WebhookType { + + /** + * 钉钉机器人 + */ + DING_ROBOT(10, "钉钉机器人"), + + ; + + private final Integer type; + + private final String label; + + public static WebhookType of(Integer type) { + if (type == null) { + return null; + } + for (WebhookType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + + public static WebhookType of(String label) { + if (label == null) { + return null; + } + for (WebhookType value : values()) { + if (value.label.equals(label)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/ws/WsCloseCode.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/ws/WsCloseCode.java new file mode 100644 index 0000000..f499e51 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/ws/WsCloseCode.java @@ -0,0 +1,130 @@ +package cn.orionsec.ops.constant.ws; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum WsCloseCode { + + /** + * 未查询到token + */ + INCORRECT_TOKEN(4100, WsCloseReason.CLOSED_CONNECTION), + + /** + * 伪造token + */ + FORGE_TOKEN(4120, WsCloseReason.CLOSED_CONNECTION), + + /** + * token已被绑定 + */ + TOKEN_BIND(4125, WsCloseReason.CLOSED_CONNECTION), + + /** + * 未知的连接 + */ + UNKNOWN_CONNECT(4130, WsCloseReason.CLOSED_CONNECTION), + + /** + * 认证失败 id不匹配 + */ + IDENTITY_MISMATCH(4140, WsCloseReason.IDENTITY_MISMATCH), + + /** + * 认证信息不匹配 + */ + VALID(4150, WsCloseReason.AUTHENTICATION_FAILURE), + + /** + * 机器不合法 + */ + INVALID_MACHINE(4200, WsCloseReason.CLOSED_CONNECTION), + + /** + * 连接远程服务器连接超时 + */ + CONNECTION_TIMEOUT(4201, WsCloseReason.CONNECTION_TIMEOUT), + + /** + * 连接远程服务器失败 + */ + CONNECTION_FAILURE(4202, WsCloseReason.REMOTE_SERVER_UNREACHABLE), + + /** + * 远程服务器认证失败 + */ + CONNECTION_AUTH_FAILURE(4205, WsCloseReason.REMOTE_SERVER_AUTHENTICATION_FAILURE), + + /** + * 远程服务器认证出现异常 + */ + CONNECTION_EXCEPTION(4210, WsCloseReason.UNABLE_TO_CONNECT_REMOTE_SERVER), + + /** + * 机器未启用 + */ + MACHINE_DISABLED(4215, WsCloseReason.MACHINE_DISABLED), + + /** + * 打开shell出现异常 + */ + OPEN_SHELL_EXCEPTION(4220, WsCloseReason.UNABLE_TO_CONNECT_REMOTE_SERVER), + + /** + * 打开command出现异常 + */ + OPEN_COMMAND_EXCEPTION(4225, WsCloseReason.UNABLE_TO_CONNECT_REMOTE_SERVER), + + /** + * 打开sftp出现异常 + */ + OPEN_SFTP_EXCEPTION(4230, WsCloseReason.UNABLE_TO_CONNECT_REMOTE_SERVER), + + /** + * 服务出现异常 + */ + RUNTIME_EXCEPTION(4300, WsCloseReason.CLOSED_CONNECTION), + + /** + * 心跳结束 + */ + HEART_DOWN(4310, WsCloseReason.CLOSED_CONNECTION), + + /** + * 用户关闭 + */ + DISCONNECT(4320, WsCloseReason.CLOSED_CONNECTION), + + /** + * 结束 + */ + EOF(4330, WsCloseReason.CLOSED_CONNECTION), + + /** + * 读取失败 + */ + READ_EXCEPTION(4335, WsCloseReason.CLOSED_CONNECTION), + + /** + * 强制下线 + */ + FORCED_OFFLINE(4500, WsCloseReason.FORCED_OFFLINE), + + ; + + private final int code; + + private final String reason; + + public static WsCloseCode of(int code) { + for (WsCloseCode value : values()) { + if (value.code == code) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/ws/WsCloseReason.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/ws/WsCloseReason.java new file mode 100644 index 0000000..716bb67 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/ws/WsCloseReason.java @@ -0,0 +1,27 @@ +package cn.orionsec.ops.constant.ws; + + +public class WsCloseReason { + + private WsCloseReason() { + } + + public static final String CLOSED_CONNECTION = "closed connection..."; + + public static final String IDENTITY_MISMATCH = "identity mismatch..."; + + public static final String AUTHENTICATION_FAILURE = "authentication failure..."; + + public static final String REMOTE_SERVER_UNREACHABLE = "remote server unreachable..."; + + public static final String CONNECTION_TIMEOUT = "connection timeout..."; + + public static final String REMOTE_SERVER_AUTHENTICATION_FAILURE = "remote server authentication failure..."; + + public static final String MACHINE_DISABLED = "machine disabled..."; + + public static final String UNABLE_TO_CONNECT_REMOTE_SERVER = "unable to connect remote server..."; + + public static final String FORCED_OFFLINE = "forced offline..."; + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/ws/WsProtocol.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/ws/WsProtocol.java new file mode 100644 index 0000000..d419104 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/constant/ws/WsProtocol.java @@ -0,0 +1,73 @@ +package cn.orionsec.ops.constant.ws; + +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.Valid; +import lombok.AllArgsConstructor; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + + +@AllArgsConstructor +public enum WsProtocol { + + /** + * 正常返回 + */ + OK("0"), + + /** + * 连接成功 + */ + CONNECTED("1"), + + /** + * ping + */ + PING("2"), + + /** + * pong + */ + PONG("3"), + + /** + * 未知操作 + */ + ERROR("4"), + + ; + + private final String code; + + /** + * 分隔符 + */ + public static final String SYMBOL = "|"; + + public byte[] get() { + return Strings.bytes(code); + } + + public byte[] msg(String body) { + Valid.notNull(body); + return this.msg(Strings.bytes(body)); + } + + public byte[] msg(byte[] body) { + return this.msg(body, 0, body.length); + } + + public byte[] msg(byte[] body, int offset, int len) { + Valid.notNull(body); + try (ByteArrayOutputStream o = new ByteArrayOutputStream()) { + o.write(Strings.bytes(code + SYMBOL)); + o.write(body, offset, len); + return o.toByteArray(); + } catch (IOException e) { + throw Exceptions.ioRuntime(e); + } + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/AttrConverts.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/AttrConverts.java new file mode 100644 index 0000000..6871c86 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/AttrConverts.java @@ -0,0 +1,165 @@ +package cn.orionsec.ops.utils; + +import cn.orionsec.kit.lang.define.collect.MutableArrayList; +import cn.orionsec.kit.lang.define.collect.MutableLinkedHashMap; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.ext.dom.*; +import cn.orionsec.ops.constant.Const; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.parser.Feature; +import com.alibaba.fastjson.serializer.SerializerFeature; +import org.dom4j.io.OutputFormat; +import org.yaml.snakeyaml.Yaml; + +import java.util.Map; + +public class AttrConverts { + + private static final String XML_ROOT_TAG = "root"; + + private static final String XML_NODE_TAG = "env"; + + private static final String XML_NODE_KEY_ATTR = "key"; + + private AttrConverts() { + } + + /** + * env -> json + * + * @param attrs env + * @return json + */ + public static String toJson(Map attrs) { + return JSON.toJSONString(attrs, SerializerFeature.PrettyFormat); + } + + /** + * env -> xml + * + * @param attrs env + * @return xml + */ + public static String toXml(Map attrs) { + DomBuilder builder = DomBuilder.create(); + DomElement root = builder.createRootElement(XML_ROOT_TAG); + attrs.forEach((k, v) -> { + DomElement child = new DomElement(XML_NODE_TAG, v); + child.addAttributes(XML_NODE_KEY_ATTR, k); + if (DomSupport.isEscape(v)) { + child.cdata(); + } + root.addChildNode(child); + }); + builder.build(); + OutputFormat format = new OutputFormat(Const.SPACE_4, true); + format.setExpandEmptyElements(true); + // 隐藏头 + format.setSuppressDeclaration(true); + return DomSupport.format(builder.getDocument(), format).substring(1); + } + + /** + * env -> yml + * + * @param attrs env + * @return yml + */ + public static String toYml(Map attrs) { + return new Yaml().dumpAsMap(attrs); + } + + /** + * env -> properties + * + * @param attrs env + * @return properties + */ + public static String toProperties(Map attrs) { + StringBuilder sb = new StringBuilder(); + attrs.forEach((k, v) -> { + sb.append(k).append("=").append(v).append(Const.LF); + }); + return sb.toString(); + } + + /** + * json -> env + * + * @param json xml + * @return json + */ + public static MutableLinkedHashMap fromJson(String json) { + MutableLinkedHashMap map = Maps.newMutableLinkedMap(); + JSONObject res = JSON.parseObject(json, Feature.OrderedField); + res.forEach((k, v) -> map.put(k, v + Strings.EMPTY)); + return map; + } + + /** + * xml -> env + * + * @param xml xml + * @return env + */ + public static MutableLinkedHashMap fromXml(String xml) { + MutableLinkedHashMap map = Maps.newMutableLinkedMap(); + DomNode domNode = DomExt.of(xml).toDomNode().get(XML_NODE_TAG); + MutableArrayList list; + if (domNode.getValueClass() == String.class) { + list = Lists.newMutableList(); + list.add(domNode); + } else { + list = domNode.getListValue(); + } + list.forEach(e -> { + Map attr = e.getAttr(); + if (Maps.isEmpty(attr)) { + return; + } + String key = attr.get(XML_NODE_KEY_ATTR); + if (Strings.isBlank(key)) { + return; + } + map.put(key, e.getStringValue()); + }); + return map; + } + + /** + * yml -> env + * + * @param yml yml + * @return env + */ + @SuppressWarnings("unchecked") + public static MutableLinkedHashMap fromYml(String yml) { + return new Yaml().loadAs(yml, MutableLinkedHashMap.class); + } + + /** + * properties -> env + * + * @param properties properties + * @return env + */ + public static MutableLinkedHashMap fromProperties(String properties) { + MutableLinkedHashMap map = Maps.newMutableLinkedMap(); + String[] rows = properties.split(Const.LF); + for (String row : rows) { + String key = row.split("=")[0]; + String value; + if (row.length() > key.length()) { + value = row.substring(key.length() + 1); + } else { + value = Strings.EMPTY; + } + map.put(key, value); + } + return map; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/AvatarPicHolder.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/AvatarPicHolder.java new file mode 100644 index 0000000..ca68111 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/AvatarPicHolder.java @@ -0,0 +1,89 @@ +package cn.orionsec.ops.utils; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.codec.Base64s; +import cn.orionsec.kit.lang.utils.io.FileReaders; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class AvatarPicHolder { + + private AvatarPicHolder() { + } + + /** + * 获取头像路径 + * + * @param uid uid + * @param suffix 后缀 + * @return path + */ + public static String getPicPath(Long uid, String suffix) { + return Const.AVATAR_DIR + "/" + uid + "." + suffix; + } + + /** + * 创建并获取头像全路径 + * + * @param uid uid + * @param suffix 后缀 + * @return path + */ + public static String touchPicFile(Long uid, String suffix) { + String path = Files1.getPath(SystemEnvAttr.PIC_PATH.getValue(), getPicPath(uid, suffix)); + Files1.touch(path); + return path; + } + + /** + * 删除图片 + * + * @param url url + */ + public static void deletePic(String url) { + String path = Files1.getPath(SystemEnvAttr.PIC_PATH.getValue(), url); + Files1.delete(path); + } + + /** + * 获取头像 + * + * @param path path + * @return avatar base64 + */ + public static String getUserAvatar(String path) { + if (isExist(path)) { + return getBase64(path); + } else { + return null; + } + } + + /** + * 判断是否存在 + * + * @param path path + * @return 是否存在 + */ + public static boolean isExist(String path) { + return Strings.isNotBlank(path) && Files1.isFile(Files1.getPath(SystemEnvAttr.PIC_PATH.getValue(), path)); + } + + /** + * 获取头像 base64 + * + * @param url url + * @return base64 + */ + public static String getBase64(String url) { + String path = Files1.getPath(SystemEnvAttr.PIC_PATH.getValue(), url); + if (!Files1.isFile(path)) { + return null; + } + return Base64s.imgEncode(FileReaders.readAllBytes(path), Files1.getSuffix(path)); + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/EventLogUtils.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/EventLogUtils.java new file mode 100644 index 0000000..595aaf7 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/EventLogUtils.java @@ -0,0 +1,24 @@ +package cn.orionsec.ops.utils; + +import cn.orionsec.ops.constant.event.EventKeys; + +import java.util.Map; + +public class EventLogUtils { + + private EventLogUtils() { + } + + /** + * 移除内部key + * + * @param map map + */ + public static void removeInnerKeys(Map map) { + map.remove(EventKeys.INNER_USER_ID); + map.remove(EventKeys.INNER_USER_NAME); + map.remove(EventKeys.INNER_SAVE); + map.remove(EventKeys.INNER_TEMPLATE); + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/FileCleaner.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/FileCleaner.java new file mode 100644 index 0000000..4275a09 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/FileCleaner.java @@ -0,0 +1,116 @@ +package cn.orionsec.ops.utils; + +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.system.SystemCleanType; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.util.Date; +import java.util.List; + +@Slf4j +public class FileCleaner { + + private FileCleaner() { + } + + /** + * 清理所有 + */ + public static void cleanAll() { + Date threshold = Dates.stream().subDay(Integer.parseInt(SystemEnvAttr.FILE_CLEAN_THRESHOLD.getValue())).get(); + long thresholdTime = threshold.getTime(); + log.info("自动清理文件-阈值 {}", Dates.format(threshold)); + long releasedBytes = 0L; + // 清理临时文件 + releasedBytes += deletePathFiles(thresholdTime, new File(SystemEnvAttr.TEMP_PATH.getValue())); + // 日志文件 + releasedBytes += deletePathFiles(thresholdTime, new File(SystemEnvAttr.LOG_PATH.getValue())); + // 交换文件 + releasedBytes += deletePathFiles(thresholdTime, new File(SystemEnvAttr.SWAP_PATH.getValue())); + // 构建产物 + releasedBytes += deletePathFiles(thresholdTime, new File(SystemEnvAttr.DIST_PATH.getValue())); + // 应用仓库 文件太多会 oom + // File repoPath = new File(SystemEnvAttr.REPO_PATH.getValue()); + // List repoPaths = Files1.listFilesFilter(repoPath, (f, n) -> f.isDirectory() && !Const.EVENT.equals(n), false, true); + // for (File repo : repoPaths) { + // releasedBytes += deletePathFiles(thresholdTime, repo); + // } + log.info("自动清理文件-释放 {}", Files1.getSize(releasedBytes)); + } + + /** + * 清理目录 + * + * @param cleanType cleanType + */ + public static void cleanDir(SystemCleanType cleanType) { + Date threshold = Dates.stream().subDay(Integer.parseInt(SystemEnvAttr.FILE_CLEAN_THRESHOLD.getValue())).get(); + long thresholdTime = threshold.getTime(); + long releasedBytes = 0L; + log.info("手动清理文件-阈值 {}", Dates.format(threshold)); + switch (cleanType) { + case TEMP_FILE: + // 清理临时文件 + releasedBytes += deletePathFiles(thresholdTime, new File(SystemEnvAttr.TEMP_PATH.getValue())); + break; + case LOG_FILE: + // 日志文件 + releasedBytes += deletePathFiles(thresholdTime, new File(SystemEnvAttr.LOG_PATH.getValue())); + break; + case SWAP_FILE: + // 交换文件 + releasedBytes += deletePathFiles(thresholdTime, new File(SystemEnvAttr.SWAP_PATH.getValue())); + break; + case DIST_FILE: + // 构建产物 + releasedBytes += deletePathFiles(thresholdTime, new File(SystemEnvAttr.DIST_PATH.getValue())); + break; + case REPO_FILE: + // 应用仓库 + File repoPath = new File(SystemEnvAttr.REPO_PATH.getValue()); + List repoPaths = Files1.listFilesFilter(repoPath, f -> f.isDirectory() && !Const.EVENT.equals(f.getName()), false, true); + for (File repo : repoPaths) { + releasedBytes += deletePathFiles(thresholdTime, repo); + } + break; + case SCREEN_FILE: + // 录屏文件 + releasedBytes += deletePathFiles(thresholdTime, new File(SystemEnvAttr.SCREEN_PATH.getValue())); + break; + default: + } + log.info("手动清理文件-释放 {}", Files1.getSize(releasedBytes)); + } + + /** + * 删除文件 + * + * @param thresholdTime 阈值 + * @param path 文件夹 + * @return size + */ + private static long deletePathFiles(long thresholdTime, File path) { + List files = Files1.listFilesFilter(path, p -> checkLessThanThreshold(thresholdTime, p), true, true); + long size = files.stream().mapToLong(File::length).sum(); + files.stream().filter(File::isFile).forEach(Files1::delete); + files.stream().filter(File::isDirectory).forEach(Files1::delete); + files.clear(); + return size; + } + + /** + * 检查文件是否超过阈值 + * + * @param thresholdTime 阈值 + * @param file 文件 + * @return 是否超过 + */ + private static boolean checkLessThanThreshold(long thresholdTime, File file) { + return file.lastModified() <= thresholdTime; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/PathBuilders.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/PathBuilders.java new file mode 100644 index 0000000..d90d146 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/PathBuilders.java @@ -0,0 +1,286 @@ +package cn.orionsec.ops.utils; + +import cn.orionsec.kit.lang.id.ObjectIds; +import cn.orionsec.kit.lang.utils.Systems; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.ops.constant.Const; + +import java.util.List; + +public class PathBuilders { + + private PathBuilders() { + } + + /** + * 获取 terminal 录屏路径 + * + * @param userId userId + * @param machineId machineId + * @return path + */ + public static String getTerminalScreenPath(Long userId, Long machineId) { + return Const.TERMINAL_DIR + + "/" + userId + + "_" + machineId + + "_" + Dates.current(Dates.YMD_HMS2) + + "." + Const.CAST_SUFFIX; + } + + /** + * 获取 exec 日志路径 + * + * @param suffix suffix + * @param execId execId + * @param machineId machineId + * @return path + */ + public static String getExecLogPath(String suffix, Long execId, Long machineId) { + return Const.EXEC_DIR + suffix + + "/" + execId + + "_" + machineId + + "_" + Dates.current(Dates.YMD_HMS2) + + "." + Const.SUFFIX_LOG; + } + + /** + * 获取应用构建产物文件目录 + * + * @param buildId buildId + * @return path + */ + public static String getBuildBundlePath(Long buildId) { + return Const.BUILD_DIR + "/" + buildId; + } + + /** + * 获取应用构建日志文件 + * + * @param buildId buildId + * @return path + */ + public static String getBuildLogPath(Long buildId) { + return Const.BUILD_DIR + + "/" + buildId + + "/" + Const.BUILD + + "." + Const.SUFFIX_LOG; + } + + /** + * 获取应用构建操作日志文件 + * + * @param buildId buildId + * @return path + */ + public static String getBuildActionLogPath(Long buildId, Long actionId) { + return Const.BUILD_DIR + + "/" + buildId + + "/" + Const.ACTION + + "_" + actionId + + "." + Const.SUFFIX_LOG; + } + + /** + * 获取应用发布目标机器日志文件 + * + * @param releaseId releaseId + * @param machineId 机器id + * @return path + */ + public static String getReleaseMachineLogPath(Long releaseId, Long machineId) { + return Const.RELEASE_DIR + + "/" + releaseId + + Const.RELEASE_MACHINE_PREFIX + + "_" + machineId + + "." + Const.SUFFIX_LOG; + } + + /** + * 获取应用发布操作日志文件 + * + * @param releaseId releaseId + * @param machineId machineId + * @param actionId actionId + * @return path + */ + public static String getReleaseActionLogPath(Long releaseId, Long machineId, Long actionId) { + return Const.RELEASE_DIR + + "/" + releaseId + + Const.RELEASE_MACHINE_PREFIX + + "_" + machineId + + "_" + Const.ACTION + + "_" + actionId + + "." + Const.SUFFIX_LOG; + } + + /** + * 获取 release 产物快照文件 + * + * @param releaseId releaseId + * @param distPath distPath + * @return path + */ + public static String getDistSnapshotPath(Long releaseId, String distPath) { + return "/" + releaseId + "_" + Files1.getFileName(distPath); + } + + /** + * 获取调度任务机器日志文件 + * + * @param taskId taskId + * @param recordId recordId + * @param machineId machineId + * @return path + */ + public static String getSchedulerTaskLogPath(Long taskId, Long recordId, Long machineId) { + return Const.TASK_DIR + + "/" + taskId + + "/" + recordId + + "/" + machineId + + "." + Const.SUFFIX_LOG; + } + + + /** + * 获取 sftp upload 文件路径 + * + * @param fileToken fileToken + * @return path + */ + public static String getSftpUploadFilePath(String fileToken) { + return Const.UPLOAD_DIR + "/" + fileToken + Const.SWAP_FILE_SUFFIX; + } + + /** + * 获取 sftp download 文件路径 + * + * @param fileToken fileToken + * @return path + */ + public static String getSftpDownloadFilePath(String fileToken) { + return Const.DOWNLOAD_DIR + "/" + fileToken + Const.SWAP_FILE_SUFFIX; + } + + /** + * 获取 sftp package 文件路径 + * + * @param fileToken fileToken + * @return path + */ + public static String getSftpPackageFilePath(String fileToken) { + return Const.PACKAGE_DIR + "/" + fileToken + "." + Const.SUFFIX_ZIP; + } + + /** + * 获取导出数据 xlsx 文件路径 + * + * @param userId userId + * @param type type + * @return path + */ + public static String getExportDataJsonPath(Long userId, Integer type, String password) { + return Const.EXPORT_DIR + + "/" + type + + "/" + Dates.current(Dates.YMD_HMS2) + + "-" + userId + + "-" + password + + "." + Const.SUFFIX_XLSX; + } + + /** + * 获取导入数据 json 文件路径 + * + * @param userId userId + * @param type type + * @param token token + * @return path + */ + public static String getImportDataJsonPath(Long userId, Integer type, String token) { + return Const.IMPORT_DIR + + "/" + type + + "/" + Dates.current(Dates.YMD_HMS2) + + "-" + userId + + "-" + token + + "." + Const.SUFFIX_JSON; + } + + /** + * 获取用户根目录 + * + * @param username 用户名 + * @return 用户目录 + */ + public static String getHomePath(String username) { + if (Const.ROOT.equals(username)) { + return "/" + Const.ROOT; + } else { + return "/home/" + username; + } + } + + /** + * 获取密钥路径 + * + * @return path + */ + public static String getSecretKeyPath() { + return "/" + ObjectIds.nextId() + Const.SECRET_KEY_SUFFIX; + } + + /** + * 获取宿主机环境目录 + * + * @param path 子目录 + * @return path + */ + public static String getHostEnvPath(String path) { + return Systems.HOME_DIR + "/" + Const.ORION_OPS + "/" + path; + } + + /** + * 获取 sftp 打包临时目录 + * + * @param username username + * @param fileToken fileToken + * @param paths paths + * @return path + */ + public static String getSftpPackageTempPath(String username, String fileToken, List paths) { + return PathBuilders.getHomePath(username) + + "/" + Const.ORION_OPS + + "/" + Const.PACKAGE + + "/" + fileToken + + "/" + Files1.getFileName(paths.get(0)) + + " more files" + + "." + Const.SUFFIX_ZIP; + } + + /** + * 获取插件目录 + * + * @param username username + * @return path + */ + public static String getPluginPath(String username) { + return PathBuilders.getHomePath(username) + + "/" + Const.ORION_OPS + + "/" + Const.PLUGINS; + } + + /** + * 获取安装日志路径 + * + * @param machineId machineId + * @param app app + * @return path + */ + public static String getInstallLogPath(Long machineId, String app) { + return Const.INSTALL_DIR + + "/" + app + + "/" + machineId + + "-" + Dates.current(Dates.YMD_HMS2) + + "." + Const.SUFFIX_LOG; + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/ResourceLoader.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/ResourceLoader.java new file mode 100644 index 0000000..d55493e --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/ResourceLoader.java @@ -0,0 +1,40 @@ +package cn.orionsec.ops.utils; + +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.io.Streams; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.Objects; + +@Slf4j +public class ResourceLoader { + + /** + * key: resource + * value: content + */ + private static final Map RESOURCE = Maps.newMap(); + + /** + * 获取资源 + * + * @param resource resource + * @param loader 加载类 + * @return content + */ + public static String get(String resource, Class loader) { + try (InputStream stream = loader.getResourceAsStream(resource)) { + String content = Streams.toString(Objects.requireNonNull(stream)); + RESOURCE.put(resource, content); + return content; + } catch (IOException e) { + throw Exceptions.notFound(Strings.format("resource not found {} by {}", resource, loader.getName()), e); + } + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/Utils.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/Utils.java new file mode 100644 index 0000000..fa32fe7 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/Utils.java @@ -0,0 +1,263 @@ +package cn.orionsec.ops.utils; + +import cn.orionsec.kit.ext.location.Region; +import cn.orionsec.kit.ext.location.region.LocationRegions; +import cn.orionsec.kit.lang.id.UUIds; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.lang.utils.net.IPs; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.ops.constant.CnConst; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.common.StainCode; +import cn.orionsec.ops.constant.system.SystemEnvAttr; + +import java.util.List; +import java.util.stream.Collectors; + +public class Utils { + + private Utils() { + } + + /** + * 获取随机后缀 + * + * @return suffix + */ + public static String getRandomSuffix() { + return UUIds.random32().substring(0, 5).toUpperCase(); + } + + /** + * 获取后缀 + * + * @param symbol symbol + * @return suffix + */ + public static String getSymbolSuffix(String symbol) { + return " - " + symbol + Strings.SPACE + UUIds.random32().substring(0, 5).toUpperCase(); + } + + /** + * 检查是否是合法 ip 配置行 + * + * @param line line + * @return 是否合法 + */ + public static boolean validIpLine(String line) { + // 普通ip + line = line.trim(); + if (!line.contains(Const.SLASH)) { + return IPs.isIpv4(line); + } + return validIpRange(line); + } + + /** + * 检查是否为合法 ip 区间 + * + * @param range range + * @return 是否合法 + */ + public static boolean validIpRange(String range) { + // ip区间 + String[] split = range.split(Const.SLASH); + String first = split[0]; + String last = split[1]; + if (split.length != 2) { + return false; + } + // 检查第一段 + if (!IPs.isIpv4(first)) { + return false; + } + // 尾ip + if (!last.contains(Const.DOT)) { + last = first.substring(0, first.lastIndexOf(Const.DOT)) + Const.DOT + last; + } + return IPs.isIpv4(last); + } + + /** + * 检查 ip 是否在范围内 + * + * @param ip ip + * @param filter filter + * @return 是否在范围内 + */ + public static boolean checkIpIn(String ip, String filter) { + filter = filter.trim(); + // 单个ip + if (!filter.contains(Const.SLASH)) { + return ip.equals(filter); + } + // ip区间 + String[] split = filter.split(Const.SLASH); + String first = split[0]; + String last = split[1]; + // 尾ip + if (!last.contains(Const.DOT)) { + last = first.substring(0, first.lastIndexOf(Const.DOT)) + Const.DOT + last; + } + return IPs.ipInRange(first, last, ip); + } + + /** + * 获取事件仓库路径 + * + * @param id id + * @return 路径 + */ + public static String getRepositoryEventDir(Long id) { + return Files1.getPath(SystemEnvAttr.REPO_PATH.getValue(), Const.EVENT_DIR + "/" + id); + } + + /** + * 获取时差 + * + * @param ms ms + * @return 时差 + */ + public static String interval(Long ms) { + if (ms == null) { + return null; + } + return Dates.interval(ms, false, "d ", "h ", "m ", "s"); + } + + /** + * 获取 ANSI 高亮颜色行 + * + * @param key key + * @param code code + * @return 高亮字体 + * @see StainCode + */ + public static String getStainKeyWords(Object key, int code) { + return StainCode.prefix(code) + key + StainCode.SUFFIX; + } + + /** + * 清除 ANSI 高亮颜色行 + * + * @param s s + * @return 清除 ANSI 颜色属性 + * @see StainCode + */ + public static String cleanStainAnsiCode(String s) { + return s.replaceAll("\\u001B\\[\\w{1,3}m", Const.EMPTY); + } + + /** + * 判断是否为 \n 结尾 + * 是则返回 \n + * 否则返回 \n\n + * + * @param s s + * @return lf + */ + public static String getEndLfWithEof(String s) { + return s.endsWith(Const.LF) ? s + Const.LF : s + Const.LF_2; + } + + /** + * 获取 sftp 压缩命令 + * + * @param zipPath zipPath + * @param paths paths + * @return zip 命令 + */ + public static String getSftpPackageCommand(String zipPath, List paths) { + return "mkdir -p " + Files1.getParentPath(zipPath) + " && zip -r \"" + zipPath + "\" " + + paths.stream() + .map(Files1::getPath) + .map(s -> "\"" + s + "\"") + .collect(Collectors.joining(Const.SPACE)); + } + + /** + * 清空高亮标签 + * + * @param m m + * @return clean + */ + public static String cleanStainTag(String m) { + if (Strings.isEmpty(m)) { + return m; + } + return m.replaceAll("", Const.EMPTY) + .replaceAll("", Const.EMPTY) + .replaceAll("", Const.EMPTY) + .replaceAll("", Const.EMPTY) + .replaceAll("", Const.EMPTY) + .replaceAll("", Const.EMPTY) + .replaceAll("", Const.EMPTY) + .replaceAll("", Const.EMPTY) + .replaceAll("", Const.EMPTY) + .replaceAll("", Const.EMPTY); + } + + /** + * 获取 ip 位置 + * + * @param ip ip + * @return ip 位置 + */ + public static String getIpLocation(String ip) { + if (ip == null) { + return CnConst.UNKNOWN; + } + Region region; + try { + region = LocationRegions.getRegion(ip, 3); + } catch (Exception e) { + return CnConst.UNKNOWN; + } + if (region != null) { + String net = region.getNet(); + String province = region.getProvince(); + if (net.equals(CnConst.INTRANET_IP)) { + return net; + } + if (province.equals(CnConst.UNKNOWN)) { + return province; + } + StringBuilder location = new StringBuilder() + .append(region.getCountry()) + .append(Const.DASHED) + .append(province) + .append(Const.DASHED) + .append(region.getCity()); + location.append(" (").append(net).append(')'); + return location.toString(); + } + return CnConst.UNKNOWN; + } + + /** + * 转换控制字符 unicode + * + * @param str str + * @return unicode + */ + public static String convertControlUnicode(String str) { + char[] chars = str.toCharArray(); + StringBuilder sb = new StringBuilder(); + for (char c : chars) { + if (c < 32 || c == 34 || c == 92 || c == 127) { + sb.append("\\u00"); + int code = (c & 0xFF); + String tmp = Integer.toHexString(code); + if (tmp.length() == 1) { + sb.append("0"); + } + sb.append(tmp); + } else { + sb.append(c); + } + } + return sb.toString(); + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/Valid.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/Valid.java new file mode 100644 index 0000000..cf83b8c --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/Valid.java @@ -0,0 +1,110 @@ +package cn.orionsec.ops.utils; + +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.kit.lang.utils.Arrays1; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.system.SystemEnvAttr; + +import java.util.Collection; + +public class Valid extends cn.orionsec.kit.lang.utils.Valid { + + public static T notNull(T object) { + return notNull(object, MessageConst.ABSENT_PARAM); + } + + public static String notBlank(String s) { + return notBlank(s, MessageConst.ABSENT_PARAM); + } + + public static > T notEmpty(T object) { + return notEmpty(object, MessageConst.ABSENT_PARAM); + } + + public static void eq(Object o1, Object o2) { + eq(o1, o2, MessageConst.INVALID_PARAM); + } + + public static void allNotNull(Object... objects) { + if (objects != null) { + for (Object t : objects) { + notNull(t, MessageConst.ABSENT_PARAM); + } + } + } + + public static void allNotBlank(String... ss) { + if (ss != null) { + for (String s : ss) { + notBlank(s, MessageConst.ABSENT_PARAM); + } + } + } + + public static boolean isTrue(boolean s) { + return isTrue(s, MessageConst.INVALID_PARAM); + } + + public static boolean isFalse(boolean s) { + return isFalse(s, MessageConst.INVALID_PARAM); + } + + public static > T gte(T t1, T t2) { + return gte(t1, t2, MessageConst.INVALID_PARAM); + } + + @SafeVarargs + public static T in(T t, T... ts) { + notNull(t); + notEmpty(ts); + isTrue(Arrays1.contains(ts, t), MessageConst.INVALID_PARAM); + return t; + } + + public static void isSafe(boolean s) { + isTrue(s, MessageConst.UNSAFE_OPERATOR); + } + + /** + * 检查路径是否合法化 即不包含 ./ ../ + * + * @param path path + */ + public static void checkNormalize(String path) { + Valid.notBlank(path); + Valid.isTrue(Files1.isNormalize(path), MessageConst.PATH_NOT_NORMALIZE); + } + + /** + * 检查是否超过文件上传阈值 + * + * @param size size + */ + public static void checkUploadSize(Long size) { + Valid.notNull(size); + String uploadThreshold = SystemEnvAttr.SFTP_UPLOAD_THRESHOLD.getValue(); + if (size / Const.BUFFER_KB_1 / Const.BUFFER_KB_1 > Long.parseLong(uploadThreshold)) { + throw Exceptions.argument(Strings.format(MessageConst.UPLOAD_TOO_LARGE, uploadThreshold, Files1.getSize(size))); + } + } + + /** + * 检查 api 调用是否成功 + * + * @param wrapper wrapper + * @param T + * @return value + */ + public static T api(HttpWrapper wrapper) { + if (wrapper.isOk()) { + return wrapper.getData(); + } else { + throw Exceptions.httpRequest(null, String.valueOf(wrapper.getData())); + } + } + +} diff --git a/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/ValueMix.java b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/ValueMix.java new file mode 100644 index 0000000..8ff1d3a --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/main/java/cn/orionsec/ops/utils/ValueMix.java @@ -0,0 +1,208 @@ +package cn.orionsec.ops.utils; + +import cn.orionsec.kit.lang.utils.Arrays1; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.codec.Base62s; +import cn.orionsec.kit.lang.utils.crypto.Signatures; +import cn.orionsec.kit.lang.utils.crypto.enums.PaddingMode; +import cn.orionsec.kit.lang.utils.crypto.enums.WorkingMode; +import cn.orionsec.kit.lang.utils.crypto.symmetric.EcbSymmetric; +import cn.orionsec.kit.lang.utils.crypto.symmetric.SymmetricBuilder; +import cn.orionsec.ops.constant.PropertiesConst; + +import java.util.Arrays; +import java.util.Optional; + +public class ValueMix { + + private static final EcbSymmetric ECB = SymmetricBuilder.aes() + .workingMode(WorkingMode.ECB) + .paddingMode(PaddingMode.PKCS5_PADDING) + .generatorSecretKey(PropertiesConst.VALUE_MIX_SECRET_KEY) + .buildEcb(); + + private ValueMix() { + } + + /** + * 加密 + * + * @param value 明文 + * @return 密文 + */ + public static String encrypt(String value) { + if (value == null) { + return null; + } + try { + return ValueMix.ECB.encryptAsString(value); + } catch (Exception e) { + return null; + } + } + + /** + * 加密 + * + * @param value 明文 + * @param key key + * @return 密文 + */ + public static String encrypt(String value, String key) { + if (value == null) { + return null; + } + try { + return SymmetricBuilder.aes() + .workingMode(WorkingMode.ECB) + .paddingMode(PaddingMode.PKCS5_PADDING) + .generatorSecretKey(key) + .buildEcb() + .encryptAsString(value); + } catch (Exception e) { + return null; + } + } + + /** + * 解密 + * + * @param value 密文 + * @return 明文 + */ + public static String decrypt(String value) { + if (value == null) { + return null; + } + try { + return ECB.decryptAsString(value); + } catch (Exception e) { + return null; + } + } + + /** + * 解密 + * + * @param value 密文 + * @param key key + * @return 明文 + */ + public static String decrypt(String value, String key) { + if (value == null) { + return null; + } + try { + return SymmetricBuilder.aes() + .workingMode(WorkingMode.ECB) + .paddingMode(PaddingMode.PKCS5_PADDING) + .generatorSecretKey(key) + .buildEcb() + .decryptAsString(value); + } catch (Exception e) { + return null; + } + } + + /** + * 明文 -> ecb -> base62 -> 密文 + * + * @param value value + * @param key key + * @return 密文 + */ + public static String base62ecbEnc(String value, String key) { + return Optional.ofNullable(value) + .map(v -> encrypt(value, key)) + .map(Base62s::encode) + .orElse(null); + } + + /** + * 密文 -> base62 -> ecb -> 明文 + * + * @param value value + * @param key key + * @return 明文 + */ + public static String base62ecbDec(String value, String key) { + return Optional.ofNullable(value) + .map(Base62s::decode) + .map(v -> decrypt(v, key)) + .orElse(null); + } + + /** + * 密码签名 + * + * @param password password + * @param salt 盐 + * @return 密文 + */ + public static String encPassword(String password, String salt) { + return Signatures.md5(password, salt, 3); + } + + /** + * 密码验签 + * + * @param password password + * @param salt salt + * @param sign 密码签名 + * @return 是否正确 + */ + public static boolean validPassword(String password, String salt, String sign) { + return sign.equals(Signatures.md5(password, salt, 3)); + } + + /** + * 创建登录token + * + * @param uid uid + * @param timestamp 时间戳 + * @return token + */ + public static String createLoginToken(Long uid, Long timestamp) { + return ValueMix.base62ecbEnc(uid + "_" + timestamp, PropertiesConst.VALUE_MIX_SECRET_KEY); + } + + /** + * 检查loginToken是否合法 + * + * @param token token + * @return true合法 + */ + public static boolean validLoginToken(String token) { + return ValueMix.base62ecbDec(token, PropertiesConst.VALUE_MIX_SECRET_KEY) != null; + } + + /** + * 获取loginToken的uid + * + * @param token token + * @return uid + */ + public static Long getLoginTokenUserId(String token) { + return Optional.ofNullable(ValueMix.base62ecbDec(token, PropertiesConst.VALUE_MIX_SECRET_KEY)) + .map(s -> s.split("_")) + .map(s -> s[0]) + .filter(Strings::isInteger) + .map(Long::valueOf) + .orElse(null); + } + + /** + * 获取loginToken的信息 + * + * @param token token + * @return [uid, loginTimestamp] + */ + public static Long[] getLoginTokenInfo(String token) { + return Optional.ofNullable(ValueMix.base62ecbDec(token, PropertiesConst.VALUE_MIX_SECRET_KEY)) + .map(s -> s.split("_")) + .filter(s -> Arrays.stream(s).allMatch(Strings::isInteger)) + .map(s -> Arrays1.mapper(s, Long[]::new, Long::valueOf)) + .orElse(null); + } + +} diff --git a/orion-ops-api/orion-ops-common/src/test/java/cn/orionsec/ops/constant/event/AddLicenseHeader.java b/orion-ops-api/orion-ops-common/src/test/java/cn/orionsec/ops/constant/event/AddLicenseHeader.java new file mode 100644 index 0000000..f8e22a4 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/test/java/cn/orionsec/ops/constant/event/AddLicenseHeader.java @@ -0,0 +1,83 @@ +package cn.orionsec.ops.constant.event; + +import cn.orionsec.kit.lang.define.StopWatch; +import cn.orionsec.kit.lang.utils.io.FileReaders; +import cn.orionsec.kit.lang.utils.io.FileWriters; +import cn.orionsec.kit.lang.utils.io.Files1; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +public class AddLicenseHeader { + + private static final String LICENSE = "/*\n" + + " * Copyright (c) 2021 - present Jiahang Li, All rights reserved.\n" + + " *\n" + + " * https://ops.orionsec.cn\n" + + " *\n" + + " * Members:\n" + + " * Jiahang Li - ljh1553488six@139.com - author\n" + + " *\n" + + " * Licensed under the Apache License, Version 2.0 (the \"License\");\n" + + " * you may not use this file except in compliance with the License.\n" + + " * You may obtain a copy of the License at\n" + + " *\n" + + " * http://www.apache.org/licenses/LICENSE-2.0\n" + + " *\n" + + " * Unless required by applicable law or agreed to in writing, software\n" + + " * distributed under the License is distributed on an \"AS IS\" BASIS,\n" + + " * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" + + " * See the License for the specific language governing permissions and\n" + + " * limitations under the License.\n" + + " */"; + + private static final String PATH = new File("").getAbsolutePath(); + + public static void main(String[] args) { + StopWatch sw = StopWatch.begin(); + // 扫描文件 + List files = Files1.listFilesFilter(PATH, file -> file.isFile() + && (file.getName().endsWith(".java") || file.getName().endsWith(".java.vm")) + && !file.getAbsolutePath().contains("generated-sources") + && !file.getAbsolutePath().contains("node_modules"), true, false); + sw.tag("list"); + // 添加头 + files.forEach(AddLicenseHeader::addLicenseToFile); + sw.tag(" add"); + sw.stop(); + System.out.println(); + System.out.println(sw); + } + + /** + * 添加 license + * + * @param file file + */ + private static void addLicenseToFile(File file) { + String path = file.getAbsolutePath().substring(PATH.length()); + try { + String line = FileReaders.readLine(file); + if (line != null && line.trim().equals("/*")) { + System.out.println("Exists " + path); + return; + } + // 读取原始文件内容 + byte[] bytes = FileReaders.readAllBytesFast(file); + // 在头部添加许可证 + ByteArrayOutputStream out = new ByteArrayOutputStream(); + out.write(LICENSE.getBytes(StandardCharsets.UTF_8)); + out.write('\n'); + out.write(new String(bytes).replaceAll("\r\n", "\n").getBytes(StandardCharsets.UTF_8)); + // 写入 + FileWriters.writeFast(file, out.toByteArray()); + System.out.println("Added " + path); + } catch (IOException e) { + System.err.println("Failed " + path); + } + } + +} diff --git a/orion-ops-api/orion-ops-common/src/test/java/cn/orionsec/ops/constant/event/EventClassifyTest.java b/orion-ops-api/orion-ops-common/src/test/java/cn/orionsec/ops/constant/event/EventClassifyTest.java new file mode 100644 index 0000000..6275872 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/test/java/cn/orionsec/ops/constant/event/EventClassifyTest.java @@ -0,0 +1,37 @@ +package cn.orionsec.ops.constant.event; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.annotation.JSONField; +import com.alibaba.fastjson.serializer.SerializerFeature; +import cn.orionsec.kit.lang.utils.collect.Maps; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.junit.Test; + +import java.util.LinkedHashMap; + +public class EventClassifyTest { + + @Test + public void generatorEnumJson() { + EventClassify[] values = EventClassify.values(); + LinkedHashMap map = Maps.newLinkedMap(); + for (EventClassify value : values) { + map.put(value.name(), new EventClassifyJson(value.getClassify(), value.getLabel())); + } + System.out.println(JSONArray.toJSONString(map, SerializerFeature.PrettyFormat, SerializerFeature.MapSortField)); + } + + @Data + @AllArgsConstructor + static class EventClassifyJson { + + @JSONField(ordinal = 0) + private Integer value; + + @JSONField(ordinal = 1) + private String label; + + } + +} diff --git a/orion-ops-api/orion-ops-common/src/test/java/cn/orionsec/ops/constant/event/EventTypeTest.java b/orion-ops-api/orion-ops-common/src/test/java/cn/orionsec/ops/constant/event/EventTypeTest.java new file mode 100644 index 0000000..e1c2df1 --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/test/java/cn/orionsec/ops/constant/event/EventTypeTest.java @@ -0,0 +1,40 @@ +package cn.orionsec.ops.constant.event; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.annotation.JSONField; +import com.alibaba.fastjson.serializer.SerializerFeature; +import cn.orionsec.kit.lang.utils.collect.Maps; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.junit.Test; + +import java.util.LinkedHashMap; + +public class EventTypeTest { + + @Test + public void generatorEnumJson() { + EventType[] values = EventType.values(); + LinkedHashMap map = Maps.newLinkedMap(); + for (EventType value : values) { + map.put(value.name(), new EventTypeTest.EventTypeJson(value.getType(), value.getLabel(), value.getClassify().getClassify())); + } + System.out.println(JSONArray.toJSONString(map, SerializerFeature.PrettyFormat, SerializerFeature.MapSortField)); + } + + @Data + @AllArgsConstructor + static class EventTypeJson { + + @JSONField(ordinal = 0) + private Integer value; + + @JSONField(ordinal = 1) + private String label; + + @JSONField(ordinal = 2) + private Integer classify; + + } + +} diff --git a/orion-ops-api/orion-ops-common/src/test/java/cn/orionsec/ops/constant/event/ReplaceVersion.java b/orion-ops-api/orion-ops-common/src/test/java/cn/orionsec/ops/constant/event/ReplaceVersion.java new file mode 100644 index 0000000..eb594bf --- /dev/null +++ b/orion-ops-api/orion-ops-common/src/test/java/cn/orionsec/ops/constant/event/ReplaceVersion.java @@ -0,0 +1,103 @@ +package cn.orionsec.ops.constant.event; + +import cn.orionsec.kit.lang.utils.io.FileReaders; +import cn.orionsec.kit.lang.utils.io.FileWriters; +import cn.orionsec.kit.lang.utils.io.Files1; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.function.Function; + +public class ReplaceVersion { + + private static final String TARGET_VERSION = "1.3.1"; + + private static final String REPLACE_VERSION = "1.3.2"; + + private static final String PATH = new File("").getAbsolutePath(); + + private static final String[] DOCKER_FILES = new String[]{ + "docker/push.sh", + "docker/adminer/build.sh", + "docker/adminer/build.sh", + "docker/mysql/build.sh", + "docker/redis/build.sh", + "docker/service/build.sh", + "docker-compose.yml", + }; + + private static final String[] POM_FILES = new String[]{ + "orion-ops-api/pom.xml", + "orion-ops-api/orion-ops-common/pom.xml", + "orion-ops-api/orion-ops-dao/pom.xml", + "orion-ops-api/orion-ops-data/pom.xml", + "orion-ops-api/orion-ops-mapping/pom.xml", + "orion-ops-api/orion-ops-model/pom.xml", + "orion-ops-api/orion-ops-runner/pom.xml", + "orion-ops-api/orion-ops-service/pom.xml", + "orion-ops-api/orion-ops-web/pom.xml", + }; + + private static final String APPLICATION_PROP_FILE = "orion-ops-api/orion-ops-web/src/main/resources/application.properties"; + + private static final String PACKAGE_JSON_FILE = "orion-ops-vue/package.json"; + + public static void main(String[] args) { + replaceDockerFiles(); + replacePomFiles(); + replaceApplicationProp(); + replacePackageJson(); + } + + /** + * 替换 docker 文件 + */ + private static void replaceDockerFiles() { + for (String file : DOCKER_FILES) { + readAndWrite(file, s -> s.replaceAll(TARGET_VERSION, REPLACE_VERSION)); + } + } + + /** + * 替换 pom 文件 + */ + private static void replacePomFiles() { + for (String file : POM_FILES) { + readAndWrite(file, s -> s.replaceAll("" + TARGET_VERSION + "", "" + REPLACE_VERSION + "")); + } + } + + /** + * 替换 application.properties 文件 + */ + private static void replaceApplicationProp() { + readAndWrite(APPLICATION_PROP_FILE, s -> s.replaceAll("app.version=" + TARGET_VERSION, "app.version=" + REPLACE_VERSION)); + } + + /** + * 替换 package.json 文件 + */ + private static void replacePackageJson() { + readAndWrite(PACKAGE_JSON_FILE, s -> s.replaceAll("\"version\": \"" + TARGET_VERSION + "\"", "\"version\": \"" + REPLACE_VERSION + "\"")); + } + + /** + * 读取并且写入 + * + * @param path path + * @param mapping mapping + */ + private static void readAndWrite(String path, Function mapping) { + String filePath = Files1.getPath(PATH, path); + try { + // 读取文件内容 + byte[] bytes = FileReaders.readAllBytesFast(filePath); + // 写入文件内容 + FileWriters.writeFast(filePath, mapping.apply(new String(bytes)).getBytes(StandardCharsets.UTF_8)); + System.out.println("OK: " + path); + } catch (Exception e) { + System.err.println("ERR: " + path); + } + } + +} diff --git a/orion-ops-api/orion-ops-dao/pom.xml b/orion-ops-api/orion-ops-dao/pom.xml new file mode 100644 index 0000000..eb7a9c3 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/pom.xml @@ -0,0 +1,68 @@ + + + + cn.orionsec.ops + orion-ops-api + 1.3.1 + ../pom.xml + + + 4.0.0 + orion-ops-dao + orion-ops-dao + + + + + org.projectlombok + lombok + + + + + io.springfox + springfox-swagger2 + + + + + org.aspectj + aspectjweaver + + + + + com.baomidou + mybatis-plus-boot-starter + + + + + com.baomidou + mybatis-plus-generator + + + org.apache.velocity + velocity-engine-core + + + + + mysql + mysql-connector-java + runtime + + + com.alibaba + druid-spring-boot-starter + + + + + cn.orionsec.kit + orion-all + + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/AlarmGroupDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/AlarmGroupDAO.java new file mode 100644 index 0000000..3f48da9 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/AlarmGroupDAO.java @@ -0,0 +1,9 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.AlarmGroupDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + + +public interface AlarmGroupDAO extends BaseMapper { + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/AlarmGroupNotifyDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/AlarmGroupNotifyDAO.java new file mode 100644 index 0000000..a539dc4 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/AlarmGroupNotifyDAO.java @@ -0,0 +1,9 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.AlarmGroupNotifyDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + + +public interface AlarmGroupNotifyDAO extends BaseMapper { + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/AlarmGroupUserDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/AlarmGroupUserDAO.java new file mode 100644 index 0000000..079a032 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/AlarmGroupUserDAO.java @@ -0,0 +1,8 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.AlarmGroupUserDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface AlarmGroupUserDAO extends BaseMapper { + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationActionDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationActionDAO.java new file mode 100644 index 0000000..8c6c5b6 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationActionDAO.java @@ -0,0 +1,21 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.ApplicationActionDO; +import cn.orionsec.ops.entity.dto.ApplicationActionConfigDTO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface ApplicationActionDAO extends BaseMapper { + + /** + * 获取应用是否已配置 + * + * @param profileId profileId + * @param appIdList appIdList + * @return rows + */ + List getAppIsConfig(@Param("profileId") Long profileId, @Param("appIdList") List appIdList); + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationActionLogDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationActionLogDAO.java new file mode 100644 index 0000000..ebbf01b --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationActionLogDAO.java @@ -0,0 +1,45 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.ApplicationActionLogDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface ApplicationActionLogDAO extends BaseMapper { + + /** + * 通过 id 查询状态信息 + * + * @param id id + * @return row + */ + ApplicationActionLogDO selectStatusInfoById(@Param("id") Long id); + + /** + * 通过 id 查询状态信息 + * + * @param idList idList + * @return rows + */ + List selectStatusInfoByIdList(@Param("idList") List idList); + + /** + * 通过 relId 查询状态信息 + * + * @param relId relId + * @param stageType stageType + * @return rows + */ + List selectStatusInfoByRelId(@Param("relId") Long relId, @Param("stageType") Integer stageType); + + /** + * 通过 relId 查询状态信息 + * + * @param relIdList relIdList + * @param stageType stageType + * @return rows + */ + List selectStatusInfoByRelIdList(@Param("relIdList") List relIdList, @Param("stageType") Integer stageType); + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationBuildDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationBuildDAO.java new file mode 100644 index 0000000..446852d --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationBuildDAO.java @@ -0,0 +1,94 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.ApplicationBuildDO; +import cn.orionsec.ops.entity.dto.ApplicationBuildStatisticsDTO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.Date; +import java.util.List; + +public interface ApplicationBuildDAO extends BaseMapper { + + /** + * 通过 id 查询状态 + * + * @param id id + * @return status + */ + Integer selectStatusById(@Param("id") Long id); + + /** + * 通过 id 查询状态信息 + * + * @param id id + * @return row + */ + ApplicationBuildDO selectStatusInfoById(@Param("id") Long id); + + /** + * 通过 id 查询状态信息 + * + * @param idList idList + * @return rows + */ + List selectStatusInfoByIdList(@Param("idList") List idList); + + /** + * 查询上一次构建分支 + * + * @param appId appId + * @param profileId profileId + * @param repoId repoId + * @return branch + */ + String selectLastBuildBranch(@Param("appId") Long appId, @Param("profileId") Long profileId, @Param("repoId") Long repoId); + + /** + * 查询构建序列 + * + * @param id id + * @return seq + */ + Integer selectBuildSeq(@Param("id") Long id); + + /** + * 查询构建发布列表 + * + * @param appId appId + * @param profileId profileId + * @param limit limit + * @return rows + */ + List selectBuildReleaseList(@Param("appId") Long appId, @Param("profileId") Long profileId, @Param("limit") Integer limit); + + /** + * 查询构建发布列表 + * + * @param appIdList appIdList + * @param profileId profileId + * @return rows + */ + List selectBuildReleaseGroupList(@Param("appIdList") List appIdList, @Param("profileId") Long profileId); + + /** + * 获取构建统计 + * + * @param appId appId + * @param profileId profileId + * @param rangeStartDate rangeStartDate + * @return 统计信息 + */ + ApplicationBuildStatisticsDTO getBuildStatistics(@Param("appId") Long appId, @Param("profileId") Long profileId, @Param("rangeStartDate") Date rangeStartDate); + + /** + * 获取构建时间线统计 + * + * @param appId appId + * @param profileId profileId + * @param rangeStartDate rangeStartDate + * @return 时间线统计信息 + */ + List getBuildDateStatistics(@Param("appId") Long appId, @Param("profileId") Long profileId, @Param("rangeStartDate") Date rangeStartDate); + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationEnvDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationEnvDAO.java new file mode 100644 index 0000000..c07db5b --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationEnvDAO.java @@ -0,0 +1,28 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.ApplicationEnvDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +public interface ApplicationEnvDAO extends BaseMapper { + + /** + * 查询一条数据 + * + * @param appId appId + * @param profileId profileId + * @param key key + * @return env + */ + ApplicationEnvDO selectOneRel(@Param("appId") Long appId, @Param("profileId") Long profileId, @Param("key") String key); + + /** + * 设置删除 + * + * @param id id + * @param deleted deleted + * @return effect + */ + Integer setDeleted(@Param("id") Long id, @Param("deleted") Integer deleted); + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationInfoDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationInfoDAO.java new file mode 100644 index 0000000..19ce651 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationInfoDAO.java @@ -0,0 +1,43 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.ApplicationInfoDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface ApplicationInfoDAO extends BaseMapper { + + /** + * 获取仓库数量 + * + * @param repoId repoId + * @return count + */ + Integer selectRepoCount(@Param("repoId") Long repoId); + + /** + * 清空仓库 + * + * @param repoId repoId + * @return effect + */ + Integer cleanRepoCount(@Param("repoId") Long repoId); + + /** + * 查询名称 + * + * @param id id + * @return name + */ + String selectNameById(@Param("id") Long id); + + /** + * 查询名称 + * + * @param idList idList + * @return name + */ + List selectNameByIdList(@Param("idList") List idList); + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationMachineDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationMachineDAO.java new file mode 100644 index 0000000..e83dcf8 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationMachineDAO.java @@ -0,0 +1,16 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.ApplicationMachineDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface ApplicationMachineDAO extends BaseMapper { + + /** + * 更新版本 + * + * @param update update + * @return effect + */ + Integer updateAppVersion(ApplicationMachineDO update); + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationPipelineDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationPipelineDAO.java new file mode 100644 index 0000000..fe4fb4e --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationPipelineDAO.java @@ -0,0 +1,8 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.ApplicationPipelineDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface ApplicationPipelineDAO extends BaseMapper { + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationPipelineDetailDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationPipelineDetailDAO.java new file mode 100644 index 0000000..7228fdf --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationPipelineDetailDAO.java @@ -0,0 +1,8 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.ApplicationPipelineDetailDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface ApplicationPipelineDetailDAO extends BaseMapper { + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationPipelineTaskDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationPipelineTaskDAO.java new file mode 100644 index 0000000..bdd83fc --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationPipelineTaskDAO.java @@ -0,0 +1,55 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.ApplicationPipelineTaskDO; +import cn.orionsec.ops.entity.dto.ApplicationPipelineTaskStatisticsDTO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.Date; +import java.util.List; + +public interface ApplicationPipelineTaskDAO extends BaseMapper { + + /** + * 设置定时执行时间为空 + * + * @param id id + * @return effect + */ + Integer setTimedExecTimeNull(@Param("id") Long id); + + /** + * 查询任务状态 + * + * @param id id + * @return row + */ + ApplicationPipelineTaskDO selectStatusById(@Param("id") Long id); + + /** + * 查询任务状态 + * + * @param idList idList + * @return rows + */ + List selectStatusByIdList(@Param("idList") List idList); + + /** + * 获取流水线任务统计 + * + * @param pipelineId pipelineId + * @param rangeStartDate rangeStartDate + * @return 统计信息 + */ + ApplicationPipelineTaskStatisticsDTO getPipelineTaskStatistics(@Param("pipelineId") Long pipelineId, @Param("rangeStartDate") Date rangeStartDate); + + /** + * 获取流水线任务时间线统计 + * + * @param pipelineId pipelineId + * @param rangeStartDate rangeStartDate + * @return 时间线统计信息 + */ + List getPipelineTaskDateStatistics(@Param("pipelineId") Long pipelineId, @Param("rangeStartDate") Date rangeStartDate); + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationPipelineTaskDetailDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationPipelineTaskDetailDAO.java new file mode 100644 index 0000000..9d0d455 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationPipelineTaskDetailDAO.java @@ -0,0 +1,27 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.ApplicationPipelineTaskDetailDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface ApplicationPipelineTaskDetailDAO extends BaseMapper { + + /** + * 查询任务详情状态 + * + * @param taskId taskId + * @return rows + */ + List selectStatusByTaskId(@Param("taskId") Long taskId); + + /** + * 查询任务详情状态 + * + * @param idList idList + * @return rows + */ + List selectStatusByIdList(@Param("idList") List idList); + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationPipelineTaskLogDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationPipelineTaskLogDAO.java new file mode 100644 index 0000000..99ff5e1 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationPipelineTaskLogDAO.java @@ -0,0 +1,8 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.ApplicationPipelineTaskLogDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface ApplicationPipelineTaskLogDAO extends BaseMapper { + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationProfileDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationProfileDAO.java new file mode 100644 index 0000000..d9ed63d --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationProfileDAO.java @@ -0,0 +1,8 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.ApplicationProfileDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface ApplicationProfileDAO extends BaseMapper { + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationReleaseDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationReleaseDAO.java new file mode 100644 index 0000000..1a15b03 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationReleaseDAO.java @@ -0,0 +1,57 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.ApplicationReleaseDO; +import cn.orionsec.ops.entity.dto.ApplicationReleaseStatisticsDTO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.Date; +import java.util.List; + +public interface ApplicationReleaseDAO extends BaseMapper { + + /** + * 查询状态 + * + * @param id id + * @return row + */ + ApplicationReleaseDO selectStatusById(@Param("id") Long id); + + /** + * 查询状态列表 + * + * @param idList idList + * @return rows + */ + List selectStatusByIdList(@Param("idList") List idList); + + /** + * 设置定时时间为 null + * + * @param id id + * @return effect + */ + Integer setTimedReleaseTimeNull(@Param("id") Long id); + + /** + * 获取发布统计 + * + * @param appId appId + * @param profileId profileId + * @param rangeStartDate rangeStartDate + * @return 统计信息 + */ + ApplicationReleaseStatisticsDTO getReleaseStatistics(@Param("appId") Long appId, @Param("profileId") Long profileId, @Param("rangeStartDate") Date rangeStartDate); + + /** + * 获取发布时间线统计 + * + * @param appId appId + * @param profileId profileId + * @param rangeStartDate rangeStartDate + * @return 时间线统计信息 + */ + List getReleaseDateStatistics(@Param("appId") Long appId, @Param("profileId") Long profileId, @Param("rangeStartDate") Date rangeStartDate); + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationReleaseMachineDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationReleaseMachineDAO.java new file mode 100644 index 0000000..24daf9a --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationReleaseMachineDAO.java @@ -0,0 +1,43 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.ApplicationReleaseMachineDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface ApplicationReleaseMachineDAO extends BaseMapper { + + /** + * 查询发布机器状态 + * + * @param releaseId releaseId + * @return rows + */ + List selectStatusByReleaseId(@Param("releaseId") Long releaseId); + + /** + * 查询发布机器状态 + * + * @param releaseIdList releaseIdList + * @return rows + */ + List selectStatusByReleaseIdList(@Param("releaseIdList") List releaseIdList); + + /** + * 查询发布机器状态 + * + * @param id id + * @return row + */ + ApplicationReleaseMachineDO selectStatusById(@Param("id") Long id); + + /** + * 查询发布机器状态 + * + * @param idList idList + * @return rows + */ + List selectStatusByIdList(@Param("idList") List idList); + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationRepositoryDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationRepositoryDAO.java new file mode 100644 index 0000000..32ddd70 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/ApplicationRepositoryDAO.java @@ -0,0 +1,27 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.ApplicationRepositoryDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface ApplicationRepositoryDAO extends BaseMapper { + + /** + * 通过id查询名称 + * + * @param idList idList + * @return rows + */ + List selectNameByIdList(@Param("idList") List idList); + + /** + * 通过名称查询id + * + * @param nameList nameList + * @return rows + */ + List selectIdByNameList(@Param("nameList") List nameList); + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/CommandExecDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/CommandExecDAO.java new file mode 100644 index 0000000..6937de8 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/CommandExecDAO.java @@ -0,0 +1,17 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.CommandExecDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +public interface CommandExecDAO extends BaseMapper { + + /** + * 通过 id 查询 status + * + * @param id id + * @return status + */ + CommandExecDO selectStatusById(@Param("id") Long id); + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/CommandTemplateDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/CommandTemplateDAO.java new file mode 100644 index 0000000..fadac4f --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/CommandTemplateDAO.java @@ -0,0 +1,8 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.CommandTemplateDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface CommandTemplateDAO extends BaseMapper { + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/FileTailListDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/FileTailListDAO.java new file mode 100644 index 0000000..7f32a0b --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/FileTailListDAO.java @@ -0,0 +1,8 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.FileTailListDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface FileTailListDAO extends BaseMapper { + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/FileTransferLogDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/FileTransferLogDAO.java new file mode 100644 index 0000000..06e0369 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/FileTransferLogDAO.java @@ -0,0 +1,8 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.FileTransferLogDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface FileTransferLogDAO extends BaseMapper { + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/HistoryValueSnapshotDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/HistoryValueSnapshotDAO.java new file mode 100644 index 0000000..243484e --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/HistoryValueSnapshotDAO.java @@ -0,0 +1,8 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.HistoryValueSnapshotDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface HistoryValueSnapshotDAO extends BaseMapper { + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineAlarmConfigDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineAlarmConfigDAO.java new file mode 100644 index 0000000..df2dd34 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineAlarmConfigDAO.java @@ -0,0 +1,8 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.MachineAlarmConfigDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface MachineAlarmConfigDAO extends BaseMapper { + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineAlarmGroupDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineAlarmGroupDAO.java new file mode 100644 index 0000000..8d90f69 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineAlarmGroupDAO.java @@ -0,0 +1,8 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.MachineAlarmGroupDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface MachineAlarmGroupDAO extends BaseMapper { + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineAlarmHistoryDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineAlarmHistoryDAO.java new file mode 100644 index 0000000..5b465fc --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineAlarmHistoryDAO.java @@ -0,0 +1,8 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.MachineAlarmHistoryDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface MachineAlarmHistoryDAO extends BaseMapper { + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineEnvDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineEnvDAO.java new file mode 100644 index 0000000..c173bfb --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineEnvDAO.java @@ -0,0 +1,27 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.MachineEnvDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +public interface MachineEnvDAO extends BaseMapper { + + /** + * 查询一条数据 + * + * @param machineId machineId + * @param key key + * @return env + */ + MachineEnvDO selectOneRel(@Param("machineId") Long machineId, @Param("key") String key); + + /** + * 设置删除 + * + * @param id id + * @param deleted deleted + * @return effect + */ + Integer setDeleted(@Param("id") Long id, @Param("deleted") Integer deleted); + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineGroupDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineGroupDAO.java new file mode 100644 index 0000000..000fceb --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineGroupDAO.java @@ -0,0 +1,26 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.MachineGroupDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +public interface MachineGroupDAO extends BaseMapper { + + /** + * 增加排序值 + * + * @param parentId parentId + * @param greaterSort > sort + * @return effect + */ + Integer incrementSort(@Param("parentId") Long parentId, @Param("greaterSort") Integer greaterSort); + + /** + * 增加最大 sort + * + * @param parentId parentId + * @return sort + */ + Integer getMaxSort(@Param("parentId") Long parentId); + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineGroupRelDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineGroupRelDAO.java new file mode 100644 index 0000000..c0c217d --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineGroupRelDAO.java @@ -0,0 +1,8 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.MachineGroupRelDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface MachineGroupRelDAO extends BaseMapper { + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineInfoDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineInfoDAO.java new file mode 100644 index 0000000..766d6c7 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineInfoDAO.java @@ -0,0 +1,58 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface MachineInfoDAO extends BaseMapper { + + /** + * 设置id + * + * @param oldId 原id + * @param newId 新id + */ + void setId(@Param("oldId") Long oldId, @Param("newId") Long newId); + + /** + * 设置proxyId为null + * + * @param proxyId proxyId + */ + void setProxyIdWithNull(@Param("proxyId") Long proxyId); + + /** + * 通过host查询id + * + * @param host host + * @return rows + */ + List selectIdByHost(@Param("host") String host); + + /** + * 查询机器名称 + * + * @param id id + * @return name + */ + String selectMachineName(@Param("id") Long id); + + /** + * 查询机器名称 + * + * @param idList idList + * @return rows + */ + List selectNameByIdList(@Param("idList") List idList); + + /** + * 查询机器id + * + * @param tagList tagList + * @return rows + */ + List selectIdByTagList(@Param("tagList") List tagList); + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineMonitorDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineMonitorDAO.java new file mode 100644 index 0000000..c172c8a --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineMonitorDAO.java @@ -0,0 +1,30 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.MachineMonitorDO; +import cn.orionsec.ops.entity.dto.MachineMonitorDTO; +import cn.orionsec.ops.entity.query.MachineMonitorQuery; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface MachineMonitorDAO extends BaseMapper { + + /** + * 查询列表 + * + * @param query query + * @param last last sql + * @return rows + */ + List selectMonitorList(@Param("query") MachineMonitorQuery query, @Param("last") String last); + + /** + * 查询条数 + * + * @param query query + * @return count + */ + Integer selectMonitorCount(@Param("query") MachineMonitorQuery query); + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineProxyDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineProxyDAO.java new file mode 100644 index 0000000..18c6f91 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineProxyDAO.java @@ -0,0 +1,8 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.MachineProxyDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface MachineProxyDAO extends BaseMapper { + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineSecretKeyDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineSecretKeyDAO.java new file mode 100644 index 0000000..c904ae1 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineSecretKeyDAO.java @@ -0,0 +1,19 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.MachineSecretKeyDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface MachineSecretKeyDAO extends BaseMapper { + + /** + * 通过名称查询id + * + * @param nameList nameList + * @return rows + */ + List selectIdByNameList(@Param("nameList") List nameList); + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineTerminalDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineTerminalDAO.java new file mode 100644 index 0000000..407f249 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineTerminalDAO.java @@ -0,0 +1,8 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.MachineTerminalDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface MachineTerminalDAO extends BaseMapper { + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineTerminalLogDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineTerminalLogDAO.java new file mode 100644 index 0000000..3e04af1 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/MachineTerminalLogDAO.java @@ -0,0 +1,8 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.MachineTerminalLogDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface MachineTerminalLogDAO extends BaseMapper { + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/SchedulerTaskDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/SchedulerTaskDAO.java new file mode 100644 index 0000000..49bfb59 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/SchedulerTaskDAO.java @@ -0,0 +1,8 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.SchedulerTaskDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface SchedulerTaskDAO extends BaseMapper { + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/SchedulerTaskMachineDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/SchedulerTaskMachineDAO.java new file mode 100644 index 0000000..82333a1 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/SchedulerTaskMachineDAO.java @@ -0,0 +1,8 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.SchedulerTaskMachineDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface SchedulerTaskMachineDAO extends BaseMapper { + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/SchedulerTaskMachineRecordDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/SchedulerTaskMachineRecordDAO.java new file mode 100644 index 0000000..de389c7 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/SchedulerTaskMachineRecordDAO.java @@ -0,0 +1,43 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.SchedulerTaskMachineRecordDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface SchedulerTaskMachineRecordDAO extends BaseMapper { + + /** + * 通过 recordId 查询状态 + * + * @param recordId recordId + * @return rows + */ + List selectStatusByRecordId(@Param("recordId") Long recordId); + + /** + * 通过 idList 查询状态 + * + * @param idList idList + * @return rows + */ + List selectStatusByIdList(@Param("idList") List idList); + + /** + * 通过 recordIdList 查询状态 + * + * @param recordIdList recordIdList + * @return rows + */ + List selectStatusByRecordIdList(@Param("recordIdList") List recordIdList); + + /** + * 通过 recordIdList 删除明细 + * + * @param recordIdList recordIdList + * @return effect + */ + Integer deleteByRecordIdList(@Param("recordIdList") List recordIdList); + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/SchedulerTaskRecordDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/SchedulerTaskRecordDAO.java new file mode 100644 index 0000000..9ad6803 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/SchedulerTaskRecordDAO.java @@ -0,0 +1,47 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.SchedulerTaskRecordDO; +import cn.orionsec.ops.entity.dto.SchedulerTaskRecordStatisticsDTO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.Date; +import java.util.List; + +public interface SchedulerTaskRecordDAO extends BaseMapper { + + /** + * 查询任务状态 + * + * @param idList idList + * @return rows + */ + List selectTaskStatusByIdList(@Param("idList") List idList); + + /** + * 获取调度任务统计 + * + * @param taskId taskId + * @param rangeStartDate 统计开始时间 + * @return 统计信息 + */ + SchedulerTaskRecordStatisticsDTO getTaskRecordStatistics(@Param("taskId") Long taskId, @Param("rangeStartDate") Date rangeStartDate); + + /** + * 获取调度任务机器统计 + * + * @param taskId taskId + * @return 统计信息 + */ + List getTaskMachineRecordStatistics(@Param("taskId") Long taskId); + + /** + * 获取调度任务时间线统计 + * + * @param taskId taskId + * @param rangeStartDate 统计开始时间 + * @return 统计信息 + */ + List getTaskRecordDateStatistics(@Param("taskId") Long taskId, @Param("rangeStartDate") Date rangeStartDate); + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/SystemEnvDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/SystemEnvDAO.java new file mode 100644 index 0000000..6e478cf --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/SystemEnvDAO.java @@ -0,0 +1,26 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.SystemEnvDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +public interface SystemEnvDAO extends BaseMapper { + + /** + * 查询一条数据 + * + * @param key key + * @return env + */ + SystemEnvDO selectOneRel(@Param("key") String key); + + /** + * 设置删除 + * + * @param id id + * @param deleted deleted + * @return effect + */ + Integer setDeleted(@Param("id") Long id, @Param("deleted") Integer deleted); + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/UserEventLogDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/UserEventLogDAO.java new file mode 100644 index 0000000..54ecc8f --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/UserEventLogDAO.java @@ -0,0 +1,8 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.UserEventLogDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface UserEventLogDAO extends BaseMapper { + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/UserInfoDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/UserInfoDAO.java new file mode 100644 index 0000000..3b535f1 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/UserInfoDAO.java @@ -0,0 +1,17 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.UserInfoDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +public interface UserInfoDAO extends BaseMapper { + + /** + * 更新最后登录时间 + * + * @param userId userId + * @return effect + */ + Integer updateLastLoginTime(@Param("userId") Long userId); + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/WebSideMessageDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/WebSideMessageDAO.java new file mode 100644 index 0000000..430093f --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/WebSideMessageDAO.java @@ -0,0 +1,8 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.WebSideMessageDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface WebSideMessageDAO extends BaseMapper { + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/WebhookConfigDAO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/WebhookConfigDAO.java new file mode 100644 index 0000000..1585a00 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/dao/WebhookConfigDAO.java @@ -0,0 +1,8 @@ +package cn.orionsec.ops.dao; + +import cn.orionsec.ops.entity.domain.WebhookConfigDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface WebhookConfigDAO extends BaseMapper { + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/AlarmGroupDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/AlarmGroupDO.java new file mode 100644 index 0000000..8e7a7b9 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/AlarmGroupDO.java @@ -0,0 +1,45 @@ +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + + +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("alarm_group") +@ApiModel(value = "AlarmGroupDO对象", description = "报警组") +public class AlarmGroupDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "报警组名称") + @TableField("group_name") + private String groupName; + + @ApiModelProperty(value = "报警组描述") + @TableField("group_description") + private String groupDescription; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/AlarmGroupNotifyDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/AlarmGroupNotifyDO.java new file mode 100644 index 0000000..25de469 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/AlarmGroupNotifyDO.java @@ -0,0 +1,48 @@ +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("alarm_group_notify") +@ApiModel(value = "AlarmGroupNotifyDO对象", description = "报警组通知方式") +public class AlarmGroupNotifyDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "报警组id") + @TableField("group_id") + private Long groupId; + + @ApiModelProperty(value = "通知id") + @TableField("notify_id") + private Long notifyId; + + @ApiModelProperty(value = "通知类型 10 webhook") + @TableField("notify_type") + private Integer notifyType; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/AlarmGroupUserDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/AlarmGroupUserDO.java new file mode 100644 index 0000000..35f6c7c --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/AlarmGroupUserDO.java @@ -0,0 +1,48 @@ +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("alarm_group_user") +@ApiModel(value = "AlarmGroupUserDO对象", description = "报警组成员") +public class AlarmGroupUserDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "报警组id") + @TableField("group_id") + private Long groupId; + + @ApiModelProperty(value = "报警组成员id") + @TableField("user_id") + private Long userId; + + @ApiModelProperty(value = "报警组成员用户名") + @TableField("username") + private String username; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationActionDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationActionDO.java new file mode 100644 index 0000000..080643b --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationActionDO.java @@ -0,0 +1,67 @@ +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "应用发布执行块") +@TableName("application_action") +@SuppressWarnings("ALL") +public class ApplicationActionDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "appId") + @TableField("app_id") + private Long appId; + + @ApiModelProperty(value = "profileId") + @TableField("profile_id") + private Long profileId; + + @ApiModelProperty(value = "名称") + @TableField("action_name") + private String actionName; + + /** + * @see cn.orionsec.ops.constant.app.ActionType + */ + @ApiModelProperty(value = "类型") + @TableField("action_type") + private Integer actionType; + + /** + * @see cn.orionsec.ops.constant.app.StageType + */ + @ApiModelProperty(value = "阶段类型") + @TableField("stage_type") + private Integer stageType; + + @ApiModelProperty(value = "执行命令") + @TableField("action_command") + private String actionCommand; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationActionLogDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationActionLogDO.java new file mode 100644 index 0000000..ba6eb9b --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationActionLogDO.java @@ -0,0 +1,94 @@ +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "应用操作日志") +@TableName("application_action_log") +@SuppressWarnings("ALL") +public class ApplicationActionLogDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * @see cn.orionsec.ops.constant.app.StageType + */ + @ApiModelProperty(value = "阶段类型 10构建 20发布") + @TableField("stage_type") + private Integer stageType; + + @ApiModelProperty(value = "引用id 构建id 发布机器id") + @TableField("rel_id") + private Long relId; + + @ApiModelProperty(value = "执行机器id") + @TableField("machine_id") + private Long machineId; + + @ApiModelProperty(value = "操作id") + @TableField("action_id") + private Long actionId; + + @ApiModelProperty(value = "操作名称") + @TableField("action_name") + private String actionName; + + /** + * @see cn.orionsec.ops.constant.app.ActionType + */ + @ApiModelProperty(value = "操作类型") + @TableField("action_type") + private Integer actionType; + + @ApiModelProperty(value = "操作命令") + @TableField("action_command") + private String actionCommand; + + @ApiModelProperty(value = "操作日志路径") + @TableField("log_path") + private String logPath; + + /** + * @see cn.orionsec.ops.constant.app.ActionStatus + */ + @ApiModelProperty(value = "状态 10未开始 20进行中 30已完成 40执行失败 50已跳过 60已取消") + @TableField("run_status") + private Integer runStatus; + + @ApiModelProperty(value = "退出码") + @TableField("exit_code") + private Integer exitCode; + + @ApiModelProperty(value = "开始时间") + @TableField("start_time") + private Date startTime; + + @ApiModelProperty(value = "结束时间") + @TableField("end_time") + private Date endTime; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationBuildDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationBuildDO.java new file mode 100644 index 0000000..67e46dc --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationBuildDO.java @@ -0,0 +1,112 @@ +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "应用构建") +@TableName("application_build") +@SuppressWarnings("ALL") +public class ApplicationBuildDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "应用id") + @TableField("app_id") + private Long appId; + + @ApiModelProperty(value = "应用名称") + @TableField("app_name") + private String appName; + + @ApiModelProperty(value = "应用唯一标识") + @TableField("app_tag") + private String appTag; + + @ApiModelProperty(value = "环境id") + @TableField("profile_id") + private Long profileId; + + @ApiModelProperty(value = "环境名称") + @TableField("profile_name") + private String profileName; + + @ApiModelProperty(value = "环境唯一标识") + @TableField("profile_tag") + private String profileTag; + + @ApiModelProperty(value = "构建序列") + @TableField("build_seq") + private Integer buildSeq; + + @ApiModelProperty(value = "构建分支") + @TableField("branch_name") + private String branchName; + + @ApiModelProperty(value = "构建提交id") + @TableField("commit_id") + private String commitId; + + @ApiModelProperty(value = "应用版本仓库id") + @TableField("repo_id") + private Long repoId; + + @ApiModelProperty(value = "构建日志路径") + @TableField("log_path") + private String logPath; + + @ApiModelProperty(value = "构建产物文件") + @TableField("bundle_path") + private String bundlePath; + + /** + * @see cn.orionsec.ops.constant.app.BuildStatus + */ + @ApiModelProperty(value = "状态 10未开始 20执行中 30已完成 40执行失败 50已取消") + @TableField("build_status") + private Integer buildStatus; + + @ApiModelProperty(value = "描述") + @TableField("description") + private String description; + + @ApiModelProperty(value = "创建人id") + @TableField("create_user_id") + private Long createUserId; + + @ApiModelProperty(value = "创建人名称") + @TableField("create_user_name") + private String createUserName; + + @ApiModelProperty(value = "构建开始时间") + @TableField("build_start_time") + private Date buildStartTime; + + @ApiModelProperty(value = "构建结束时间") + @TableField("build_end_time") + private Date buildEndTime; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationEnvDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationEnvDO.java new file mode 100644 index 0000000..2dfae7c --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationEnvDO.java @@ -0,0 +1,65 @@ +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "应用环境变量") +@TableName("application_env") +@SuppressWarnings("ALL") +public class ApplicationEnvDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "应用id") + @TableField("app_id") + private Long appId; + + @ApiModelProperty(value = "环境id") + @TableField("profile_id") + private Long profileId; + + @ApiModelProperty(value = "key") + @TableField("attr_key") + private String attrKey; + + @ApiModelProperty(value = "value") + @TableField("attr_value") + private String attrValue; + + /** + * @see cn.orionsec.ops.constant.Const#IS_SYSTEM + * @see cn.orionsec.ops.constant.Const#NOT_SYSTEM + */ + @ApiModelProperty(value = "是否为系统变量 1是 2否") + @TableField("system_env") + private Integer systemEnv; + + @ApiModelProperty(value = "描述") + @TableField("description") + private String description; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationInfoDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationInfoDO.java new file mode 100644 index 0000000..050fe1a --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationInfoDO.java @@ -0,0 +1,56 @@ +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "应用表") +@TableName("application_info") +public class ApplicationInfoDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "名称") + @TableField("app_name") + private String appName; + + @ApiModelProperty(value = "应用唯一标识") + @TableField("app_tag") + private String appTag; + + @ApiModelProperty(value = "排序") + @TableField("app_sort") + private Integer appSort; + + @ApiModelProperty(value = "应用版本仓库id") + @TableField(value = "repo_id", updateStrategy = FieldStrategy.IGNORED) + private Long repoId; + + @ApiModelProperty(value = "描述") + @TableField("description") + private String description; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationMachineDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationMachineDO.java new file mode 100644 index 0000000..ac12b5e --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationMachineDO.java @@ -0,0 +1,60 @@ +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "应用依赖机器表") +@TableName("application_machine") +public class ApplicationMachineDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "应用id") + @TableField("app_id") + private Long appId; + + @ApiModelProperty(value = "环境id") + @TableField("profile_id") + private Long profileId; + + @ApiModelProperty(value = "机器id") + @TableField("machine_id") + private Long machineId; + + @ApiModelProperty(value = "当前版本发布id") + @TableField("release_id") + private Long releaseId; + + @ApiModelProperty(value = "当前版本构建id") + @TableField("build_id") + private Long buildId; + + @ApiModelProperty(value = "当前版本构建序列") + @TableField("build_seq") + private Integer buildSeq; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationPipelineDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationPipelineDO.java new file mode 100644 index 0000000..0a006d7 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationPipelineDO.java @@ -0,0 +1,48 @@ +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "应用流水线") +@TableName("application_pipeline") +public class ApplicationPipelineDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "环境id") + @TableField("profile_id") + private Long profileId; + + @ApiModelProperty(value = "流水线名称") + @TableField("pipeline_name") + private String pipelineName; + + @ApiModelProperty(value = "描述") + @TableField("description") + private String description; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "修改时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "创建时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationPipelineDetailDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationPipelineDetailDO.java new file mode 100644 index 0000000..71638f6 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationPipelineDetailDO.java @@ -0,0 +1,56 @@ +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "应用流水线详情") +@TableName("application_pipeline_detail") +@SuppressWarnings("ALL") +public class ApplicationPipelineDetailDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "流水线id") + @TableField("pipeline_id") + private Long pipelineId; + + @ApiModelProperty(value = "应用id") + @TableField("app_id") + private Long appId; + + @ApiModelProperty(value = "环境id") + @TableField("profile_id") + private Long profileId; + + /** + * @see cn.orionsec.ops.constant.app.StageType + */ + @ApiModelProperty(value = "阶段类型 10构建 20发布") + @TableField("stage_type") + private Integer stageType; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationPipelineTaskDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationPipelineTaskDO.java new file mode 100644 index 0000000..5af4242 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationPipelineTaskDO.java @@ -0,0 +1,120 @@ +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "应用流水线任务") +@TableName("application_pipeline_task") +@SuppressWarnings("ALL") +public class ApplicationPipelineTaskDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "流水线id") + @TableField("pipeline_id") + private Long pipelineId; + + @ApiModelProperty(value = "流水线名称") + @TableField("pipeline_name") + private String pipelineName; + + @ApiModelProperty(value = "环境id") + @TableField("profile_id") + private Long profileId; + + @ApiModelProperty(value = "环境名称") + @TableField("profile_name") + private String profileName; + + @ApiModelProperty(value = "环境唯一标识") + @TableField("profile_tag") + private String profileTag; + + @ApiModelProperty(value = "执行标题") + @TableField("exec_title") + private String execTitle; + + @ApiModelProperty(value = "执行描述") + @TableField("exec_description") + private String execDescription; + + /** + * @see cn.orionsec.ops.constant.app.PipelineStatus + */ + @ApiModelProperty(value = "执行状态 10待审核 20审核驳回 30待执行 35待调度 40执行中 50执行完成 60执行停止 70执行失败") + @TableField("exec_status") + private Integer execStatus; + + @ApiModelProperty(value = "是否是定时执行 10普通执行 20定时执行") + @TableField("timed_exec") + private Integer timedExec; + + @ApiModelProperty(value = "定时执行时间") + @TableField("timed_exec_time") + private Date timedExecTime; + + @ApiModelProperty(value = "创建人id") + @TableField("create_user_id") + private Long createUserId; + + @ApiModelProperty(value = "创建人名称") + @TableField("create_user_name") + private String createUserName; + + @ApiModelProperty(value = "审核人id") + @TableField("audit_user_id") + private Long auditUserId; + + @ApiModelProperty(value = "审核人名称") + @TableField("audit_user_name") + private String auditUserName; + + @ApiModelProperty(value = "审核时间") + @TableField("audit_time") + private Date auditTime; + + @ApiModelProperty(value = "审核备注") + @TableField("audit_reason") + private String auditReason; + + @ApiModelProperty(value = "执行人id") + @TableField("exec_user_id") + private Long execUserId; + + @ApiModelProperty(value = "执行人名称") + @TableField("exec_user_name") + private String execUserName; + + @ApiModelProperty(value = "执行开始时间") + @TableField("exec_start_time") + private Date execStartTime; + + @ApiModelProperty(value = "执行结束时间") + @TableField("exec_end_time") + private Date execEndTime; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationPipelineTaskDetailDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationPipelineTaskDetailDO.java new file mode 100644 index 0000000..0786c88 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationPipelineTaskDetailDO.java @@ -0,0 +1,95 @@ +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "应用流水线任务详情") +@TableName("application_pipeline_task_detail") +@SuppressWarnings("ALL") +public class ApplicationPipelineTaskDetailDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "流水线id") + @TableField("pipeline_id") + private Long pipelineId; + + @ApiModelProperty(value = "流水线详情id") + @TableField("pipeline_detail_id") + private Long pipelineDetailId; + + @ApiModelProperty(value = "流水线操作任务id") + @TableField("task_id") + private Long taskId; + + @ApiModelProperty(value = "引用id") + @TableField("rel_id") + private Long relId; + + @ApiModelProperty(value = "应用id") + @TableField("app_id") + private Long appId; + + @ApiModelProperty(value = "应用名称") + @TableField("app_name") + private String appName; + + @ApiModelProperty(value = "应用唯一标识") + @TableField("app_tag") + private String appTag; + + /** + * @see cn.orionsec.ops.constant.app.StageType + */ + @ApiModelProperty(value = "阶段类型 10构建 20发布") + @TableField("stage_type") + private Integer stageType; + + /** + * @see cn.orionsec.ops.entity.dto.ApplicationPipelineStageConfigDTO + */ + @ApiModelProperty(value = "阶段操作配置") + @TableField("stage_config") + private String stageConfig; + + /** + * @see cn.orionsec.ops.constant.app.PipelineDetailStatus + */ + @ApiModelProperty(value = "状态 10未开始 20进行中 30已完成 40执行失败 50已跳过 60已终止") + @TableField("exec_status") + private Integer execStatus; + + @ApiModelProperty(value = "执行开始时间") + @TableField("exec_start_time") + private Date execStartTime; + + @ApiModelProperty(value = "执行结束时间") + @TableField("exec_end_time") + private Date execEndTime; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationPipelineTaskLogDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationPipelineTaskLogDO.java new file mode 100644 index 0000000..0d6a64e --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationPipelineTaskLogDO.java @@ -0,0 +1,64 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "应用流水线任务日志") +@TableName("application_pipeline_task_log") +@SuppressWarnings("ALL") +public class ApplicationPipelineTaskLogDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "流水线任务id") + @TableField("task_id") + private Long taskId; + + @ApiModelProperty(value = "流水线任务详情id") + @TableField("task_detail_id") + private Long taskDetailId; + + /** + * @see cn.orionsec.ops.constant.app.PipelineLogStatus + */ + @ApiModelProperty(value = "日志状态 10创建 20执行 30成功 40失败 50停止 60跳过") + @TableField("log_status") + private Integer logStatus; + + /** + * @see cn.orionsec.ops.constant.app.StageType + */ + @ApiModelProperty(value = "阶段类型 10构建 20发布") + @TableField("stage_type") + private Integer stageType; + + @ApiModelProperty(value = "日志详情") + @TableField("log_info") + private String logInfo; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationProfileDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationProfileDO.java new file mode 100644 index 0000000..5a9cb7b --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationProfileDO.java @@ -0,0 +1,59 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "应用环境表") +@TableName("application_profile") +@SuppressWarnings("ALL") +public class ApplicationProfileDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "环境名称") + @TableField("profile_name") + private String profileName; + + @ApiModelProperty(value = "环境唯一标识") + @TableField("profile_tag") + private String profileTag; + + @ApiModelProperty(value = "环境描述") + @TableField("description") + private String description; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "发布是否需要审核 1需要 2无需") + @TableField("release_audit") + private Integer releaseAudit; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationReleaseDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationReleaseDO.java new file mode 100644 index 0000000..a71ef13 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationReleaseDO.java @@ -0,0 +1,181 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "发布任务") +@TableName("application_release") +@SuppressWarnings("ALL") +public class ApplicationReleaseDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "发布标题") + @TableField("release_title") + private String releaseTitle; + + @ApiModelProperty(value = "发布描述") + @TableField("release_description") + private String releaseDescription; + + @ApiModelProperty(value = "构建id") + @TableField("build_id") + private Long buildId; + + @ApiModelProperty(value = "构建seq") + @TableField("build_seq") + private Integer buildSeq; + + @ApiModelProperty(value = "应用id") + @TableField("app_id") + private Long appId; + + @ApiModelProperty(value = "应用名称") + @TableField("app_name") + private String appName; + + @ApiModelProperty(value = "应用唯一标识") + @TableField("app_tag") + private String appTag; + + @ApiModelProperty(value = "环境id") + @TableField("profile_id") + private Long profileId; + + @ApiModelProperty(value = "环境名称") + @TableField("profile_name") + private String profileName; + + @ApiModelProperty(value = "环境唯一标识") + @TableField("profile_tag") + private String profileTag; + + /** + * @see cn.orionsec.ops.constant.app.ReleaseType + */ + @ApiModelProperty(value = "发布类型 10正常发布 20回滚发布") + @TableField("release_type") + private Integer releaseType; + + /** + * @see cn.orionsec.ops.constant.app.ReleaseStatus + */ + @ApiModelProperty(value = "发布状态 10待审核 20审核驳回 30待发布 35待调度 40发布中 50发布完成 60发布停止 70发布失败") + @TableField("release_status") + private Integer releaseStatus; + + /** + * @see cn.orionsec.ops.constant.common.SerialType + */ + @ApiModelProperty(value = "发布序列 10串行 20并行") + @TableField("release_serialize") + private Integer releaseSerialize; + + /** + * @see cn.orionsec.ops.constant.common.ExceptionHandlerType + */ + @ApiModelProperty(value = "异常处理 10跳过所有 20跳过错误") + @TableField("exception_handler") + private Integer exceptionHandler; + + @ApiModelProperty(value = "构建产物文件") + @TableField("bundle_path") + private String bundlePath; + + @ApiModelProperty(value = "产物传输路径") + @TableField("transfer_path") + private String transferPath; + + /** + * @see cn.orionsec.ops.constant.app.TransferMode + */ + @ApiModelProperty(value = "产物传输方式") + @TableField("transfer_mode") + private String transferMode; + + @ApiModelProperty(value = "回滚发布id") + @TableField("rollback_release_id") + private Long rollbackReleaseId; + + /** + * @see cn.orionsec.ops.constant.app.TimedType + */ + @ApiModelProperty(value = "是否是定时发布 10普通发布 20定时发布") + @TableField("timed_release") + private Integer timedRelease; + + @ApiModelProperty(value = "定时发布时间") + @TableField("timed_release_time") + private Date timedReleaseTime; + + @ApiModelProperty(value = "创建人id") + @TableField("create_user_id") + private Long createUserId; + + @ApiModelProperty(value = "创建人名称") + @TableField("create_user_name") + private String createUserName; + + @ApiModelProperty(value = "审核人id") + @TableField("audit_user_id") + private Long auditUserId; + + @ApiModelProperty(value = "审核人名称") + @TableField("audit_user_name") + private String auditUserName; + + @ApiModelProperty(value = "审核时间") + @TableField("audit_time") + private Date auditTime; + + @ApiModelProperty(value = "审核备注") + @TableField("audit_reason") + private String auditReason; + + @ApiModelProperty(value = "发布开始时间") + @TableField("release_start_time") + private Date releaseStartTime; + + @ApiModelProperty(value = "发布结束时间") + @TableField("release_end_time") + private Date releaseEndTime; + + @ApiModelProperty(value = "发布人id") + @TableField("release_user_id") + private Long releaseUserId; + + @ApiModelProperty(value = "发布人名称") + @TableField("release_user_name") + private String releaseUserName; + + @ApiModelProperty(value = "发布操作json") + @TableField("action_config") + private String actionConfig; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationReleaseMachineDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationReleaseMachineDO.java new file mode 100644 index 0000000..f524851 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationReleaseMachineDO.java @@ -0,0 +1,78 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "发布任务机器表") +@TableName("application_release_machine") +@SuppressWarnings("ALL") +public class ApplicationReleaseMachineDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "发布任务id") + @TableField("release_id") + private Long releaseId; + + @ApiModelProperty(value = "机器id") + @TableField("machine_id") + private Long machineId; + + @ApiModelProperty(value = "机器名称") + @TableField("machine_name") + private String machineName; + + @ApiModelProperty(value = "机器唯一标识") + @TableField("machine_tag") + private String machineTag; + + @ApiModelProperty(value = "机器主机") + @TableField("machine_host") + private String machineHost; + + /** + * @see cn.orionsec.ops.constant.app.ActionStatus + */ + @ApiModelProperty(value = "状态 10未开始 20进行中 30已完成 40执行失败 50已跳过 60已取消") + @TableField("run_status") + private Integer runStatus; + + @ApiModelProperty(value = "日志路径") + @TableField("log_path") + private String logPath; + + @ApiModelProperty(value = "开始时间") + @TableField("start_time") + private Date startTime; + + @ApiModelProperty(value = "结束时间") + @TableField("end_time") + private Date endTime; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationRepositoryDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationRepositoryDO.java new file mode 100644 index 0000000..c447cca --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/ApplicationRepositoryDO.java @@ -0,0 +1,91 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "应用版本仓库") +@TableName("application_repository") +@SuppressWarnings("ALL") +public class ApplicationRepositoryDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "名称") + @TableField("repo_name") + private String repoName; + + @ApiModelProperty(value = "描述") + @TableField("repo_description") + private String repoDescription; + + /** + * @see cn.orionsec.ops.constant.app.RepositoryType + */ + @ApiModelProperty(value = "类型 1git") + @TableField("repo_type") + private Integer repoType; + + @ApiModelProperty(value = "url") + @TableField("repo_url") + private String repoUrl; + + @ApiModelProperty(value = "用户名") + @TableField("repo_username") + private String repoUsername; + + @ApiModelProperty(value = "密码") + @TableField("repo_password") + private String repoPassword; + + @ApiModelProperty(value = "token") + @TableField("repo_private_token") + private String repoPrivateToken; + + /** + * @see cn.orionsec.ops.constant.app.RepositoryStatus + */ + @ApiModelProperty(value = "状态 10未初始化 20初始化中 30正常 40失败") + @TableField("repo_status") + private Integer repoStatus; + + /** + * @see cn.orionsec.ops.constant.app.RepositoryAuthType + */ + @ApiModelProperty(value = "认证类型 10密码 20令牌") + @TableField("repo_auth_type") + private Integer repoAuthType; + + /** + * @see cn.orionsec.ops.constant.app.RepositoryTokenType + */ + @ApiModelProperty(value = "令牌类型 10github 20gitee 30gitlab") + @TableField("repo_token_type") + private Integer repoTokenType; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "更新时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/CommandExecDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/CommandExecDO.java new file mode 100644 index 0000000..faf7d7d --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/CommandExecDO.java @@ -0,0 +1,101 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "命令执行表") +@TableName("command_exec") +@SuppressWarnings("ALL") +public class CommandExecDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "用户id") + @TableField("user_id") + private Long userId; + + @ApiModelProperty(value = "用户名") + @TableField("user_name") + private String userName; + + /** + * @see cn.orionsec.ops.constant.command.ExecType + */ + @ApiModelProperty(value = "执行类型 10批量执行") + @TableField("exec_type") + private Integer execType; + + @ApiModelProperty(value = "机器id") + @TableField("machine_id") + private Long machineId; + + @ApiModelProperty(value = "机器名称") + @TableField("machine_name") + private String machineName; + + @ApiModelProperty(value = "机器主机") + @TableField("machine_host") + private String machineHost; + + @ApiModelProperty(value = "机器唯一标识") + @TableField("machine_tag") + private String machineTag; + + /** + * @see cn.orionsec.ops.constant.command.ExecStatus + */ + @ApiModelProperty(value = "执行状态 10未开始 20执行中 30执行成功 40执行失败 50执行终止") + @TableField("exec_status") + private Integer execStatus; + + @ApiModelProperty(value = "执行返回码") + @TableField("exit_code") + private Integer exitCode; + + @ApiModelProperty(value = "命令") + @TableField("exec_command") + private String execCommand; + + @ApiModelProperty(value = "描述") + @TableField("description") + private String description; + + @ApiModelProperty(value = "日志目录") + @TableField("log_path") + private String logPath; + + @ApiModelProperty(value = "执行开始时间") + @TableField("start_date") + private Date startDate; + + @ApiModelProperty(value = "执行结束时间") + @TableField("end_date") + private Date endDate; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/CommandTemplateDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/CommandTemplateDO.java new file mode 100644 index 0000000..854c2bc --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/CommandTemplateDO.java @@ -0,0 +1,66 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "命令模板表") +@TableName("command_template") +public class CommandTemplateDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "模板名称") + @TableField("template_name") + private String templateName; + + @ApiModelProperty(value = "命令") + @TableField("template_value") + private String templateValue; + + @ApiModelProperty(value = "命令描述") + @TableField("description") + private String description; + + @ApiModelProperty(value = "创建用户id") + @TableField("create_user_id") + private Long createUserId; + + @ApiModelProperty(value = "创建用户名") + @TableField("create_user_name") + private String createUserName; + + @ApiModelProperty(value = "修改用户id") + @TableField("update_user_id") + private Long updateUserId; + + @ApiModelProperty(value = "修改用户名") + @TableField("update_user_name") + private String updateUserName; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/FileTailListDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/FileTailListDO.java new file mode 100644 index 0000000..51e22b1 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/FileTailListDO.java @@ -0,0 +1,70 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "文件tail表") +@TableName("file_tail_list") +@SuppressWarnings("ALL") +public class FileTailListDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "机器id") + @TableField("machine_id") + private Long machineId; + + @ApiModelProperty(value = "别名") + @TableField("alias_name") + private String aliasName; + + @ApiModelProperty(value = "文件路径") + @TableField("file_path") + private String filePath; + + @ApiModelProperty(value = "文件编码") + @TableField("file_charset") + private String fileCharset; + + @ApiModelProperty(value = "尾部文件偏移行数") + @TableField("file_offset") + private Integer fileOffset; + + @ApiModelProperty(value = "tail 命令") + @TableField("tail_command") + private String tailCommand; + + /** + * @see cn.orionsec.ops.constant.tail.FileTailMode + */ + @ApiModelProperty(value = "宿主机文件追踪类型 tracker/tail") + @TableField("tail_mode") + private String tailMode; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/FileTransferLogDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/FileTransferLogDO.java new file mode 100644 index 0000000..a5412cf --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/FileTransferLogDO.java @@ -0,0 +1,89 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "sftp传输日志表") +@TableName("file_transfer_log") +@SuppressWarnings("ALL") +public class FileTransferLogDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "用户id") + @TableField("user_id") + private Long userId; + + @ApiModelProperty(value = "用户名") + @TableField("user_name") + private String userName; + + @ApiModelProperty(value = "文件token") + @TableField("file_token") + private String fileToken; + + /** + * @see cn.orionsec.ops.constant.sftp.SftpTransferType + */ + @ApiModelProperty(value = "传输类型 10上传 20下载 30传输") + @TableField("transfer_type") + private Integer transferType; + + @ApiModelProperty(value = "机器id") + @TableField("machine_id") + private Long machineId; + + @ApiModelProperty(value = "远程文件") + @TableField("remote_file") + private String remoteFile; + + @ApiModelProperty(value = "本机文件") + @TableField("local_file") + private String localFile; + + @ApiModelProperty(value = "当前大小") + @TableField("current_size") + private Long currentSize; + + @ApiModelProperty(value = "文件大小") + @TableField("file_size") + private Long fileSize; + + @ApiModelProperty(value = "当前进度") + @TableField("now_progress") + private Double nowProgress; + + /** + * @see cn.orionsec.ops.constant.sftp.SftpTransferStatus + */ + @ApiModelProperty(value = "传输状态 10未开始 20进行中 30已暂停 40已完成 50已取消 60传输异常") + @TableField("transfer_status") + private Integer transferStatus; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/HistoryValueSnapshotDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/HistoryValueSnapshotDO.java new file mode 100644 index 0000000..3c847ff --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/HistoryValueSnapshotDO.java @@ -0,0 +1,72 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "历史值快照表") +@TableName("history_value_snapshot") +@SuppressWarnings("ALL") +public class HistoryValueSnapshotDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "值id") + @TableField("value_id") + private Long valueId; + + /** + * @see cn.orionsec.ops.constant.history.HistoryOperator + */ + @ApiModelProperty(value = "操作类型 1新增 2修改 3删除") + @TableField("operator_type") + private Integer operatorType; + + /** + * @see cn.orionsec.ops.constant.history.HistoryValueType + */ + @ApiModelProperty(value = "值类型 10机器环境变量 20应用环境变量") + @TableField("value_type") + private Integer valueType; + + @ApiModelProperty(value = "原始值") + @TableField("before_value") + private String beforeValue; + + @ApiModelProperty(value = "新值") + @TableField("after_value") + private String afterValue; + + @ApiModelProperty(value = "修改人id") + @TableField("update_user_id") + private Long updateUserId; + + @ApiModelProperty(value = "修改人用户名") + @TableField("update_user_name") + private String updateUserName; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineAlarmConfigDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineAlarmConfigDO.java new file mode 100644 index 0000000..9359d76 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineAlarmConfigDO.java @@ -0,0 +1,61 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("machine_alarm_config") +@ApiModel(value = "MachineAlarmConfigDO对象", description = "机器报警配置") +@SuppressWarnings("ALL") +public class MachineAlarmConfigDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "机器id") + @TableField("machine_id") + private Long machineId; + + /** + * @see cn.orionsec.ops.constant.machine.MachineAlarmType + */ + @ApiModelProperty(value = "报警类型 10: cpu使用率 20: 内存使用率") + @TableField("alarm_type") + private Integer alarmType; + + @ApiModelProperty(value = "报警阈值") + @TableField("alarm_threshold") + private Double alarmThreshold; + + @ApiModelProperty(value = "触发报警阈值 次") + @TableField("trigger_threshold") + private Integer triggerThreshold; + + @ApiModelProperty(value = "报警通知沉默时间 分") + @TableField("notify_silence") + private Integer notifySilence; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineAlarmGroupDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineAlarmGroupDO.java new file mode 100644 index 0000000..a6d5b95 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineAlarmGroupDO.java @@ -0,0 +1,46 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + + +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("machine_alarm_group") +@ApiModel(value = "MachineAlarmGroupDO对象", description = "机器报警通知组") +public class MachineAlarmGroupDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "机器id") + @TableField("machine_id") + private Long machineId; + + @ApiModelProperty(value = " 报警组id") + @TableField("group_id") + private Long groupId; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineAlarmHistoryDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineAlarmHistoryDO.java new file mode 100644 index 0000000..9622738 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineAlarmHistoryDO.java @@ -0,0 +1,55 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("machine_alarm_history") +@ApiModel(value = "MachineAlarmHistoryDO对象", description = "机器报警历史") +@SuppressWarnings("ALL") +public class MachineAlarmHistoryDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "机器id") + @TableField("machine_id") + private Long machineId; + + /** + * @see cn.orionsec.ops.constant.machine.MachineAlarmType + */ + @ApiModelProperty(value = "报警类型 10: cpu使用率 20: 内存使用率") + @TableField("alarm_type") + private Integer alarmType; + + @ApiModelProperty(value = "报警值") + @TableField("alarm_value") + private Double alarmValue; + + @ApiModelProperty(value = "报警时间") + @TableField("alarm_time") + private Date alarmTime; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @TableField("create_time") + private Date createTime; + + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineEnvDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineEnvDO.java new file mode 100644 index 0000000..f2b239a --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineEnvDO.java @@ -0,0 +1,53 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "机器环境变量") +@TableName("machine_env") +public class MachineEnvDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "机器id") + @TableField("machine_id") + private Long machineId; + + @ApiModelProperty(value = "key") + @TableField("attr_key") + private String attrKey; + + @ApiModelProperty(value = "value") + @TableField("attr_value") + private String attrValue; + + @ApiModelProperty(value = "描述") + @TableField("description") + private String description; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineGroupDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineGroupDO.java new file mode 100644 index 0000000..59f66ab --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineGroupDO.java @@ -0,0 +1,50 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + + +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("machine_group") +@ApiModel(value = "MachineGroupDO对象", description = "机器分组") +public class MachineGroupDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "父id") + @TableField("parent_id") + private Long parentId; + + @ApiModelProperty(value = "组名称") + @TableField("group_name") + private String groupName; + + @ApiModelProperty(value = "排序") + @TableField("sort") + private Integer sort; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineGroupRelDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineGroupRelDO.java new file mode 100644 index 0000000..da1146c --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineGroupRelDO.java @@ -0,0 +1,45 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("machine_group_rel") +@ApiModel(value = "MachineGroupRelDO对象", description = "机器分组关联表") +public class MachineGroupRelDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "组id") + @TableField("group_id") + private Long groupId; + + @ApiModelProperty(value = "机器id") + @TableField("machine_id") + private Long machineId; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineInfoDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineInfoDO.java new file mode 100644 index 0000000..b43bc71 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineInfoDO.java @@ -0,0 +1,90 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "机器信息表") +@TableName("machine_info") +@SuppressWarnings("ALL") +public class MachineInfoDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "代理id") + @TableField(value = "proxy_id", updateStrategy = FieldStrategy.IGNORED) + private Long proxyId; + + @ApiModelProperty(value = "密钥id") + @TableField(value = "key_id", updateStrategy = FieldStrategy.IGNORED) + private Long keyId; + + @ApiModelProperty(value = "主机ip") + @TableField("machine_host") + private String machineHost; + + @ApiModelProperty(value = "ssh端口") + @TableField("ssh_port") + private Integer sshPort; + + @ApiModelProperty(value = "机器名称") + @TableField("machine_name") + private String machineName; + + @ApiModelProperty(value = "机器唯一标识") + @TableField("machine_tag") + private String machineTag; + + @ApiModelProperty(value = "机器描述") + @TableField("description") + private String description; + + @ApiModelProperty(value = "机器账号") + @TableField("username") + private String username; + + @ApiModelProperty(value = "机器密码") + @TableField("password") + private String password; + + /** + * @see cn.orionsec.ops.constant.machine.MachineAuthType + */ + @ApiModelProperty(value = "机器认证方式 1: 密码认证 2: 独立密钥") + @TableField("auth_type") + private Integer authType; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "机器状态 1有效 2无效") + @TableField("machine_status") + private Integer machineStatus; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineMonitorDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineMonitorDO.java new file mode 100644 index 0000000..1ba955f --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineMonitorDO.java @@ -0,0 +1,61 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "机器监控配置表") +@TableName("machine_monitor") +@SuppressWarnings("ALL") +public class MachineMonitorDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "机器id") + @TableField("machine_id") + private Long machineId; + + /** + * @see cn.orionsec.ops.constant.monitor.MonitorStatus + */ + @ApiModelProperty(value = "插件状态 1未安装 2安装中 3未运行 4运行中") + @TableField("monitor_status") + private Integer monitorStatus; + + @ApiModelProperty(value = "请求 api url") + @TableField("monitor_url") + private String monitorUrl; + + @ApiModelProperty(value = "请求 api accessToken") + @TableField("access_token") + private String accessToken; + + @ApiModelProperty(value = "插件版本") + @TableField("agent_version") + private String agentVersion; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineProxyDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineProxyDO.java new file mode 100644 index 0000000..226f220 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineProxyDO.java @@ -0,0 +1,65 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "机器代理") +@TableName("machine_proxy") +@SuppressWarnings("ALL") +public class MachineProxyDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "代理主机") + @TableField("proxy_host") + private String proxyHost; + + @ApiModelProperty(value = "代理端口") + @TableField("proxy_port") + private Integer proxyPort; + + @ApiModelProperty(value = "代理用户名") + @TableField("proxy_username") + private String proxyUsername; + + @ApiModelProperty(value = "代理密码") + @TableField("proxy_password") + private String proxyPassword; + + /** + * @see cn.orionsec.ops.constant.machine.ProxyType + */ + @ApiModelProperty(value = "代理类型 1http代理 2socket4代理 3socket5代理") + @TableField("proxy_type") + private Integer proxyType; + + @ApiModelProperty(value = "描述") + @TableField("description") + private String description; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineSecretKeyDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineSecretKeyDO.java new file mode 100644 index 0000000..bbde0cf --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineSecretKeyDO.java @@ -0,0 +1,53 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "机器ssh登录密钥") +@TableName("machine_secret_key") +public class MachineSecretKeyDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "密钥名称") + @TableField("key_name") + private String keyName; + + @ApiModelProperty(value = "密钥文件本地路径") + @TableField("secret_key_path") + private String secretKeyPath; + + @ApiModelProperty(value = "密钥密码") + @TableField("password") + private String password; + + @ApiModelProperty(value = "描述") + @TableField("description") + private String description; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineTerminalDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineTerminalDO.java new file mode 100644 index 0000000..49ed010 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineTerminalDO.java @@ -0,0 +1,85 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "机器终端配置表") +@TableName("machine_terminal") +@SuppressWarnings("ALL") +public class MachineTerminalDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "机器id") + @TableField("machine_id") + private Long machineId; + + /** + * @see cn.orionsec.kit.net.host.ssh.TerminalType#XTERM + */ + @ApiModelProperty(value = "终端类型") + @TableField("terminal_type") + private String terminalType; + + /** + * @see cn.orionsec.ops.constant.terminal.TerminalConst#BACKGROUND_COLOR + */ + @ApiModelProperty(value = "背景色") + @TableField("background_color") + private String backgroundColor; + + /** + * @see cn.orionsec.ops.constant.terminal.TerminalConst#FONT_COLOR + */ + @ApiModelProperty(value = "字体颜色") + @TableField("font_color") + private String fontColor; + + /** + * @see cn.orionsec.ops.constant.terminal.TerminalConst#FONT_SIZE + */ + @ApiModelProperty(value = "字体大小") + @TableField("font_size") + private Integer fontSize; + + /** + * @see cn.orionsec.ops.constant.terminal.TerminalConst#FONT_FAMILY + */ + @ApiModelProperty(value = "字体名称") + @TableField("font_family") + private String fontFamily; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "是否开启url link 1开启 2关闭") + @TableField("enable_web_link") + private Integer enableWebLink; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineTerminalLogDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineTerminalLogDO.java new file mode 100644 index 0000000..295b4ea --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/MachineTerminalLogDO.java @@ -0,0 +1,84 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "机器终端操作日志") +@TableName("machine_terminal_log") +@SuppressWarnings("ALL") +public class MachineTerminalLogDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "用户id") + @TableField("user_id") + private Long userId; + + @ApiModelProperty(value = "用户名") + @TableField("username") + private String username; + + @ApiModelProperty(value = "机器id") + @TableField("machine_id") + private Long machineId; + + @ApiModelProperty(value = "机器名称") + @TableField("machine_name") + private String machineName; + + @ApiModelProperty(value = "机器唯一标识") + @TableField("machine_tag") + private String machineTag; + + @ApiModelProperty(value = "机器host") + @TableField("machine_host") + private String machineHost; + + @ApiModelProperty(value = "token") + @TableField("access_token") + private String accessToken; + + @ApiModelProperty(value = "建立连接时间") + @TableField("connected_time") + private Date connectedTime; + + @ApiModelProperty(value = "断开连接时间") + @TableField("disconnected_time") + private Date disconnectedTime; + + /** + * @see cn.orionsec.ops.constant.ws.WsCloseCode + */ + @ApiModelProperty(value = "close code") + @TableField("close_code") + private Integer closeCode; + + @ApiModelProperty(value = "录屏文件路径") + @TableField("screen_path") + private String screenPath; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/SchedulerTaskDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/SchedulerTaskDO.java new file mode 100644 index 0000000..513d14b --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/SchedulerTaskDO.java @@ -0,0 +1,87 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "调度任务") +@TableName("scheduler_task") +@SuppressWarnings("ALL") +public class SchedulerTaskDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "任务名称") + @TableField("task_name") + private String taskName; + + @ApiModelProperty(value = "任务描述") + @TableField("description") + private String description; + + @ApiModelProperty(value = "执行命令") + @TableField("task_command") + private String taskCommand; + + @ApiModelProperty(value = "cron表达式") + @TableField("expression") + private String expression; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "启用状态 1启用 2停用") + @TableField("enable_status") + private Integer enableStatus; + + /** + * @see cn.orionsec.ops.constant.scheduler.SchedulerTaskStatus + */ + @ApiModelProperty(value = "最近状态 10待调度 20调度中 30调度成功 40调度失败 50已停止") + @TableField("lately_status") + private Integer latelyStatus; + + /** + * @see cn.orionsec.ops.constant.common.SerialType + */ + @ApiModelProperty(value = "调度序列 10串行 20并行") + @TableField("serialize_type") + private Integer serializeType; + + /** + * @see cn.orionsec.ops.constant.common.ExceptionHandlerType + */ + @ApiModelProperty(value = "异常处理 10跳过所有 20跳过错误") + @TableField("exception_handler") + private Integer exceptionHandler; + + @ApiModelProperty(value = "上次调度时间") + @TableField(value = "lately_schedule_time") + private Date latelyScheduleTime; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/SchedulerTaskMachineDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/SchedulerTaskMachineDO.java new file mode 100644 index 0000000..36fb290 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/SchedulerTaskMachineDO.java @@ -0,0 +1,45 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "调度任务机器") +@TableName("scheduler_task_machine") +public class SchedulerTaskMachineDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "任务id") + @TableField("task_id") + private Long taskId; + + @ApiModelProperty(value = "调度机器id") + @TableField("machine_id") + private Long machineId; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/SchedulerTaskMachineRecordDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/SchedulerTaskMachineRecordDO.java new file mode 100644 index 0000000..32a3aa5 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/SchedulerTaskMachineRecordDO.java @@ -0,0 +1,94 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "调度任务执行明细机器详情") +@TableName("scheduler_task_machine_record") +@SuppressWarnings("ALL") +public class SchedulerTaskMachineRecordDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "任务id") + @TableField("task_id") + private Long taskId; + + @ApiModelProperty(value = "明细id") + @TableField("record_id") + private Long recordId; + + @ApiModelProperty(value = "任务机器id") + @TableField("task_machine_id") + private Long taskMachineId; + + @ApiModelProperty(value = "执行机器id") + @TableField("machine_id") + private Long machineId; + + @ApiModelProperty(value = "机器名称") + @TableField("machine_name") + private String machineName; + + @ApiModelProperty(value = "机器主机") + @TableField("machine_host") + private String machineHost; + + @ApiModelProperty(value = "机器唯一标识") + @TableField("machine_tag") + private String machineTag; + + @ApiModelProperty(value = "执行命令") + @TableField("exec_command") + private String execCommand; + + /** + * @see cn.orionsec.ops.constant.scheduler.SchedulerTaskMachineStatus + */ + @ApiModelProperty(value = "执行状态 10待调度 20调度中 30调度成功 40调度失败 50已跳过 60已停止") + @TableField("exec_status") + private Integer execStatus; + + @ApiModelProperty(value = "退出码") + @TableField("exit_code") + private Integer exitCode; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "日志路径") + @TableField("log_path") + private String logPath; + + @ApiModelProperty(value = "开始时间") + @TableField("start_time") + private Date startTime; + + @ApiModelProperty(value = "结束时间") + @TableField("end_time") + private Date endTime; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/SchedulerTaskRecordDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/SchedulerTaskRecordDO.java new file mode 100644 index 0000000..16c6243 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/SchedulerTaskRecordDO.java @@ -0,0 +1,62 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "调度任务执行日志") +@TableName("scheduler_task_record") +@SuppressWarnings("ALL") +public class SchedulerTaskRecordDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "任务id") + @TableField("task_id") + private Long taskId; + + @ApiModelProperty(value = "任务名称") + @TableField("task_name") + private String taskName; + + /** + * @see cn.orionsec.ops.constant.scheduler.SchedulerTaskStatus + */ + @ApiModelProperty(value = "任务状态 10待调度 20调度中 30调度成功 40调度失败 50已停止") + @TableField("task_status") + private Integer taskStatus; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "开始时间") + @TableField("start_time") + private Date startTime; + + @ApiModelProperty(value = "结束时间") + @TableField("end_time") + private Date endTime; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/SystemEnvDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/SystemEnvDO.java new file mode 100644 index 0000000..6f2f2a9 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/SystemEnvDO.java @@ -0,0 +1,59 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "系统环境变量") +@TableName("system_env") +@SuppressWarnings("ALL") +public class SystemEnvDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "key") + @TableField("attr_key") + private String attrKey; + + @ApiModelProperty(value = "value") + @TableField("attr_value") + private String attrValue; + + /** + * @see cn.orionsec.ops.constant.Const#IS_SYSTEM + * @see cn.orionsec.ops.constant.Const#NOT_SYSTEM + */ + @ApiModelProperty(value = "是否为系统变量 1是 2否") + @TableField("system_env") + private Integer systemEnv; + + @ApiModelProperty(value = "描述") + @TableField("description") + private String description; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/UserEventLogDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/UserEventLogDO.java new file mode 100644 index 0000000..22c7ff6 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/UserEventLogDO.java @@ -0,0 +1,73 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "用户事件日志") +@TableName("user_event_log") +@SuppressWarnings("ALL") +public class UserEventLogDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "用户id") + @TableField("user_id") + private Long userId; + + @ApiModelProperty(value = "用户名") + @TableField("username") + private String username; + + /** + * @see cn.orionsec.ops.constant.event.EventClassify + */ + @ApiModelProperty(value = "事件分类") + @TableField("event_classify") + private Integer eventClassify; + + /** + * @see cn.orionsec.ops.constant.event.EventType + */ + @ApiModelProperty(value = "事件类型") + @TableField("event_type") + private Integer eventType; + + @ApiModelProperty(value = "日志信息") + @TableField("log_info") + private String logInfo; + + @ApiModelProperty(value = "日志参数") + @TableField("params_json") + private String paramsJson; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "是否执行成功 1成功 2失败") + @TableField("exec_result") + private Integer execResult; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/UserInfoDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/UserInfoDO.java new file mode 100644 index 0000000..bddd6e6 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/UserInfoDO.java @@ -0,0 +1,98 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "用户表") +@TableName("user_info") +@SuppressWarnings("ALL") +public class UserInfoDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "用户名") + @TableField("username") + private String username; + + @ApiModelProperty(value = "昵称") + @TableField("nickname") + private String nickname; + + @ApiModelProperty(value = "密码") + @TableField("password") + private String password; + + @ApiModelProperty(value = "盐值") + @TableField("salt") + private String salt; + + /** + * @see cn.orionsec.ops.constant.user.RoleType + */ + @ApiModelProperty(value = "角色类型 10管理员 20开发 30运维") + @TableField("role_type") + private Integer roleType; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "用户状态 1启用 2禁用") + @TableField("user_status") + private Integer userStatus; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "锁定状态 1正常 2锁定") + @TableField("lock_status") + private Integer lockStatus; + + @ApiModelProperty(value = "登录失败次数") + @TableField("failed_login_count") + private Integer failedLoginCount; + + @ApiModelProperty(value = "头像地址") + @TableField("avatar_pic") + private String avatarPic; + + @ApiModelProperty(value = "联系手机") + @TableField("contact_phone") + private String contactPhone; + + @ApiModelProperty(value = "联系邮箱") + @TableField("contact_email") + private String contactEmail; + + @ApiModelProperty(value = "最后登录时间") + @TableField("last_login_time") + private Date lastLoginTime; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/WebSideMessageDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/WebSideMessageDO.java new file mode 100644 index 0000000..efd0391 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/WebSideMessageDO.java @@ -0,0 +1,76 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "系统站内信") +@TableName("web_side_message") +@SuppressWarnings("ALL") +public class WebSideMessageDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * @see cn.orionsec.ops.constant.message.MessageClassify + */ + @ApiModelProperty(value = "消息分类") + @TableField("message_classify") + private Integer messageClassify; + + /** + * @see cn.orionsec.ops.constant.message.MessageType + */ + @ApiModelProperty(value = "消息类型") + @TableField("message_type") + private Integer messageType; + + /** + * @see cn.orionsec.ops.constant.message.ReadStatus + */ + @ApiModelProperty(value = "是否已读 1未读 2已读") + @TableField("read_status") + private Integer readStatus; + + @ApiModelProperty(value = "收信人id") + @TableField("to_user_id") + private Long toUserId; + + @ApiModelProperty(value = "收信人名称") + @TableField("to_user_name") + private String toUserName; + + @ApiModelProperty(value = "消息") + @TableField("send_message") + private String sendMessage; + + @ApiModelProperty(value = "消息关联id") + @TableField("rel_id") + private Long relId; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/WebhookConfigDO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/WebhookConfigDO.java new file mode 100644 index 0000000..28f34e9 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/domain/WebhookConfigDO.java @@ -0,0 +1,57 @@ + +package cn.orionsec.ops.entity.domain; + +import com.baomidou.mybatisplus.annotation.*; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = false) +@TableName("webhook_config") +@ApiModel(value = "WebhookConfigDO对象", description = "webhook 配置") +@SuppressWarnings("ALL") +public class WebhookConfigDO implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "id") + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + @ApiModelProperty(value = "名称") + @TableField("webhook_name") + private String webhookName; + + @ApiModelProperty(value = "url") + @TableField("webhook_url") + private String webhookUrl; + + /** + * @see cn.orionsec.ops.constant.webhook.WebhookType + */ + @ApiModelProperty(value = "类型 10: 钉钉机器人") + @TableField("webhook_type") + private Integer webhookType; + + @ApiModelProperty(value = "配置项 json") + @TableField("webhook_config") + private String webhookConfig; + + @ApiModelProperty(value = "是否删除 1未删除 2已删除") + @TableLogic + private Integer deleted; + + @ApiModelProperty(value = "创建时间") + @TableField("create_time") + private Date createTime; + + @ApiModelProperty(value = "更新时间") + @TableField("update_time") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/ApplicationActionConfigDTO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/ApplicationActionConfigDTO.java new file mode 100644 index 0000000..1c9142a --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/ApplicationActionConfigDTO.java @@ -0,0 +1,22 @@ + +package cn.orionsec.ops.entity.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + + +@Data +@ApiModel(value = "检查应用是否已配置") +public class ApplicationActionConfigDTO { + + @ApiModelProperty(value = "appId") + private Long appId; + + @ApiModelProperty(value = "构建阶段数量") + private Integer buildStageCount; + + @ApiModelProperty(value = "发布阶段数量") + private Integer releaseStageCount; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/ApplicationBuildStatisticsDTO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/ApplicationBuildStatisticsDTO.java new file mode 100644 index 0000000..35e68a8 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/ApplicationBuildStatisticsDTO.java @@ -0,0 +1,29 @@ + +package cn.orionsec.ops.entity.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "应用构建统计") +public class ApplicationBuildStatisticsDTO { + + @ApiModelProperty(value = "构建次数") + private Integer buildCount; + + @ApiModelProperty(value = "成功次数") + private Integer successCount; + + @ApiModelProperty(value = "失败次数") + private Integer failureCount; + + @ApiModelProperty(value = "日期") + private Date date; + + @ApiModelProperty(value = "平均构建时长ms (成功)") + private Long avgUsed; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/ApplicationPipelineTaskStatisticsDTO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/ApplicationPipelineTaskStatisticsDTO.java new file mode 100644 index 0000000..769de49 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/ApplicationPipelineTaskStatisticsDTO.java @@ -0,0 +1,30 @@ + +package cn.orionsec.ops.entity.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + + +@Data +@ApiModel(value = "应用流水线统计") +public class ApplicationPipelineTaskStatisticsDTO { + + @ApiModelProperty(value = "执行次数") + private Integer execCount; + + @ApiModelProperty(value = "成功次数") + private Integer successCount; + + @ApiModelProperty(value = "失败次数") + private Integer failureCount; + + @ApiModelProperty(value = "日期") + private Date date; + + @ApiModelProperty(value = "平均执行时长ms (成功)") + private Long avgUsed; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/ApplicationReleaseStatisticsDTO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/ApplicationReleaseStatisticsDTO.java new file mode 100644 index 0000000..c6d3a84 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/ApplicationReleaseStatisticsDTO.java @@ -0,0 +1,29 @@ + +package cn.orionsec.ops.entity.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "应用发布统计") +public class ApplicationReleaseStatisticsDTO { + + @ApiModelProperty(value = "发布次数") + private Integer releaseCount; + + @ApiModelProperty(value = "成功次数") + private Integer successCount; + + @ApiModelProperty(value = "失败次数") + private Integer failureCount; + + @ApiModelProperty(value = "日期") + private Date date; + + @ApiModelProperty(value = "平均发布时长ms (成功)") + private Long avgUsed; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/MachineMonitorDTO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/MachineMonitorDTO.java new file mode 100644 index 0000000..126139b --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/MachineMonitorDTO.java @@ -0,0 +1,45 @@ + +package cn.orionsec.ops.entity.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; + + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "机器监控配置") +@SuppressWarnings("ALL") +public class MachineMonitorDTO implements Serializable { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "机器名称") + private String machineName; + + @ApiModelProperty(value = "机器主机") + private String machineHost; + + /** + * @see cn.orionsec.ops.constant.monitor.MonitorStatus + */ + @ApiModelProperty(value = "监控状态 1未安装 2安装中 3未运行 4运行中") + private Integer monitorStatus; + + @ApiModelProperty(value = "请求 api url") + private String monitorUrl; + + @ApiModelProperty(value = "请求 api accessToken") + private String accessToken; + + @ApiModelProperty(value = "插件版本") + private String agentVersion; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/SchedulerTaskRecordStatisticsDTO.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/SchedulerTaskRecordStatisticsDTO.java new file mode 100644 index 0000000..60acba3 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/dto/SchedulerTaskRecordStatisticsDTO.java @@ -0,0 +1,35 @@ + +package cn.orionsec.ops.entity.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "调度任务机器执行统计") +public class SchedulerTaskRecordStatisticsDTO { + + @ApiModelProperty(value = "调度次数") + private Integer scheduledCount; + + @ApiModelProperty(value = "成功次数") + private Integer successCount; + + @ApiModelProperty(value = "失败次数") + private Integer failureCount; + + @ApiModelProperty(value = "日期") + private Date date; + + @ApiModelProperty(value = "机器平均执行时长ms (成功)") + private Long avgUsed; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "机器名称") + private String machineName; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/query/MachineMonitorQuery.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/query/MachineMonitorQuery.java new file mode 100644 index 0000000..0f76045 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/entity/query/MachineMonitorQuery.java @@ -0,0 +1,25 @@ + +package cn.orionsec.ops.entity.query; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "机器监控查询参数") +@SuppressWarnings("ALL") +public class MachineMonitorQuery { + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "机器名称") + private String machineName; + + /** + * @see cn.orionsec.ops.constant.monitor.MonitorStatus + */ + @ApiModelProperty(value = "监控状态 1未安装 2安装中 3未运行 4运行中") + private Integer monitorStatus; + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/utils/CodeGenerator.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/utils/CodeGenerator.java new file mode 100644 index 0000000..180a3fa --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/utils/CodeGenerator.java @@ -0,0 +1,190 @@ + +package cn.orionsec.ops.utils; + +import cn.orionsec.kit.lang.constant.Const; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.Systems; +import cn.orionsec.kit.lang.utils.ext.PropertiesExt; +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.generator.AutoGenerator; +import com.baomidou.mybatisplus.generator.config.DataSourceConfig; +import com.baomidou.mybatisplus.generator.config.GlobalConfig; +import com.baomidou.mybatisplus.generator.config.PackageConfig; +import com.baomidou.mybatisplus.generator.config.StrategyConfig; +import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert; +import com.baomidou.mybatisplus.generator.config.rules.DateType; +import com.baomidou.mybatisplus.generator.config.rules.DbColumnType; +import com.baomidou.mybatisplus.generator.config.rules.IColumnType; +import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; + +import java.io.File; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +public class CodeGenerator { + + private static final Pattern ENV_VAR_PATTERN = Pattern.compile("\\$\\{([^:]+):([^}]+)\\}"); + + public static void main(String[] args) { + runGenerator(); + } + + /** + * 代码生成器 + */ + private static void runGenerator() { + // 获取配置文件 + File file = new File("orion-ops-api/orion-ops-web/src/main/resources/application-dev.properties"); + PropertiesExt ext = new PropertiesExt(file); + String url = resolveConfigValue(ext.getValue("spring.datasource.url")); + String username = resolveConfigValue(ext.getValue("spring.datasource.username")); + String password = resolveConfigValue(ext.getValue("spring.datasource.password")); + // 全局配置 + GlobalConfig gbConfig = new GlobalConfig() + // 是否支持AR模式 + .setActiveRecord(false) + // 设置作者 + .setAuthor(Const.ORION_AUTHOR) + // 生成路径 + .setOutputDir("D:/MP/") + // 文件是否覆盖 + .setFileOverride(true) + // 主键策略 + .setIdType(IdType.AUTO) + // Service名称 + .setServiceName("%sService") + // 业务实现类名称 + .setServiceImplName("%sServiceImpl") + // 实体对象名名称 + .setEntityName("%sDO") + // 映射接口名称 + .setMapperName("%sDAO") + // 映射文件名称 + .setXmlName("%sMapper") + // web名称 + .setControllerName("%sController") + // 生成 swagger2 注解 + .setSwagger2(true) + // 开启 Kotlin 模式 + .setKotlin(false) + // 是否生成ResultMap + .setBaseResultMap(true) + // 是否生成二级缓存 + .setEnableCache(false) + // date类型 + .setDateType(DateType.ONLY_DATE) + // 是否生成SQL片段 + .setBaseColumnList(true); + + // 数据源配置 + DataSourceConfig dsConfig = new DataSourceConfig() + // 配置数据库类型 + .setDbType(DbType.MYSQL) + // 配置驱动 + .setDriverName("com.mysql.cj.jdbc.Driver") + // 配置路径 + .setUrl(url) + // 配置账号 + .setUsername(username) + // 配置密码 + .setPassword(password) + // 转换器 + .setTypeConvert(new MySqlTypeConvert() { + @Override + public IColumnType processTypeConvert(GlobalConfig globalConfig, String fieldType) { + if (fieldType.toLowerCase().contains("tinyint")) { + return DbColumnType.INTEGER; + } + return super.processTypeConvert(globalConfig, fieldType); + } + }); + + // 策略配置 + StrategyConfig stConfig = new StrategyConfig() + // 全局大写命名 + .setCapitalMode(true) + // 生成实体类注解 + .setEntityTableFieldAnnotationEnable(true) + // 是否使用lombok写get set 方法 + .setEntityLombokModel(true) + // 前端是否使用 @RestController + .setRestControllerStyle(true) + // 驼峰转连接字符 + .setControllerMappingHyphenStyle(false) + // Boolean类型字段是否移除is前缀 + .setEntityBooleanColumnRemoveIsPrefix(false) + // 下滑线转驼峰命名策略 + .setNaming(NamingStrategy.underline_to_camel) + // 是否生成字段常量 + .setEntityColumnConstant(false) + // 是否链式结构 + .setChainModel(false) + // 配置表前缀 + .setTablePrefix("") + // 配置字段前缀 + .setFieldPrefix("") + // 生成的表 + .setInclude("machine_group", "machine_group_rel"); + + // 包名策略配置 + PackageConfig pkConfig = new PackageConfig() + // 声明父包 + .setParent("cn.orionsec.ops") + // 映射接口的包 + .setMapper("dao") + // service接口的包 + .setService("service") + // serviceImpl接口的包 + .setServiceImpl("service.impl") + // controller接口的包 + .setController("controller") + // 实体类的包 + .setEntity("entity.domain") + // 映射文件的包 + .setXml("mapper"); + + // 整合配置 + AutoGenerator ag = new AutoGenerator() + // 整合全局配置 + .setGlobalConfig(gbConfig) + // 整合数据源配置 + .setDataSource(dsConfig) + // 整合表名配置 + .setStrategy(stConfig) + // 整合包名策略 + .setPackageInfo(pkConfig); + + // 执行 + ag.execute(); + } + + /** + * 解析实际的配置 + * + * @param value value + * @return value + */ + private static String resolveConfigValue(String value) { + if (Strings.isBlank(value)) { + return value; + } + Matcher matcher = ENV_VAR_PATTERN.matcher(value); + StringBuffer resultString = new StringBuffer(); + while (matcher.find()) { + // 环境变量名 + String envVar = matcher.group(1); + // 默认值 + String defaultValue = matcher.group(2); + // 获取环境变量的值 + String envValue = Systems.getEnv(envVar, defaultValue); + // 替换占位符 + matcher.appendReplacement(resultString, Matcher.quoteReplacement(envValue)); + } + // 处理结尾的剩余部分 + matcher.appendTail(resultString); + return resultString.toString(); + } + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/utils/DataQuery.java b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/utils/DataQuery.java new file mode 100644 index 0000000..623726d --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/java/cn/orionsec/ops/utils/DataQuery.java @@ -0,0 +1,99 @@ + +package cn.orionsec.ops.utils; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import cn.orionsec.kit.lang.define.wrapper.Pager; +import cn.orionsec.kit.lang.utils.Valid; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.convert.Converts; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class DataQuery { + + private final BaseMapper dao; + + private PageRequest page; + + private LambdaQueryWrapper wrapper; + + private DataQuery(BaseMapper dao) { + this.dao = dao; + } + + public static DataQuery of(BaseMapper dao) { + Valid.notNull(dao, "dao is null"); + return new DataQuery<>(dao); + } + + public DataQuery page(PageRequest page) { + this.page = Valid.notNull(page, "page is null"); + return this; + } + + public DataQuery wrapper(LambdaQueryWrapper wrapper) { + this.wrapper = Valid.notNull(wrapper, "wrapper is null"); + return this; + } + + public Optional get() { + return Optional.ofNullable(dao.selectOne(wrapper)); + } + + public Optional get(Class c) { + return Optional.ofNullable(dao.selectOne(wrapper)) + .map(s -> Converts.to(s, c)); + } + + public Stream list() { + return dao.selectList(wrapper).stream(); + } + + public List list(Class c) { + return Converts.toList(dao.selectList(wrapper), c); + } + + public Integer count() { + return dao.selectCount(wrapper); + } + + public boolean present() { + return dao.selectCount(wrapper) > 0; + } + + public DataGrid dataGrid() { + return this.dataGrid(Function.identity()); + } + + public DataGrid dataGrid(Class c) { + return this.dataGrid(t -> Converts.to(t, c)); + } + + public DataGrid dataGrid(Function convert) { + Valid.notNull(convert, "convert is null"); + Valid.notNull(page, "page is null"); + Valid.notNull(wrapper, "wrapper is null"); + Integer count = dao.selectCount(wrapper); + Pager pager = new Pager<>(page); + pager.setTotal(count); + boolean next = pager.hasMoreData(); + if (next) { + wrapper.last(pager.getSql()); + List rows = dao.selectList(wrapper).stream() + .map(convert) + .collect(Collectors.toList()); + pager.setRows(rows); + } else { + pager.setRows(Lists.empty()); + } + return DataGrid.of(pager); + } + +} diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/AlarmGroupMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/AlarmGroupMapper.xml new file mode 100644 index 0000000..b4b8e41 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/AlarmGroupMapper.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + id + , group_name, group_description, deleted, create_time, update_time + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/AlarmGroupNotifyMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/AlarmGroupNotifyMapper.xml new file mode 100644 index 0000000..a3ee534 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/AlarmGroupNotifyMapper.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + id + , group_id, notify_id, notify_type, deleted, create_time, update_time + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/AlarmGroupUserMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/AlarmGroupUserMapper.xml new file mode 100644 index 0000000..085b402 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/AlarmGroupUserMapper.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + id + , group_id, user_id, username, deleted, create_time, update_time + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationActionLogMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationActionLogMapper.xml new file mode 100644 index 0000000..584f229 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationActionLogMapper.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + id, stage_type, rel_id, machine_id, action_id, action_name, action_type, action_command, log_path, run_status, + exit_code, start_time, end_time, deleted, create_time, update_time + + + + + SELECT id, rel_id, run_status, start_time, end_time, exit_code + FROM application_action_log + + + + + + + + + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationActionMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationActionMapper.xml new file mode 100644 index 0000000..4ba6afa --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationActionMapper.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + id, app_id, profile_id, action_name, action_type, stage_type, action_command, deleted, create_time, update_time + + + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationBuildMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationBuildMapper.xml new file mode 100644 index 0000000..d514348 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationBuildMapper.xml @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id, app_id, app_name, app_tag, profile_id, profile_name, profile_tag, + build_seq, branch_name, commit_id, repo_id, log_path, bundle_path, build_status, description, + create_user_id, create_user_name, build_start_time, build_end_time, deleted, create_time, update_time + + + + + + + + + + + + + + + + + + + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationEnvMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationEnvMapper.xml new file mode 100644 index 0000000..77593ef --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationEnvMapper.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + id, app_id, profile_id, attr_key, attr_value, system_env, description, deleted, create_time, update_time + + + + + + UPDATE application_env + SET deleted = #{deleted} + WHERE id = #{id} + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationInfoMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationInfoMapper.xml new file mode 100644 index 0000000..a6239aa --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationInfoMapper.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + id, app_name, app_tag, app_sort, repo_id, description, deleted, create_time, update_time + + + + + + UPDATE application_info + SET repo_id = null + WHERE repo_id = #{repoId} + AND deleted = 1 + + + + + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationMachineMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationMachineMapper.xml new file mode 100644 index 0000000..0df3a6e --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationMachineMapper.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + id, app_id, profile_id, machine_id, release_id, build_id, build_seq, deleted, create_time, update_time + + + + UPDATE application_machine + SET release_id = #{releaseId}, + build_id = #{buildId}, + build_seq = #{buildSeq} + WHERE app_id = #{appId} + AND profile_id = #{profileId} + AND machine_id = #{machineId} + AND deleted = 1 + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationPipelineDetailMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationPipelineDetailMapper.xml new file mode 100644 index 0000000..c919754 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationPipelineDetailMapper.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + id, pipeline_id, app_id, profile_id, stage_type, deleted, create_time, update_time + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationPipelineMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationPipelineMapper.xml new file mode 100644 index 0000000..7c430ab --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationPipelineMapper.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + id, profile_id, pipeline_name, description, deleted, create_time, update_time + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationPipelineTaskDetailMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationPipelineTaskDetailMapper.xml new file mode 100644 index 0000000..ac63015 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationPipelineTaskDetailMapper.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + id, pipeline_id, pipeline_detail_id, task_id, rel_id, app_id, app_name, app_tag, stage_type, stage_config, exec_status, exec_start_time, exec_end_time, deleted, create_time, update_time + + + + id, task_id, rel_id, exec_status, exec_start_time, exec_end_time + + + + + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationPipelineTaskLogMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationPipelineTaskLogMapper.xml new file mode 100644 index 0000000..b1ceda2 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationPipelineTaskLogMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + id, task_id, task_detail_id, log_status, stage_type, log_info, deleted, create_time, update_time + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationPipelineTaskMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationPipelineTaskMapper.xml new file mode 100644 index 0000000..13978c0 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationPipelineTaskMapper.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id, pipeline_id, pipeline_name, profile_id, profile_name, profile_tag, exec_title, exec_description, exec_status, timed_exec, timed_exec_time, create_user_id, create_user_name, audit_user_id, audit_user_name, audit_time, audit_reason, exec_user_id, exec_user_name, exec_start_time, exec_end_time, deleted, create_time, update_time + + + + id, exec_status, exec_start_time, exec_end_time + + + + UPDATE application_pipeline_task + SET timed_exec_time = NULL + WHERE id = #{id} + + + + + + + + + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationProfileMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationProfileMapper.xml new file mode 100644 index 0000000..df9ff85 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationProfileMapper.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + id, profile_name, profile_tag, description, release_audit, deleted, create_time, update_time + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationReleaseMachineMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationReleaseMachineMapper.xml new file mode 100644 index 0000000..087a138 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationReleaseMachineMapper.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + id, release_id, machine_id, machine_name, machine_tag, machine_host, run_status, log_path, start_time, end_time, deleted, create_time, update_time + + + + id, release_id, run_status, start_time, end_time + + + + + + + + + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationReleaseMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationReleaseMapper.xml new file mode 100644 index 0000000..63bc1d9 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationReleaseMapper.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id, release_title, release_description, build_id, build_seq, app_id, app_name, app_tag, profile_id, profile_name, profile_tag, + release_type, release_status, release_serialize, exception_handler, bundle_path, transfer_path, transfer_mode, rollback_release_id, timed_release, timed_release_time, create_user_id, create_user_name, audit_user_id, audit_user_name, audit_time, audit_reason, + release_start_time, release_end_time, release_user_id, release_user_name, action_config, deleted, create_time, update_time + + + + id, release_status, release_start_time, release_end_time + + + + UPDATE application_release + SET timed_release_time = NULL + WHERE id = #{id} + + + + + + + + + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationRepositoryMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationRepositoryMapper.xml new file mode 100644 index 0000000..6b8961a --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ApplicationRepositoryMapper.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + id, repo_name, repo_description, repo_type, repo_url, repo_username, repo_password, repo_private_token, + repo_status, repo_auth_type, repo_token_type, deleted, create_time, update_time + + + + + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/CommandExecMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/CommandExecMapper.xml new file mode 100644 index 0000000..973596f --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/CommandExecMapper.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id, user_id, user_name, exec_type, machine_id, machine_name, machine_host, machine_tag, exec_status, exit_code, exec_command, + description, log_path, start_date, end_date, deleted, create_time, update_time + + + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/CommandTemplateMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/CommandTemplateMapper.xml new file mode 100644 index 0000000..f4e0fd1 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/CommandTemplateMapper.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + id, template_name, template_value, description, create_user_id, create_user_name, + update_user_id, update_user_name, deleted, create_time, update_time + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/FileTailListMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/FileTailListMapper.xml new file mode 100644 index 0000000..4fb951d --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/FileTailListMapper.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + id, machine_id, alias_name, file_path, file_charset, file_offset, tail_command, tail_mode, deleted, create_time, update_time + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/FileTransferLogMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/FileTransferLogMapper.xml new file mode 100644 index 0000000..83d9540 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/FileTransferLogMapper.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + id, user_id, user_name, file_token, transfer_type, machine_id, remote_file, local_file, current_size, file_size, now_progress, transfer_status, deleted, create_time, update_time + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/HistoryValueSnapshotMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/HistoryValueSnapshotMapper.xml new file mode 100644 index 0000000..654059d --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/HistoryValueSnapshotMapper.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + id, value_id, operator_type, value_type, before_value, after_value, update_user_id, update_user_name, create_time, update_time + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineAlarmConfigMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineAlarmConfigMapper.xml new file mode 100644 index 0000000..beb2442 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineAlarmConfigMapper.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + id + , machine_id, alarm_type, alarm_threshold, trigger_threshold, notify_silence, deleted, create_time, update_time + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineAlarmGroupMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineAlarmGroupMapper.xml new file mode 100644 index 0000000..e747f37 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineAlarmGroupMapper.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + id + , machine_id, group_id, deleted, create_time, update_time + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineAlarmHistoryMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineAlarmHistoryMapper.xml new file mode 100644 index 0000000..c12cff9 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineAlarmHistoryMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + id + , machine_id, alarm_type, alarm_value, alarm_time, deleted, create_time, update_time + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineEnvMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineEnvMapper.xml new file mode 100644 index 0000000..82df557 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineEnvMapper.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + id, machine_id, attr_key, attr_value, description, deleted, create_time, update_time + + + + + + UPDATE machine_env + SET deleted = #{deleted} + WHERE id = #{id} + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineGroupMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineGroupMapper.xml new file mode 100644 index 0000000..137bb74 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineGroupMapper.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + id + , parent_id, group_name, sort, deleted, create_time, update_time + + + + UPDATE machine_group + SET sort = sort + 1 + WHERE parent_id = #{parentId} + + AND sort > #{greaterSort} + + + + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineGroupRelMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineGroupRelMapper.xml new file mode 100644 index 0000000..f4489de --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineGroupRelMapper.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + id + , group_id, machine_id, deleted, create_time, update_time + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineInfoMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineInfoMapper.xml new file mode 100644 index 0000000..3e65004 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineInfoMapper.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + id, proxy_id, machine_host, ssh_port, machine_name, machine_tag, description, username, password, auth_type, machine_status, deleted, create_time, update_time + + + + UPDATE machine_info + SET id = #{newId} + WHERE id = #{oldId} + + + + UPDATE machine_info + SET proxy_id = null + WHERE proxy_id = #{proxyId} + + + + + + + + + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineMonitorMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineMonitorMapper.xml new file mode 100644 index 0000000..c039d26 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineMonitorMapper.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id, machine_id, monitor_status, monitor_url, access_token, agent_version, deleted, create_time, update_time + + + + + WHERE deleted = 1 + AND machine_status = 1 + + AND machine_name LIKE concat('%', #{query.machineName}, '%') + + + AND machine_id = #{query.machineId} + + + AND monitor_status = #{query.monitorStatus} + + + + + SELECT m.id id, + IFNULL(m.monitor_status, 1) monitor_status, + m.monitor_url monitor_url, + m.access_token access_token, + m.agent_version agent_version, + i.id machine_id, + i.machine_name machine_name, + i.machine_host machine_host, + i.machine_status machine_status, + i.deleted deleted + FROM machine_monitor m + RIGHT JOIN machine_info i ON i.id = m.machine_id + + + + + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineProxyMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineProxyMapper.xml new file mode 100644 index 0000000..97901dc --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineProxyMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + id, proxy_host, proxy_port, proxy_username, proxy_password, description, deleted, create_time, update_time + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineSecretKeyMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineSecretKeyMapper.xml new file mode 100644 index 0000000..dcab3f6 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineSecretKeyMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + id + , key_name, secret_key_path, password, description, deleted, create_time, update_time + + + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineTerminalLogMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineTerminalLogMapper.xml new file mode 100644 index 0000000..2a29965 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineTerminalLogMapper.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + id, user_id, username, machine_id, machine_name, machine_host, machine_tag, access_token, connected_time, disconnected_time, close_code, screen_path, create_time, update_time + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineTerminalMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineTerminalMapper.xml new file mode 100644 index 0000000..f39425f --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/MachineTerminalMapper.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + id, machine_id, terminal_type, background_color, font_color, font_size, font_family, + enable_web_link, deleted, create_time, update_time + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ScheduleTaskMachineMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ScheduleTaskMachineMapper.xml new file mode 100644 index 0000000..3933f82 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ScheduleTaskMachineMapper.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + id, task_id, machine_id, deleted, create_time, update_time + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ScheduleTaskMachineRecordMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ScheduleTaskMachineRecordMapper.xml new file mode 100644 index 0000000..4c9b1cc --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ScheduleTaskMachineRecordMapper.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + id, task_id, record_id, task_machine_id, machine_id, machine_name, machine_host, machine_tag, exec_command, + exec_status, exit_code, deleted, log_path, start_time, end_time, create_time, update_time + + + + + id, record_id, exec_status, exit_code, start_time, end_time + + + + + + + + + + UPDATE scheduler_task_machine_record + SET deleted = 2 + WHERE record_id IN + + #{recordId} + + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ScheduleTaskMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ScheduleTaskMapper.xml new file mode 100644 index 0000000..02089f6 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ScheduleTaskMapper.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + id, task_name, description, task_command, expression, enable_status, lately_status, serialize_type, exception_handler, lately_schedule_time, deleted, create_time, update_time + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ScheduleTaskRecordMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ScheduleTaskRecordMapper.xml new file mode 100644 index 0000000..73af256 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/ScheduleTaskRecordMapper.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id, task_id, task_name, task_status, deleted, start_time, end_time, create_time, update_time + + + + + id, task_status, start_time, end_time + + + + + + + + + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/SystemEnvMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/SystemEnvMapper.xml new file mode 100644 index 0000000..94461f4 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/SystemEnvMapper.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + id, attr_key, attr_value, system_env, description, deleted, create_time, update_time + + + + + + UPDATE system_env + SET deleted = #{deleted} + WHERE id = #{id} + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/UserEventLogMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/UserEventLogMapper.xml new file mode 100644 index 0000000..25df2f0 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/UserEventLogMapper.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + id, user_id, username, event_classify, event_type, log_info, params_json, exec_result, deleted, create_time + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/UserInfoMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/UserInfoMapper.xml new file mode 100644 index 0000000..b9fdcb5 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/UserInfoMapper.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + id, username, nickname, password, salt, role_type, user_status, lock_status, failed_login_count, avatar_pic, contact_phone, contact_email, last_login_time, deleted, create_time, update_time + + + + UPDATE user_info + SET last_login_time = now(), + update_time = now() + WHERE id = #{userId} + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/WebSideMessageMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/WebSideMessageMapper.xml new file mode 100644 index 0000000..2588381 --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/WebSideMessageMapper.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + id + , message_classify, message_type, read_status, to_user_id, to_user_name, send_message, deleted, rel_id, create_time, update_time + + + diff --git a/orion-ops-api/orion-ops-dao/src/main/resources/mapper/WebhookConfigMapper.xml b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/WebhookConfigMapper.xml new file mode 100644 index 0000000..7e89fda --- /dev/null +++ b/orion-ops-api/orion-ops-dao/src/main/resources/mapper/WebhookConfigMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + id + , webhook_name, webhook_url, webhook_type, webhook_config, deleted, create_time, update_time + + + diff --git a/orion-ops-api/orion-ops-data/pom.xml b/orion-ops-api/orion-ops-data/pom.xml new file mode 100644 index 0000000..347a9b1 --- /dev/null +++ b/orion-ops-api/orion-ops-data/pom.xml @@ -0,0 +1,30 @@ + + + + cn.orionsec.ops + orion-ops-api + 1.3.1 + ../pom.xml + + + orion-ops-data + orion-ops-data + 4.0.0 + + + + + cn.orionsec.kit + orion-all + + + + + cn.orionsec.ops + orion-ops-service + ${project.version} + + + + diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/DataModuleConversionProvider.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/DataModuleConversionProvider.java new file mode 100644 index 0000000..3215ee9 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/DataModuleConversionProvider.java @@ -0,0 +1,5 @@ + +package cn.orionsec.ops; + +public class DataModuleConversionProvider { +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/constant/DataClearRange.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/constant/DataClearRange.java new file mode 100644 index 0000000..ed4c636 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/constant/DataClearRange.java @@ -0,0 +1,43 @@ + +package cn.orionsec.ops.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; + + +@Getter +@AllArgsConstructor +public enum DataClearRange { + + /** + * 保留天数 + */ + DAY(10), + + /** + * 保留条数 + */ + TOTAL(20), + + /** + * 关联数据 + */ + REL_ID(30), + + ; + + private final Integer range; + + public static DataClearRange of(Integer range) { + if (range == null) { + return null; + } + for (DataClearRange value : values()) { + if (value.range.equals(range)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/constant/DataClearType.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/constant/DataClearType.java new file mode 100644 index 0000000..388b067 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/constant/DataClearType.java @@ -0,0 +1,70 @@ + +package cn.orionsec.ops.constant; + +import lombok.AllArgsConstructor; +import lombok.Getter; + + +@Getter +@AllArgsConstructor +public enum DataClearType { + + /** + * 批量执行命令记录 + */ + BATCH_EXEC(10, "批量执行命令记录"), + + /** + * 终端日志记录 + */ + TERMINAL_LOG(20, "终端日志记录"), + + /** + * 定时调度任务执行记录 + */ + SCHEDULER_RECORD(30, "定时调度任务执行记录"), + + /** + * 应用构建记录 + */ + APP_BUILD(40, "应用构建记录"), + + /** + * 应用发布记录 + */ + APP_RELEASE(50, "应用发布记录"), + + /** + * 应用流水线执行记录 + */ + APP_PIPELINE_EXEC(60, "应用流水线执行记录"), + + /** + * 用户操作日志 + */ + USER_EVENT_LOG(70, "用户操作日志记录"), + + /** + * 机器报警历史记录 + */ + MACHINE_ALARM_HISTORY(80, "机器报警历史记录"), + + ; + + private final Integer type; + + private final String label; + + public static DataClearType of(Integer type) { + if (type == null) { + return null; + } + for (DataClearType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/constant/ExportConst.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/constant/ExportConst.java new file mode 100644 index 0000000..102a430 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/constant/ExportConst.java @@ -0,0 +1,45 @@ + +package cn.orionsec.ops.constant; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.time.Dates; + + +public class ExportConst { + + private ExportConst() { + } + + public static final String MACHINE_EXPORT_NAME = "机器信息导出-{}.xlsx"; + + public static final String MACHINE_PROXY_EXPORT_NAME = "机器代理导出-{}.xlsx"; + + public static final String MACHINE_ALARM_HISTORY_EXPORT_NAME = "机器报警记录导出-{}.xlsx"; + + public static final String TERMINAL_LOG_EXPORT_NAME = "终端日志导出-{}.xlsx"; + + public static final String TAIL_FILE_EXPORT_NAME = "日志文件导出-{}.xlsx"; + + public static final String APP_PROFILE_EXPORT_NAME = "应用环境导出-{}.xlsx"; + + public static final String APPLICATION_EXPORT_NAME = "应用信息导出-{}.xlsx"; + + public static final String APP_REPOSITORY_EXPORT_NAME = "应用版本仓库导出-{}.xlsx"; + + public static final String COMMAND_TEMPLATE_EXPORT_NAME = "命令模板导出-{}.xlsx"; + + public static final String USER_EVENT_LOG_EXPORT_NAME = "操作日志导出-{}.xlsx"; + + public static final String WEBHOOK_EXPORT_NAME = "webhook导出-{}.xlsx"; + + /** + * 格式导出文件名 + * + * @param name name + * @return name + */ + public static String getFileName(String name) { + return Strings.format(name, Dates.current(Dates.YMD_HM2)); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/constant/ExportType.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/constant/ExportType.java new file mode 100644 index 0000000..fda8f95 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/constant/ExportType.java @@ -0,0 +1,124 @@ + +package cn.orionsec.ops.constant; + +import cn.orionsec.ops.entity.exporter.*; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.function.Supplier; + +@Getter +@AllArgsConstructor +public enum ExportType { + + /** + * 机器信息 + */ + MACHINE_INFO(100, + "机器信息", + MachineInfoExportDTO.class, + () -> ExportConst.getFileName(ExportConst.MACHINE_EXPORT_NAME)), + + /** + * 机器代理 + */ + MACHINE_PROXY(110, + "机器代理", + MachineProxyExportDTO.class, + () -> ExportConst.getFileName(ExportConst.MACHINE_PROXY_EXPORT_NAME)), + + /** + * 终端日志 + */ + TERMINAL_LOG(120, + "终端日志", + MachineTerminalLogExportDTO.class, + () -> ExportConst.getFileName(ExportConst.TERMINAL_LOG_EXPORT_NAME)), + + /** + * 机器报警记录 + */ + MACHINE_ALARM_HISTORY(130, + "机器报警记录", + MachineAlarmHistoryExportDTO.class, + () -> ExportConst.getFileName(ExportConst.MACHINE_ALARM_HISTORY_EXPORT_NAME)), + + /** + * 应用环境 + */ + APP_PROFILE(200, + "应用环境", + ApplicationProfileExportDTO.class, + () -> ExportConst.getFileName(ExportConst.APP_PROFILE_EXPORT_NAME)), + + /** + * 应用信息 + */ + APPLICATION(210, + "应用信息", + ApplicationExportDTO.class, + () -> ExportConst.getFileName(ExportConst.APPLICATION_EXPORT_NAME)), + + /** + * 应用仓库 + */ + APP_REPOSITORY(220, + "应用仓库", + ApplicationRepositoryExportDTO.class, + () -> ExportConst.getFileName(ExportConst.APP_REPOSITORY_EXPORT_NAME)), + + /** + * 命令模板 + */ + COMMAND_TEMPLATE(300, + "命令模板", + CommandTemplateExportDTO.class + , () -> ExportConst.getFileName(ExportConst.COMMAND_TEMPLATE_EXPORT_NAME)), + + /** + * 用户操作日志 + */ + USER_EVENT_LOG(310, + "用户操作日志", + EventLogExportDTO.class, + () -> ExportConst.getFileName(ExportConst.USER_EVENT_LOG_EXPORT_NAME)), + + /** + * 日志文件 + */ + TAIL_FILE(320, + "日志文件", + MachineTailFileExportDTO.class, + () -> ExportConst.getFileName(ExportConst.TAIL_FILE_EXPORT_NAME)), + + /** + * webhook + */ + WEBHOOK(330, + "webhook", + WebhookExportDTO.class, + () -> ExportConst.getFileName(ExportConst.WEBHOOK_EXPORT_NAME)), + + ; + + private final Integer type; + + private final String label; + + private final Class dataClass; + + private final Supplier nameSupplier; + + public static ExportType of(Integer type) { + if (type == null) { + return null; + } + for (ExportType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/constant/ImportType.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/constant/ImportType.java new file mode 100644 index 0000000..44637b4 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/constant/ImportType.java @@ -0,0 +1,150 @@ + +package cn.orionsec.ops.constant; + +import cn.orionsec.ops.entity.domain.*; +import cn.orionsec.ops.entity.importer.*; +import cn.orionsec.ops.handler.importer.validator.*; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.io.Serializable; + +@Getter +@AllArgsConstructor +public enum ImportType { + + /** + * 机器信息 + */ + MACHINE_INFO(100, + "机器信息", + "/templates/import/machine-import-template.xlsx", + "机器导入模板.xlsx", + MachineValidator.INSTANCE, + MachineInfoImportDTO.class, + MachineInfoDO.class), + + /** + * 机器代理 + */ + MACHINE_PROXY(110, + "机器代理", + "/templates/import/machine-proxy-import-template.xlsx", + "机器代理导入模板.xlsx", + MachineProxyValidator.INSTANCE, + MachineProxyImportDTO.class, + MachineProxyDO.class), + + /** + * 日志文件 + */ + TAIL_FILE(120, + "日志文件", + "/templates/import/tail-file-import-template.xlsx", + "日志文件导入模板.xlsx", + FileTailValidator.INSTANCE, + MachineTailFileImportDTO.class, + FileTailListDO.class), + + /** + * 应用环境 + */ + APP_PROFILE(200, + "应用环境", + "/templates/import/app-profile-import-template.xlsx", + "应用环境导入模板.xlsx", + ApplicationProfileValidator.INSTANCE, + ApplicationProfileImportDTO.class, + ApplicationProfileDO.class), + + /** + * 应用信息 + */ + APPLICATION(210, + "应用信息", + "/templates/import/application-import-template.xlsx", + "应用导入模板.xlsx", + ApplicationValidator.INSTANCE, + ApplicationImportDTO.class, + ApplicationInfoDO.class), + + /** + * 应用仓库 + */ + APP_REPOSITORY(220, + "版本仓库", + "/templates/import/app-repository-import-template.xlsx", + "应用仓库导入模板.xlsx", + ApplicationRepositoryValidator.INSTANCE, + ApplicationRepositoryImportDTO.class, + ApplicationRepositoryDO.class), + + /** + * 命令模板 + */ + COMMAND_TEMPLATE(300, + "命令模板", + "/templates/import/command-template-import-template.xlsx", + "命令模板导入模板.xlsx", + CommandTemplateValidator.INSTANCE, + CommandTemplateImportDTO.class, + CommandTemplateDO.class), + + /** + * webhook + */ + WEBHOOK(310, + "webhook", + "/templates/import/webhook-import-template.xlsx", + "webhook导入模板.xlsx", + WebhookValidator.INSTANCE, + WebhookImportDTO.class, + WebhookConfigDO.class), + + ; + + /** + * 类型 + */ + private final Integer type; + + /** + * 导入标签 + */ + private final String label; + + /** + * 文件路径 + */ + private final String templatePath; + + /** + * 下载名称 + */ + private final String templateName; + + /** + * 数据验证器 + */ + private final DataValidator validator; + + /** + * importClass + */ + private final Class importClass; + + private final Class convertClass; + + public static ImportType of(Integer type) { + if (type == null) { + return null; + } + for (ImportType value : values()) { + if (value.type.equals(type)) { + return value; + } + } + return null; + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/ApplicationExportDTO.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/ApplicationExportDTO.java new file mode 100644 index 0000000..7cb6ce3 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/ApplicationExportDTO.java @@ -0,0 +1,50 @@ + +package cn.orionsec.ops.entity.exporter; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.office.excel.annotation.ExportField; +import cn.orionsec.kit.office.excel.annotation.ExportSheet; +import cn.orionsec.kit.office.excel.annotation.ExportTitle; +import cn.orionsec.ops.entity.domain.ApplicationInfoDO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + + +@Data +@ApiModel(value = "应用信息导出") +@ExportTitle(title = "应用信息导出") +@ExportSheet(name = "应用信息", height = 22, freezeHeader = true, filterHeader = true) +public class ApplicationExportDTO { + + @ApiModelProperty(value = "应用名称") + @ExportField(index = 0, header = "应用名称", width = 35) + private String name; + + @ApiModelProperty(value = "唯一标识") + @ExportField(index = 1, header = "唯一标识", width = 30) + private String tag; + + @ApiModelProperty(value = "应用仓库名称") + @ExportField(index = 2, header = "应用仓库名称", width = 30) + private String repoName; + + @ApiModelProperty(value = "描述") + @ExportField(index = 3, header = "描述", width = 35, wrapText = true) + private String description; + + @ApiModelProperty(value = "仓库id", hidden = true) + private Long repoId; + + static { + TypeStore.STORE.register(ApplicationInfoDO.class, ApplicationExportDTO.class, p -> { + ApplicationExportDTO dto = new ApplicationExportDTO(); + dto.setName(p.getAppName()); + dto.setTag(p.getAppTag()); + dto.setRepoId(p.getRepoId()); + dto.setDescription(p.getDescription()); + return dto; + }); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/ApplicationProfileExportDTO.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/ApplicationProfileExportDTO.java new file mode 100644 index 0000000..0df9576 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/ApplicationProfileExportDTO.java @@ -0,0 +1,49 @@ + +package cn.orionsec.ops.entity.exporter; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.office.excel.annotation.ExportField; +import cn.orionsec.kit.office.excel.annotation.ExportSheet; +import cn.orionsec.kit.office.excel.annotation.ExportTitle; +import cn.orionsec.kit.office.excel.type.ExcelAlignType; +import cn.orionsec.ops.constant.CnConst; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.entity.domain.ApplicationProfileDO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "应用环境信息导出") +@ExportTitle(title = "应用环境导出") +@ExportSheet(name = "应用环境", height = 22, freezeHeader = true, filterHeader = true) +public class ApplicationProfileExportDTO { + + @ApiModelProperty(value = "环境名称") + @ExportField(index = 0, header = "环境名称", width = 30) + private String name; + + @ApiModelProperty(value = "唯一标识") + @ExportField(index = 1, header = "唯一标识", width = 30) + private String tag; + + @ApiModelProperty(value = "发布审核") + @ExportField(index = 2, header = "发布审核", width = 17, align = ExcelAlignType.CENTER, selectOptions = {CnConst.OPEN, CnConst.CLOSE}) + private String releaseAudit; + + @ApiModelProperty(value = "描述") + @ExportField(index = 3, header = "描述", width = 35, wrapText = true) + private String description; + + static { + TypeStore.STORE.register(ApplicationProfileDO.class, ApplicationProfileExportDTO.class, p -> { + ApplicationProfileExportDTO dto = new ApplicationProfileExportDTO(); + dto.setName(p.getProfileName()); + dto.setTag(p.getProfileTag()); + dto.setReleaseAudit(Const.ENABLE.equals(p.getReleaseAudit()) ? CnConst.OPEN : CnConst.CLOSE); + dto.setDescription(p.getDescription()); + return dto; + }); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/ApplicationRepositoryExportDTO.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/ApplicationRepositoryExportDTO.java new file mode 100644 index 0000000..7ef7c90 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/ApplicationRepositoryExportDTO.java @@ -0,0 +1,93 @@ + +package cn.orionsec.ops.entity.exporter; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.office.excel.annotation.ExportField; +import cn.orionsec.kit.office.excel.annotation.ExportSheet; +import cn.orionsec.kit.office.excel.annotation.ExportTitle; +import cn.orionsec.ops.constant.CnConst; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.app.RepositoryAuthType; +import cn.orionsec.ops.constant.app.RepositoryTokenType; +import cn.orionsec.ops.entity.domain.ApplicationRepositoryDO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Optional; + + +@Data +@ApiModel(value = "应用仓库导出") +@ExportTitle(title = "应用仓库导出") +@ExportSheet(name = "应用仓库", titleHeight = 22, headerHeight = 22, freezeHeader = true, filterHeader = true) +public class ApplicationRepositoryExportDTO { + + @ApiModelProperty(value = "名称") + @ExportField(index = 0, header = "名称", width = 20) + private String name; + + @ApiModelProperty(value = "url") + @ExportField(index = 1, header = "url", width = 40, wrapText = true) + private String url; + + /** + * @see RepositoryAuthType + */ + @ApiModelProperty(value = "认证方式(密码/令牌)") + @ExportField(index = 2, header = "认证方式(密码/令牌)", width = 23, selectOptions = {CnConst.PASSWORD, CnConst.TOKEN}) + private String authType; + + /** + * @see RepositoryTokenType + */ + @ApiModelProperty(value = "令牌类型") + @ExportField(index = 3, header = "令牌类型", width = 13, selectOptions = {Const.GITHUB, Const.GITEE, Const.GITLAB}) + private String tokenType; + + @ApiModelProperty(value = "用户名") + @ExportField(index = 4, header = "用户名", width = 20) + private String username; + + @ApiModelProperty(value = "导出密码/令牌 (密文)") + @ExportField(index = 5, header = "导出密码/令牌", width = 21, hidden = true, wrapText = true) + private String encryptAuthValue; + + @ApiModelProperty(value = "导入密码/令牌 (明文)") + @ExportField(index = 6, header = "导入密码/令牌", width = 21, wrapText = true) + private String importAuthValue; + + @ApiModelProperty(value = "描述") + @ExportField(index = 7, header = "描述", width = 25, wrapText = true) + private String description; + + static { + TypeStore.STORE.register(ApplicationRepositoryDO.class, ApplicationRepositoryExportDTO.class, p -> { + ApplicationRepositoryExportDTO dto = new ApplicationRepositoryExportDTO(); + dto.setName(p.getRepoName()); + dto.setUrl(p.getRepoUrl()); + // 认证方式 + RepositoryAuthType authType = RepositoryAuthType.of(p.getRepoAuthType()); + if (authType != null) { + dto.setAuthType(authType.getLabel()); + } + // 令牌类型 + if (RepositoryAuthType.TOKEN.equals(authType)) { + Optional.ofNullable(p.getRepoTokenType()) + .map(RepositoryTokenType::of) + .map(RepositoryTokenType::getLabel) + .ifPresent(dto::setTokenType); + if (RepositoryTokenType.GITEE.getLabel().equals(dto.getTokenType())) { + dto.setUsername(p.getRepoUsername()); + } + dto.setEncryptAuthValue(p.getRepoPrivateToken()); + } else { + dto.setUsername(p.getRepoUsername()); + dto.setEncryptAuthValue(p.getRepoPassword()); + } + dto.setDescription(p.getRepoDescription()); + return dto; + }); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/CommandTemplateExportDTO.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/CommandTemplateExportDTO.java new file mode 100644 index 0000000..026a6cd --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/CommandTemplateExportDTO.java @@ -0,0 +1,41 @@ + +package cn.orionsec.ops.entity.exporter; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.office.excel.annotation.ExportField; +import cn.orionsec.kit.office.excel.annotation.ExportSheet; +import cn.orionsec.kit.office.excel.annotation.ExportTitle; +import cn.orionsec.ops.entity.domain.CommandTemplateDO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "命令模板导出") +@ExportTitle(title = "命令模板导出") +@ExportSheet(name = "命令模板", titleHeight = 22, headerHeight = 22, freezeHeader = true, filterHeader = true) +public class CommandTemplateExportDTO { + + @ApiModelProperty(value = "模板名称") + @ExportField(index = 0, header = "模板名称", width = 25) + private String name; + + @ApiModelProperty(value = "模板命令") + @ExportField(index = 1, header = "模板命令", width = 80, wrapText = true) + private String template; + + @ApiModelProperty(value = "描述") + @ExportField(index = 2, header = "描述", width = 35, wrapText = true) + private String description; + + static { + TypeStore.STORE.register(CommandTemplateDO.class, CommandTemplateExportDTO.class, p -> { + CommandTemplateExportDTO dto = new CommandTemplateExportDTO(); + dto.setName(p.getTemplateName()); + dto.setTemplate(p.getTemplateValue()); + dto.setDescription(p.getDescription()); + return dto; + }); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/EventLogExportDTO.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/EventLogExportDTO.java new file mode 100644 index 0000000..911f08a --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/EventLogExportDTO.java @@ -0,0 +1,76 @@ + +package cn.orionsec.ops.entity.exporter; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.kit.office.excel.annotation.ExportField; +import cn.orionsec.kit.office.excel.annotation.ExportSheet; +import cn.orionsec.kit.office.excel.annotation.ExportTitle; +import cn.orionsec.kit.office.excel.type.ExcelFieldType; +import cn.orionsec.ops.constant.event.EventClassify; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.entity.domain.UserEventLogDO; +import cn.orionsec.ops.utils.Utils; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.Optional; + +@Data +@ApiModel(value = "站内信导出") +@ExportTitle(title = "操作日志导出") +@ExportSheet(name = "操作日志", height = 22, freezeHeader = true, filterHeader = true) +public class EventLogExportDTO { + + @ApiModelProperty(value = "用户名") + @ExportField(index = 0, header = "用户名", width = 15, wrapText = true) + private String username; + + /** + * @see EventClassify + */ + @ApiModelProperty(value = "事件分类") + @ExportField(index = 1, header = "事件分类", width = 17) + private String classify; + + /** + * @see EventType + */ + @ApiModelProperty(value = "事件类型") + @ExportField(index = 2, header = "事件类型", width = 22) + private String type; + + @ApiModelProperty(value = "触发时间") + @ExportField(index = 3, header = "触发时间", width = 20, wrapText = true, type = ExcelFieldType.DATE, format = Dates.YMD_HMS) + private Date time; + + @ApiModelProperty(value = "日志信息") + @ExportField(index = 4, header = "日志信息", width = 70, wrapText = true) + private String message; + + @ApiModelProperty(value = "参数") + @ExportField(index = 5, header = "参数", width = 20, wrapText = true) + private String params; + + static { + TypeStore.STORE.register(UserEventLogDO.class, EventLogExportDTO.class, p -> { + EventLogExportDTO dto = new EventLogExportDTO(); + dto.setUsername(p.getUsername()); + Optional.ofNullable(p.getEventClassify()) + .map(EventClassify::of) + .map(EventClassify::getLabel) + .ifPresent(dto::setClassify); + Optional.ofNullable(p.getEventType()) + .map(EventType::of) + .map(EventType::getLabel) + .ifPresent(dto::setType); + dto.setTime(p.getCreateTime()); + dto.setMessage(Utils.cleanStainTag(p.getLogInfo())); + dto.setParams(p.getParamsJson()); + return dto; + }); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/MachineAlarmHistoryExportDTO.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/MachineAlarmHistoryExportDTO.java new file mode 100644 index 0000000..a9431d0 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/MachineAlarmHistoryExportDTO.java @@ -0,0 +1,62 @@ + +package cn.orionsec.ops.entity.exporter; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.kit.office.excel.annotation.ExportField; +import cn.orionsec.kit.office.excel.annotation.ExportFont; +import cn.orionsec.kit.office.excel.annotation.ExportSheet; +import cn.orionsec.kit.office.excel.annotation.ExportTitle; +import cn.orionsec.ops.constant.machine.MachineAlarmType; +import cn.orionsec.ops.entity.domain.MachineAlarmHistoryDO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "机器报警记录导出") +@ExportTitle(title = "机器报警记录导出") +@ExportSheet(name = "机器报警记录", height = 22, freezeHeader = true, filterHeader = true) +public class MachineAlarmHistoryExportDTO { + + @ApiModelProperty(value = "报警机器名称") + @ExportField(index = 0, header = "报警机器名称", width = 24, wrapText = true) + private String name; + + @ApiModelProperty(value = "报警机器主机") + @ExportField(index = 1, header = "报警机器主机", width = 20, wrapText = true) + private String host; + + /** + * @see MachineAlarmType + */ + @ApiModelProperty(value = "报警类型 10: cpu使用率 20: 内存使用率") + @ExportField(index = 2, header = "报警类型", width = 18, wrapText = true) + private String alarmType; + + @ApiModelProperty(value = "报警值") + @ExportField(index = 3, header = "报警值", width = 15, wrapText = true) + @ExportFont(color = "#F5222D") + private Double alarmValue; + + @ApiModelProperty(value = "报警时间") + @ExportField(index = 4, header = "报警时间", width = 25, format = Dates.YMD_HMS) + private Date alarmTime; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + static { + TypeStore.STORE.register(MachineAlarmHistoryDO.class, MachineAlarmHistoryExportDTO.class, p -> { + MachineAlarmHistoryExportDTO dto = new MachineAlarmHistoryExportDTO(); + dto.setMachineId(p.getMachineId()); + dto.setAlarmType(MachineAlarmType.of(p.getAlarmType()).getLabel()); + dto.setAlarmValue(p.getAlarmValue()); + dto.setAlarmTime(p.getAlarmTime()); + return dto; + }); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/MachineInfoExportDTO.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/MachineInfoExportDTO.java new file mode 100644 index 0000000..7c97b97 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/MachineInfoExportDTO.java @@ -0,0 +1,88 @@ + +package cn.orionsec.ops.entity.exporter; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.office.excel.annotation.ExportField; +import cn.orionsec.kit.office.excel.annotation.ExportSheet; +import cn.orionsec.kit.office.excel.annotation.ExportTitle; +import cn.orionsec.ops.constant.CnConst; +import cn.orionsec.ops.constant.machine.MachineAuthType; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "机器信息导出") +@ExportTitle(title = "机器信息导出") +@ExportSheet(name = "机器信息", height = 22, freezeHeader = true, filterHeader = true) +public class MachineInfoExportDTO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "机器名称") + @ExportField(index = 0, header = "机器名称", width = 20, wrapText = true) + private String name; + + @ApiModelProperty(value = "唯一标识") + @ExportField(index = 1, header = "唯一标识", width = 20, wrapText = true) + private String tag; + + @ApiModelProperty(value = "机器主机") + @ExportField(index = 2, header = "机器主机", width = 20, wrapText = true) + private String host; + + @ApiModelProperty(value = "ssh 端口") + @ExportField(index = 3, header = "ssh 端口", width = 10) + private Integer port; + + /** + * @see MachineAuthType + */ + @ApiModelProperty(value = "认证方式") + @ExportField(index = 4, header = "认证方式", width = 13, selectOptions = {CnConst.PASSWORD, CnConst.SECRET_KEY}) + private String authType; + + @ApiModelProperty(value = "用户名") + @ExportField(index = 5, header = "用户名", width = 20, wrapText = true) + private String username; + + @ApiModelProperty(value = "导出密码") + @ExportField(index = 6, header = "导出密码", hidden = true, width = 16, wrapText = true) + private String encryptPassword; + + @ApiModelProperty(value = "导入密码") + @ExportField(index = 7, header = "导入密码", width = 16, wrapText = true) + private String importPassword; + + @ApiModelProperty(value = "密钥名称") + @ExportField(index = 8, header = "密钥名称", width = 16, wrapText = true) + private String keyName; + + @ApiModelProperty(value = "描述") + @ExportField(index = 9, header = "描述", width = 25, wrapText = true) + private String description; + + static { + TypeStore.STORE.register(MachineInfoDO.class, MachineInfoExportDTO.class, p -> { + MachineInfoExportDTO dto = new MachineInfoExportDTO(); + dto.setId(p.getId()); + dto.setName(p.getMachineName()); + dto.setTag(p.getMachineTag()); + dto.setHost(p.getMachineHost()); + dto.setPort(p.getSshPort()); + dto.setUsername(p.getUsername()); + MachineAuthType authType = MachineAuthType.of(p.getAuthType()); + if (authType != null) { + dto.setAuthType(authType.getLabel()); + } + if (MachineAuthType.PASSWORD.equals(authType)) { + dto.setEncryptPassword(p.getPassword()); + } + dto.setDescription(p.getDescription()); + return dto; + }); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/MachineProxyExportDTO.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/MachineProxyExportDTO.java new file mode 100644 index 0000000..99f7618 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/MachineProxyExportDTO.java @@ -0,0 +1,70 @@ + +package cn.orionsec.ops.entity.exporter; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.office.excel.annotation.ExportField; +import cn.orionsec.kit.office.excel.annotation.ExportSheet; +import cn.orionsec.kit.office.excel.annotation.ExportTitle; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.machine.ProxyType; +import cn.orionsec.ops.entity.domain.MachineProxyDO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Optional; + +@Data +@ApiModel(value = "机器代理导出") +@ExportTitle(title = "机器代理导出") +@ExportSheet(name = "机器代理", height = 22, freezeHeader = true, filterHeader = true) +public class MachineProxyExportDTO { + + @ApiModelProperty(value = "代理主机") + @ExportField(index = 0, header = "代理主机", width = 25) + private String host; + + @ApiModelProperty(value = "代理端口") + @ExportField(index = 1, header = "代理端口", width = 10) + private Integer port; + + /** + * @see ProxyType + */ + @ApiModelProperty(value = "代理类型") + @ExportField(index = 2, header = "代理类型", width = 13, selectOptions = {Const.PROTOCOL_HTTP, Const.SOCKS4, Const.SOCKS5}) + private String proxyType; + + @ApiModelProperty(value = "用户名") + @ExportField(index = 3, header = "用户名", width = 25) + private String username; + + @ApiModelProperty(value = "导出密码") + @ExportField(index = 4, header = "导出密码", hidden = true, width = 16, wrapText = true) + private String encryptPassword; + + @ApiModelProperty(value = "导入密码") + @ExportField(index = 5, header = "导入密码", width = 16, wrapText = true) + private String importPassword; + + @ApiModelProperty(value = "描述") + @ExportField(index = 6, header = "描述", width = 35, wrapText = true) + private String description; + + static { + TypeStore.STORE.register(MachineProxyDO.class, MachineProxyExportDTO.class, p -> { + MachineProxyExportDTO dto = new MachineProxyExportDTO(); + dto.setHost(p.getProxyHost()); + dto.setPort(p.getProxyPort()); + Optional.ofNullable(p.getProxyType()) + .map(ProxyType::of) + .map(ProxyType::getLabel) + .ifPresent(dto::setProxyType); + dto.setUsername(p.getProxyUsername()); + dto.setEncryptPassword(p.getProxyPassword()); + dto.setDescription(p.getDescription()); + return dto; + }); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/MachineTailFileExportDTO.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/MachineTailFileExportDTO.java new file mode 100644 index 0000000..261469a --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/MachineTailFileExportDTO.java @@ -0,0 +1,63 @@ + +package cn.orionsec.ops.entity.exporter; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.office.excel.annotation.ExportField; +import cn.orionsec.kit.office.excel.annotation.ExportSheet; +import cn.orionsec.kit.office.excel.annotation.ExportTitle; +import cn.orionsec.ops.entity.domain.FileTailListDO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "日志文件导出") +@ExportTitle(title = "日志文件导出") +@ExportSheet(name = "日志文件", titleHeight = 22, headerHeight = 22, freezeHeader = true, filterHeader = true) +public class MachineTailFileExportDTO { + + @ApiModelProperty(value = "机器名称") + @ExportField(index = 0, header = "机器名称", width = 20, wrapText = true) + private String machineName; + + @ApiModelProperty(value = "机器标识") + @ExportField(index = 1, header = "机器标识", width = 20, wrapText = true) + private String machineTag; + + @ApiModelProperty(value = "文件别名") + @ExportField(index = 2, header = "文件别名", width = 20, wrapText = true) + private String name; + + @ApiModelProperty(value = "文件路径") + @ExportField(index = 3, header = "文件路径", width = 50, wrapText = true) + private String path; + + @ApiModelProperty(value = "文件编码") + @ExportField(index = 4, header = "文件编码", width = 12) + private String charset; + + @ApiModelProperty(value = "尾部偏移行") + @ExportField(index = 5, header = "尾部偏移行", width = 12) + private Integer offset; + + @ApiModelProperty(value = "执行命令") + @ExportField(index = 6, header = "执行命令", width = 35, wrapText = true) + private String command; + + @ApiModelProperty(value = "机器id", hidden = true) + private Long machineId; + + static { + TypeStore.STORE.register(FileTailListDO.class, MachineTailFileExportDTO.class, p -> { + MachineTailFileExportDTO dto = new MachineTailFileExportDTO(); + dto.setMachineId(p.getMachineId()); + dto.setName(p.getAliasName()); + dto.setPath(p.getFilePath()); + dto.setCharset(p.getFileCharset()); + dto.setOffset(p.getFileOffset()); + dto.setCommand(p.getTailCommand()); + return dto; + }); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/MachineTerminalLogExportDTO.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/MachineTerminalLogExportDTO.java new file mode 100644 index 0000000..ce920cd --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/MachineTerminalLogExportDTO.java @@ -0,0 +1,75 @@ + +package cn.orionsec.ops.entity.exporter; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.kit.office.excel.annotation.ExportField; +import cn.orionsec.kit.office.excel.annotation.ExportSheet; +import cn.orionsec.kit.office.excel.annotation.ExportTitle; +import cn.orionsec.kit.office.excel.type.ExcelFieldType; +import cn.orionsec.ops.entity.domain.MachineTerminalLogDO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "终端日志导出") +@ExportTitle(title = "终端日志导出") +@ExportSheet(name = "终端日志", height = 22, freezeHeader = true, filterHeader = true) +public class MachineTerminalLogExportDTO { + + @ApiModelProperty(value = "用户名") + @ExportField(index = 0, header = "用户名", width = 17, wrapText = true) + private String username; + + @ApiModelProperty(value = "机器名称") + @ExportField(index = 1, header = "机器名称", width = 20, wrapText = true) + private String machineName; + + @ApiModelProperty(value = "机器标识") + @ExportField(index = 2, header = "机器标识", width = 20, wrapText = true) + private String machineTag; + + @ApiModelProperty(value = "机器主机") + @ExportField(index = 3, header = "机器主机", width = 20, wrapText = true) + private String machineHost; + + @ApiModelProperty(value = "accessToken") + @ExportField(index = 4, header = "accessToken", width = 5, hidden = true, wrapText = true) + private String accessToken; + + @ApiModelProperty(value = "建立连接时间") + @ExportField(index = 5, header = "建立连接时间", width = 20, wrapText = true, type = ExcelFieldType.DATE, format = Dates.YMD_HMS) + private Date connectedTime; + + @ApiModelProperty(value = "断开连接时间") + @ExportField(index = 6, header = "断开连接时间", width = 20, wrapText = true, type = ExcelFieldType.DATE, format = Dates.YMD_HMS) + private Date disconnectedTime; + + @ApiModelProperty(value = "close code") + @ExportField(index = 7, header = "close code", width = 11, wrapText = true) + private Integer closeCode; + + @ApiModelProperty(value = "录屏文件路径") + @ExportField(index = 8, header = "录屏文件路径", width = 35, wrapText = true) + private String screenPath; + + static { + TypeStore.STORE.register(MachineTerminalLogDO.class, MachineTerminalLogExportDTO.class, p -> { + MachineTerminalLogExportDTO dto = new MachineTerminalLogExportDTO(); + dto.setUsername(p.getUsername()); + dto.setMachineName(p.getMachineName()); + dto.setMachineTag(p.getMachineTag()); + dto.setMachineHost(p.getMachineHost()); + dto.setAccessToken(p.getAccessToken()); + dto.setConnectedTime(p.getConnectedTime()); + dto.setDisconnectedTime(p.getDisconnectedTime()); + dto.setCloseCode(p.getCloseCode()); + dto.setScreenPath(p.getScreenPath()); + return dto; + }); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/WebhookExportDTO.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/WebhookExportDTO.java new file mode 100644 index 0000000..cb99968 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/exporter/WebhookExportDTO.java @@ -0,0 +1,45 @@ + +package cn.orionsec.ops.entity.exporter; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.office.excel.annotation.ExportField; +import cn.orionsec.kit.office.excel.annotation.ExportSheet; +import cn.orionsec.kit.office.excel.annotation.ExportTitle; +import cn.orionsec.ops.constant.webhook.WebhookType; +import cn.orionsec.ops.entity.domain.WebhookConfigDO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "webhook导出") +@ExportTitle(title = "webhook导出") +@ExportSheet(name = "webhook", height = 22, freezeHeader = true, filterHeader = true) +public class WebhookExportDTO { + + @ApiModelProperty(value = "名称") + @ExportField(index = 0, header = "名称", width = 20, wrapText = true) + private String name; + + /** + * @see WebhookType + */ + @ApiModelProperty(value = "类型") + @ExportField(index = 1, header = "类型", width = 20, wrapText = true) + private String type; + + @ApiModelProperty(value = "url") + @ExportField(index = 2, header = "url", width = 80, wrapText = true) + private String url; + + static { + TypeStore.STORE.register(WebhookConfigDO.class, WebhookExportDTO.class, p -> { + WebhookExportDTO dto = new WebhookExportDTO(); + dto.setName(p.getWebhookName()); + dto.setType(WebhookType.of(p.getWebhookType()).getLabel()); + dto.setUrl(p.getWebhookUrl()); + return dto; + }); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/ApplicationImportDTO.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/ApplicationImportDTO.java new file mode 100644 index 0000000..0f68ae9 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/ApplicationImportDTO.java @@ -0,0 +1,56 @@ + +package cn.orionsec.ops.entity.importer; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.office.excel.annotation.ImportField; +import cn.orionsec.ops.entity.domain.ApplicationInfoDO; +import cn.orionsec.ops.entity.vo.data.DataImportCheckRowVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "应用信息导入") +public class ApplicationImportDTO extends BaseDataImportDTO { + + @ApiModelProperty(value = "应用名称") + @ImportField(index = 0) + private String name; + + @ApiModelProperty(value = "唯一标识") + @ImportField(index = 1) + private String tag; + + @ApiModelProperty(value = "应用仓库名称") + @ImportField(index = 2) + private String repositoryName; + + @ApiModelProperty(value = "描述") + @ImportField(index = 3) + private String description; + + @ApiModelProperty(value = "仓库id", hidden = true) + private Long repositoryId; + + static { + TypeStore.STORE.register(ApplicationImportDTO.class, DataImportCheckRowVO.class, p -> { + DataImportCheckRowVO vo = new DataImportCheckRowVO(); + vo.setSymbol(p.tag); + vo.setIllegalMessage(p.getIllegalMessage()); + vo.setId(p.getId()); + return vo; + }); + TypeStore.STORE.register(ApplicationImportDTO.class, ApplicationInfoDO.class, p -> { + ApplicationInfoDO d = new ApplicationInfoDO(); + d.setId(p.getId()); + d.setAppName(p.name); + d.setAppTag(p.tag); + d.setRepoId(p.repositoryId); + d.setDescription(p.description); + return d; + }); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/ApplicationProfileImportDTO.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/ApplicationProfileImportDTO.java new file mode 100644 index 0000000..3dff051 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/ApplicationProfileImportDTO.java @@ -0,0 +1,55 @@ + +package cn.orionsec.ops.entity.importer; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.office.excel.annotation.ImportField; +import cn.orionsec.ops.constant.CnConst; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.entity.domain.ApplicationProfileDO; +import cn.orionsec.ops.entity.vo.data.DataImportCheckRowVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "应用环境信息导入") +public class ApplicationProfileImportDTO extends BaseDataImportDTO { + + @ApiModelProperty(value = "环境名称") + @ImportField(index = 0) + private String name; + + @ApiModelProperty(value = "唯一标识") + @ImportField(index = 1) + private String tag; + + @ApiModelProperty(value = "发布审核") + @ImportField(index = 2) + private String releaseAudit; + + @ApiModelProperty(value = "描述") + @ImportField(index = 3) + private String description; + + static { + TypeStore.STORE.register(ApplicationProfileImportDTO.class, DataImportCheckRowVO.class, p -> { + DataImportCheckRowVO vo = new DataImportCheckRowVO(); + vo.setSymbol(p.tag); + vo.setIllegalMessage(p.getIllegalMessage()); + vo.setId(p.getId()); + return vo; + }); + TypeStore.STORE.register(ApplicationProfileImportDTO.class, ApplicationProfileDO.class, p -> { + ApplicationProfileDO d = new ApplicationProfileDO(); + d.setId(p.getId()); + d.setProfileName(p.name); + d.setProfileTag(p.tag); + d.setReleaseAudit(CnConst.OPEN.equals(p.releaseAudit) ? Const.ENABLE : Const.DISABLE); + d.setDescription(p.description); + return d; + }); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/ApplicationRepositoryImportDTO.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/ApplicationRepositoryImportDTO.java new file mode 100644 index 0000000..8b34414 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/ApplicationRepositoryImportDTO.java @@ -0,0 +1,109 @@ + +package cn.orionsec.ops.entity.importer; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.office.excel.annotation.ImportField; +import cn.orionsec.ops.constant.app.RepositoryAuthType; +import cn.orionsec.ops.constant.app.RepositoryTokenType; +import cn.orionsec.ops.constant.app.RepositoryType; +import cn.orionsec.ops.entity.domain.ApplicationRepositoryDO; +import cn.orionsec.ops.entity.vo.data.DataImportCheckRowVO; +import cn.orionsec.ops.utils.ValueMix; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Optional; +import java.util.function.BiConsumer; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "应用仓库导入") +public class ApplicationRepositoryImportDTO extends BaseDataImportDTO { + + @ApiModelProperty(value = "名称") + @ImportField(index = 0) + private String name; + + @ApiModelProperty(value = "url") + @ImportField(index = 1) + private String url; + + /** + * @see RepositoryAuthType + */ + @ApiModelProperty(value = "认证方式") + @ImportField(index = 2) + private String authType; + + /** + * @see RepositoryTokenType + */ + @ApiModelProperty(value = "令牌类型") + @ImportField(index = 3) + private String tokenType; + + @ApiModelProperty(value = "用户名") + @ImportField(index = 4) + private String username; + + @ApiModelProperty(value = "导出密码/令牌 (密文)") + @ImportField(index = 5) + private String encryptAuthValue; + + @ApiModelProperty(value = "导入密码/令牌 (明文)") + @ImportField(index = 6) + private String importAuthValue; + + @ApiModelProperty(value = "描述") + @ImportField(index = 7) + private String description; + + @ApiModelProperty(value = "密码/令牌密文解密后的明文") + private String decryptAuthValue; + + static { + TypeStore.STORE.register(ApplicationRepositoryImportDTO.class, DataImportCheckRowVO.class, p -> { + DataImportCheckRowVO vo = new DataImportCheckRowVO(); + vo.setSymbol(p.name); + vo.setIllegalMessage(p.getIllegalMessage()); + vo.setId(p.getId()); + return vo; + }); + TypeStore.STORE.register(ApplicationRepositoryImportDTO.class, ApplicationRepositoryDO.class, p -> { + ApplicationRepositoryDO d = new ApplicationRepositoryDO(); + d.setId(p.getId()); + d.setRepoName(p.name); + d.setRepoDescription(p.description); + d.setRepoType(RepositoryType.GIT.getType()); + d.setRepoUrl(p.url); + Optional.ofNullable(p.authType) + .map(RepositoryAuthType::of) + .map(RepositoryAuthType::getType) + .ifPresent(d::setRepoAuthType); + if (RepositoryAuthType.PASSWORD.getType().equals(d.getRepoAuthType()) || RepositoryTokenType.GITEE.getLabel().equals(p.tokenType)) { + d.setRepoUsername(p.username); + } + Optional.ofNullable(p.tokenType) + .map(RepositoryTokenType::of) + .map(RepositoryTokenType::getType) + .ifPresent(d::setRepoTokenType); + BiConsumer authValueSetter; + if (RepositoryAuthType.PASSWORD.getType().equals(d.getRepoAuthType())) { + authValueSetter = ApplicationRepositoryDO::setRepoPassword; + } else { + authValueSetter = ApplicationRepositoryDO::setRepoPrivateToken; + } + if (!Strings.isBlank(p.decryptAuthValue)) { + authValueSetter.accept(d, ValueMix.encrypt(p.decryptAuthValue)); + } + if (!Strings.isBlank(p.importAuthValue)) { + authValueSetter.accept(d, ValueMix.encrypt(p.importAuthValue)); + } + return d; + }); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/BaseDataImportDTO.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/BaseDataImportDTO.java new file mode 100644 index 0000000..d0f0481 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/BaseDataImportDTO.java @@ -0,0 +1,19 @@ + +package cn.orionsec.ops.entity.importer; + +import lombok.Data; + +@Data +public class BaseDataImportDTO { + + /** + * 非法信息 非法操作 + */ + private String illegalMessage; + + /** + * 数据id 更新操作 + */ + private Long id; + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/CommandTemplateImportDTO.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/CommandTemplateImportDTO.java new file mode 100644 index 0000000..a666b30 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/CommandTemplateImportDTO.java @@ -0,0 +1,49 @@ + +package cn.orionsec.ops.entity.importer; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.office.excel.annotation.ImportField; +import cn.orionsec.ops.entity.domain.CommandTemplateDO; +import cn.orionsec.ops.entity.vo.data.DataImportCheckRowVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "命令模板导入") +public class CommandTemplateImportDTO extends BaseDataImportDTO { + + @ApiModelProperty(value = "模板名称") + @ImportField(index = 0) + private String name; + + @ApiModelProperty(value = "模板命令") + @ImportField(index = 1) + private String template; + + @ApiModelProperty(value = "描述") + @ImportField(index = 2) + private String description; + + static { + TypeStore.STORE.register(CommandTemplateImportDTO.class, DataImportCheckRowVO.class, p -> { + DataImportCheckRowVO vo = new DataImportCheckRowVO(); + vo.setSymbol(p.name); + vo.setIllegalMessage(p.getIllegalMessage()); + vo.setId(p.getId()); + return vo; + }); + TypeStore.STORE.register(CommandTemplateImportDTO.class, CommandTemplateDO.class, p -> { + CommandTemplateDO d = new CommandTemplateDO(); + d.setId(p.getId()); + d.setTemplateName(p.name); + d.setTemplateValue(p.template); + d.setDescription(p.description); + return d; + }); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/DataImportDTO.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/DataImportDTO.java new file mode 100644 index 0000000..71a8afd --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/DataImportDTO.java @@ -0,0 +1,50 @@ + +package cn.orionsec.ops.entity.importer; + +import cn.orionsec.ops.constant.ImportType; +import cn.orionsec.ops.entity.vo.data.DataImportCheckVO; +import lombok.Data; + +import java.util.Date; + +@Data +public class DataImportDTO { + + /** + * token + */ + private String importToken; + + /** + * 类型 + * + * @see ImportType + */ + private Integer type; + + /** + * 导入数据 json + */ + private String data; + + /** + * 检查数据 + */ + private DataImportCheckVO check; + + /** + * 导入用户id 手动 + */ + private Long userId; + + /** + * 导入用户名称 手动 + */ + private String userName; + + /** + * 导入时间 手动 + */ + private Date importTime; + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/MachineInfoImportDTO.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/MachineInfoImportDTO.java new file mode 100644 index 0000000..7fb284b --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/MachineInfoImportDTO.java @@ -0,0 +1,106 @@ + +package cn.orionsec.ops.entity.importer; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.office.excel.annotation.ImportField; +import cn.orionsec.ops.constant.machine.MachineAuthType; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.vo.data.DataImportCheckRowVO; +import cn.orionsec.ops.utils.ValueMix; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Optional; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "机器信息导入") +public class MachineInfoImportDTO extends BaseDataImportDTO { + + @ApiModelProperty(value = "机器名称") + @ImportField(index = 0) + private String name; + + @ApiModelProperty(value = "唯一标识") + @ImportField(index = 1) + private String tag; + + @ApiModelProperty(value = "机器主机") + @ImportField(index = 2) + private String host; + + @ApiModelProperty(value = "ssh 端口") + @ImportField(index = 3) + private Integer port; + + /** + * @see MachineAuthType + */ + @ApiModelProperty(value = "认证方式") + @ImportField(index = 4) + private String authType; + + @ApiModelProperty(value = "用户名") + @ImportField(index = 5) + private String username; + + @ApiModelProperty(value = "密码 (密文)") + @ImportField(index = 6) + private String encryptPassword; + + @ApiModelProperty(value = "导入密码 (明文)") + @ImportField(index = 7) + private String importPassword; + + @ApiModelProperty(value = "密钥名称") + @ImportField(index = 8) + private String keyName; + + @ApiModelProperty(value = "描述") + @ImportField(index = 9) + private String description; + + @ApiModelProperty(value = "密码密文解密后的明文", hidden = true) + private String decryptPassword; + + @ApiModelProperty(value = "密钥id", hidden = true) + private Long keyId; + + static { + TypeStore.STORE.register(MachineInfoImportDTO.class, DataImportCheckRowVO.class, p -> { + DataImportCheckRowVO vo = new DataImportCheckRowVO(); + vo.setSymbol(p.tag); + vo.setIllegalMessage(p.getIllegalMessage()); + vo.setId(p.getId()); + return vo; + }); + TypeStore.STORE.register(MachineInfoImportDTO.class, MachineInfoDO.class, p -> { + MachineInfoDO d = new MachineInfoDO(); + d.setId(p.getId()); + d.setMachineName(p.name); + d.setMachineTag(p.tag); + d.setMachineHost(p.host); + d.setSshPort(p.port); + d.setKeyId(p.keyId); + Optional.ofNullable(p.authType) + .map(MachineAuthType::of) + .map(MachineAuthType::getType) + .ifPresent(d::setAuthType); + d.setUsername(p.username); + if (MachineAuthType.PASSWORD.getType().equals(d.getAuthType())) { + if (!Strings.isBlank(p.decryptPassword)) { + d.setPassword(ValueMix.encrypt(p.decryptPassword)); + } + if (!Strings.isBlank(p.importPassword)) { + d.setPassword(ValueMix.encrypt(p.importPassword)); + } + } + d.setDescription(p.description); + return d; + }); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/MachineProxyImportDTO.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/MachineProxyImportDTO.java new file mode 100644 index 0000000..c04693d --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/MachineProxyImportDTO.java @@ -0,0 +1,86 @@ + +package cn.orionsec.ops.entity.importer; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.office.excel.annotation.ImportField; +import cn.orionsec.ops.constant.machine.ProxyType; +import cn.orionsec.ops.entity.domain.MachineProxyDO; +import cn.orionsec.ops.entity.vo.data.DataImportCheckRowVO; +import cn.orionsec.ops.utils.ValueMix; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Optional; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "机器代理导入") +public class MachineProxyImportDTO extends BaseDataImportDTO { + + @ApiModelProperty(value = "代理主机") + @ImportField(index = 0) + private String host; + + @ApiModelProperty(value = "代理端口") + @ImportField(index = 1) + private Integer port; + + /** + * @see ProxyType + */ + @ApiModelProperty(value = "代理类型") + @ImportField(index = 2) + private String proxyType; + + @ApiModelProperty(value = "用户名") + @ImportField(index = 3) + private String username; + + @ApiModelProperty(value = "导出密码 (密文)") + @ImportField(index = 4) + private String encryptPassword; + + @ApiModelProperty(value = "导入密码 (明文)") + @ImportField(index = 5) + private String importPassword; + + @ApiModelProperty(value = "描述") + @ImportField(index = 6) + private String description; + + @ApiModelProperty(value = "密码密文解密后的明文", hidden = true) + private String decryptPassword; + + static { + TypeStore.STORE.register(MachineProxyImportDTO.class, DataImportCheckRowVO.class, p -> { + DataImportCheckRowVO vo = new DataImportCheckRowVO(); + vo.setSymbol(p.host); + vo.setIllegalMessage(p.getIllegalMessage()); + vo.setId(p.getId()); + return vo; + }); + TypeStore.STORE.register(MachineProxyImportDTO.class, MachineProxyDO.class, p -> { + MachineProxyDO d = new MachineProxyDO(); + d.setId(p.getId()); + d.setProxyHost(p.host); + d.setProxyPort(p.port); + d.setProxyUsername(p.username); + if (!Strings.isBlank(p.decryptPassword)) { + d.setProxyPassword(ValueMix.encrypt(p.decryptPassword)); + } + if (!Strings.isBlank(p.importPassword)) { + d.setProxyPassword(ValueMix.encrypt(p.importPassword)); + } + Optional.ofNullable(p.proxyType) + .map(ProxyType::of) + .map(ProxyType::getType) + .ifPresent(d::setProxyType); + d.setDescription(p.description); + return d; + }); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/MachineTailFileImportDTO.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/MachineTailFileImportDTO.java new file mode 100644 index 0000000..cbf0e24 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/MachineTailFileImportDTO.java @@ -0,0 +1,73 @@ + +package cn.orionsec.ops.entity.importer; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.office.excel.annotation.ImportField; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.tail.FileTailMode; +import cn.orionsec.ops.entity.domain.FileTailListDO; +import cn.orionsec.ops.entity.vo.data.DataImportCheckRowVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "机器代理导入") +public class MachineTailFileImportDTO extends BaseDataImportDTO { + + @ApiModelProperty(value = "机器标识") + @ImportField(index = 1) + private String machineTag; + + @ApiModelProperty(value = "别名") + @ImportField(index = 2) + private String name; + + @ApiModelProperty(value = "文件路径") + @ImportField(index = 3) + private String path; + + @ApiModelProperty(value = "文件编码") + @ImportField(index = 4) + private String charset; + + @ApiModelProperty(value = "尾部偏移行") + @ImportField(index = 5) + private Integer offset; + + @ApiModelProperty(value = "执行命令") + @ImportField(index = 6) + private String command; + + @ApiModelProperty(value = "机器id", hidden = true) + private Long machineId; + + static { + TypeStore.STORE.register(MachineTailFileImportDTO.class, DataImportCheckRowVO.class, p -> { + DataImportCheckRowVO vo = new DataImportCheckRowVO(); + vo.setSymbol(p.name); + vo.setIllegalMessage(p.getIllegalMessage()); + vo.setId(p.getId()); + return vo; + }); + TypeStore.STORE.register(MachineTailFileImportDTO.class, FileTailListDO.class, p -> { + FileTailListDO d = new FileTailListDO(); + d.setId(p.getId()); + d.setMachineId(p.machineId); + d.setAliasName(p.name); + d.setFilePath(p.path); + d.setFileCharset(p.charset); + d.setFileOffset(p.offset); + d.setTailCommand(p.command); + if (Const.HOST_MACHINE_ID.equals(p.machineId)) { + d.setTailMode(FileTailMode.TRACKER.getMode()); + } else { + d.setTailMode(FileTailMode.TAIL.getMode()); + } + return d; + }); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/WebhookImportDTO.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/WebhookImportDTO.java new file mode 100644 index 0000000..cb0b9bf --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/entity/importer/WebhookImportDTO.java @@ -0,0 +1,57 @@ + +package cn.orionsec.ops.entity.importer; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.office.excel.annotation.ImportField; +import cn.orionsec.ops.constant.webhook.WebhookType; +import cn.orionsec.ops.entity.domain.WebhookConfigDO; +import cn.orionsec.ops.entity.vo.data.DataImportCheckRowVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Optional; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "webhook 导入") +public class WebhookImportDTO extends BaseDataImportDTO { + + @ApiModelProperty(value = "名称") + @ImportField(index = 0) + private String name; + + /** + * @see WebhookType + */ + @ApiModelProperty(value = "类型") + @ImportField(index = 1) + private String type; + + @ApiModelProperty(value = "url") + @ImportField(index = 2) + private String url; + + static { + TypeStore.STORE.register(WebhookImportDTO.class, DataImportCheckRowVO.class, p -> { + DataImportCheckRowVO vo = new DataImportCheckRowVO(); + vo.setSymbol(p.name); + vo.setIllegalMessage(p.getIllegalMessage()); + vo.setId(p.getId()); + return vo; + }); + TypeStore.STORE.register(WebhookImportDTO.class, WebhookConfigDO.class, p -> { + WebhookConfigDO d = new WebhookConfigDO(); + d.setId(p.getId()); + d.setWebhookName(p.name); + d.setWebhookUrl(p.url); + Optional.ofNullable(p.type) + .map(WebhookType::of) + .map(WebhookType::getType) + .ifPresent(d::setWebhookType); + return d; + }); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/AbstractDataExporter.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/AbstractDataExporter.java new file mode 100644 index 0000000..1b753b8 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/AbstractDataExporter.java @@ -0,0 +1,105 @@ + +package cn.orionsec.ops.handler.exporter; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.io.FileWriters; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.office.excel.writer.exporting.ExcelExport; +import cn.orionsec.kit.web.servlet.web.Servlets; +import cn.orionsec.ops.constant.ExportType; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.entity.request.data.DataExportRequest; +import cn.orionsec.ops.utils.Currents; +import cn.orionsec.ops.utils.EventParamsHolder; +import cn.orionsec.ops.utils.PathBuilders; + +import javax.servlet.http.HttpServletResponse; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; + +public abstract class AbstractDataExporter implements IDataExporter { + + /** + * 导出类型 + */ + protected final ExportType exportType; + + /** + * 请求参数 + */ + protected final DataExportRequest request; + + /** + * http response + */ + protected final HttpServletResponse response; + + protected ExcelExport exporter; + + public AbstractDataExporter(ExportType exportType, DataExportRequest request, HttpServletResponse response) { + this.exportType = exportType; + this.request = request; + this.response = response; + } + + @Override + @SuppressWarnings("unchecked") + public void doExport() throws IOException { + // 查询数据 + List exportList = this.queryData(); + // 初始化导出器 + this.exporter = new ExcelExport((Class) exportType.getDataClass()).init(); + exporter.addRows(exportList); + // 写入到 workbook + this.writeWorkbook(); + // 设置日志参数 + this.setEventParams(); + } + + /** + * 查询数据 + * + * @return rows + */ + protected abstract List queryData(); + + /** + * 写入 workbook + * + * @throws IOException IOException + */ + protected void writeWorkbook() throws IOException { + // 设置 http 响应头 + Servlets.setAttachmentHeader(response, exportType.getNameSupplier().get()); + // 写入 workbook 到 byte + ByteArrayOutputStream store = new ByteArrayOutputStream(); + String password = request.getProtectPassword(); + if (!Strings.isBlank(password)) { + exporter.write(store, password); + } else { + exporter.write(store); + } + // 设置 http 返回 + byte[] excelData = store.toByteArray(); + response.getOutputStream().write(excelData); + // 保存至本地 + String exportPath = PathBuilders.getExportDataJsonPath(Currents.getUserId(), exportType.getType(), Strings.def(password)); + String path = Files1.getPath(SystemEnvAttr.LOG_PATH.getValue(), exportPath); + FileWriters.write(path, excelData); + } + + /** + * 设置日志参数 + */ + protected void setEventParams() { + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.EXPORT_PASSWORD, request.getExportPassword()); + EventParamsHolder.addParam(EventKeys.PROTECT, request.getProtectPassword() != null); + EventParamsHolder.addParam(EventKeys.COUNT, exporter.getRows()); + EventParamsHolder.addParam(EventKeys.EXPORT_TYPE, exportType.getType()); + EventParamsHolder.addParam(EventKeys.LABEL, exportType.getLabel()); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/AppProfileDataExporter.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/AppProfileDataExporter.java new file mode 100644 index 0000000..8ffa585 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/AppProfileDataExporter.java @@ -0,0 +1,30 @@ + +package cn.orionsec.ops.handler.exporter; + +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.ExportType; +import cn.orionsec.ops.dao.ApplicationProfileDAO; +import cn.orionsec.ops.entity.domain.ApplicationProfileDO; +import cn.orionsec.ops.entity.exporter.ApplicationProfileExportDTO; +import cn.orionsec.ops.entity.request.data.DataExportRequest; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +public class AppProfileDataExporter extends AbstractDataExporter { + + private static final ApplicationProfileDAO applicationProfileDAO = SpringHolder.getBean(ApplicationProfileDAO.class); + + public AppProfileDataExporter(DataExportRequest request, HttpServletResponse response) { + super(ExportType.APP_PROFILE, request, response); + } + + @Override + protected List queryData() { + // 查询数据 + List profileList = applicationProfileDAO.selectList(null); + return Converts.toList(profileList, ApplicationProfileExportDTO.class); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/AppRepositoryDataExporter.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/AppRepositoryDataExporter.java new file mode 100644 index 0000000..6840442 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/AppRepositoryDataExporter.java @@ -0,0 +1,35 @@ + +package cn.orionsec.ops.handler.exporter; + +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.ExportType; +import cn.orionsec.ops.dao.ApplicationRepositoryDAO; +import cn.orionsec.ops.entity.domain.ApplicationRepositoryDO; +import cn.orionsec.ops.entity.exporter.ApplicationRepositoryExportDTO; +import cn.orionsec.ops.entity.request.data.DataExportRequest; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +public class AppRepositoryDataExporter extends AbstractDataExporter { + + private static final ApplicationRepositoryDAO applicationRepositoryDAO = SpringHolder.getBean(ApplicationRepositoryDAO.class); + + public AppRepositoryDataExporter(DataExportRequest request, HttpServletResponse response) { + super(ExportType.APP_REPOSITORY, request, response); + } + + @Override + protected List queryData() { + // 查询数据 + List repoList = applicationRepositoryDAO.selectList(null); + List exportList = Converts.toList(repoList, ApplicationRepositoryExportDTO.class); + if (!Const.ENABLE.equals(request.getExportPassword())) { + exportList.forEach(s -> s.setEncryptAuthValue(null)); + } + return exportList; + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/ApplicationDataExporter.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/ApplicationDataExporter.java new file mode 100644 index 0000000..fe806f2 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/ApplicationDataExporter.java @@ -0,0 +1,56 @@ + +package cn.orionsec.ops.handler.exporter; + +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.ExportType; +import cn.orionsec.ops.dao.ApplicationInfoDAO; +import cn.orionsec.ops.dao.ApplicationRepositoryDAO; +import cn.orionsec.ops.entity.domain.ApplicationInfoDO; +import cn.orionsec.ops.entity.domain.ApplicationRepositoryDO; +import cn.orionsec.ops.entity.exporter.ApplicationExportDTO; +import cn.orionsec.ops.entity.request.data.DataExportRequest; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class ApplicationDataExporter extends AbstractDataExporter { + + private static final ApplicationInfoDAO applicationInfoDAO = SpringHolder.getBean(ApplicationInfoDAO.class); + + private static final ApplicationRepositoryDAO applicationRepositoryDAO = SpringHolder.getBean(ApplicationRepositoryDAO.class); + + public ApplicationDataExporter(DataExportRequest request, HttpServletResponse response) { + super(ExportType.APPLICATION, request, response); + } + + @Override + protected List queryData() { + // 查询数据 + List appList = applicationInfoDAO.selectList(null); + List exportList = Converts.toList(appList, ApplicationExportDTO.class); + // 仓库名称 + List repoIdList = appList.stream() + .map(ApplicationInfoDO::getRepoId) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (!repoIdList.isEmpty()) { + List repoNameList = applicationRepositoryDAO.selectNameByIdList(repoIdList); + // 设置仓库名称 + for (ApplicationExportDTO export : exportList) { + Long repoId = export.getRepoId(); + if (repoId == null) { + continue; + } + repoNameList.stream() + .filter(s -> s.getId().equals(repoId)) + .findFirst() + .ifPresent(s -> export.setRepoName(s.getRepoName())); + } + } + return exportList; + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/CommandTemplateDataExporter.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/CommandTemplateDataExporter.java new file mode 100644 index 0000000..480fce5 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/CommandTemplateDataExporter.java @@ -0,0 +1,30 @@ + +package cn.orionsec.ops.handler.exporter; + +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.ExportType; +import cn.orionsec.ops.dao.CommandTemplateDAO; +import cn.orionsec.ops.entity.domain.CommandTemplateDO; +import cn.orionsec.ops.entity.exporter.CommandTemplateExportDTO; +import cn.orionsec.ops.entity.request.data.DataExportRequest; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + + +public class CommandTemplateDataExporter extends AbstractDataExporter { + + private static final CommandTemplateDAO commandTemplateDAO = SpringHolder.getBean(CommandTemplateDAO.class); + + public CommandTemplateDataExporter(DataExportRequest request, HttpServletResponse response) { + super(ExportType.COMMAND_TEMPLATE, request, response); + } + + @Override + protected List queryData() { + List templateList = commandTemplateDAO.selectList(null); + return Converts.toList(templateList, CommandTemplateExportDTO.class); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/IDataExporter.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/IDataExporter.java new file mode 100644 index 0000000..8d56f08 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/IDataExporter.java @@ -0,0 +1,59 @@ + +package cn.orionsec.ops.handler.exporter; + +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.ops.constant.ExportType; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.entity.request.data.DataExportRequest; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + + +public interface IDataExporter { + + /** + * 执行导出 + * + * @throws IOException IOException + */ + void doExport() throws IOException; + + /** + * 创建数据导出器 + * + * @param exportType 导出类型 + * @param request request + * @param response response + * @return exporter + */ + static IDataExporter create(ExportType exportType, DataExportRequest request, HttpServletResponse response) { + switch (exportType) { + case MACHINE_INFO: + return new MachineInfoDataExporter(request, response); + case MACHINE_PROXY: + return new MachineProxyDataExporter(request, response); + case TERMINAL_LOG: + return new TerminalLogDataExporter(request, response); + case MACHINE_ALARM_HISTORY: + return new MachineAlarmHistoryDataExporter(request, response); + case APP_PROFILE: + return new AppProfileDataExporter(request, response); + case APPLICATION: + return new ApplicationDataExporter(request, response); + case APP_REPOSITORY: + return new AppRepositoryDataExporter(request, response); + case COMMAND_TEMPLATE: + return new CommandTemplateDataExporter(request, response); + case USER_EVENT_LOG: + return new UserEventLogDataExporter(request, response); + case TAIL_FILE: + return new TailFileDataExporter(request, response); + case WEBHOOK: + return new WebhookDataExporter(request, response); + default: + throw Exceptions.argument(MessageConst.UNKNOWN_EXPORT_TYPE); + } + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/MachineAlarmHistoryDataExporter.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/MachineAlarmHistoryDataExporter.java new file mode 100644 index 0000000..36ec7b3 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/MachineAlarmHistoryDataExporter.java @@ -0,0 +1,67 @@ + +package cn.orionsec.ops.handler.exporter; + +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.ExportType; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.dao.MachineAlarmHistoryDAO; +import cn.orionsec.ops.dao.MachineInfoDAO; +import cn.orionsec.ops.entity.domain.MachineAlarmHistoryDO; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.exporter.MachineAlarmHistoryExportDTO; +import cn.orionsec.ops.entity.request.data.DataExportRequest; +import cn.orionsec.ops.utils.EventParamsHolder; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class MachineAlarmHistoryDataExporter extends AbstractDataExporter { + + private static final MachineInfoDAO machineInfoDAO = SpringHolder.getBean(MachineInfoDAO.class); + + private static final MachineAlarmHistoryDAO machineAlarmHistoryDAO = SpringHolder.getBean(MachineAlarmHistoryDAO.class); + + public MachineAlarmHistoryDataExporter(DataExportRequest request, HttpServletResponse response) { + super(ExportType.MACHINE_ALARM_HISTORY, request, response); + } + + @Override + protected List queryData() { + // 查询数据 + Long machineId = request.getMachineId(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(Objects.nonNull(machineId), MachineAlarmHistoryDO::getMachineId, machineId) + .orderByDesc(MachineAlarmHistoryDO::getCreateTime); + List history = machineAlarmHistoryDAO.selectList(wrapper); + List historyList = Converts.toList(history, MachineAlarmHistoryExportDTO.class); + // 查询机器名称 + List machineIdList = historyList.stream() + .map(MachineAlarmHistoryExportDTO::getMachineId) + .distinct() + .collect(Collectors.toList()); + if (!machineIdList.isEmpty()) { + List machines = machineInfoDAO.selectNameByIdList(machineIdList); + for (MachineAlarmHistoryExportDTO record : historyList) { + machines.stream() + .filter(s -> s.getId().equals(record.getMachineId())) + .findFirst() + .ifPresent(m -> { + record.setName(m.getMachineName()); + record.setHost(m.getMachineHost()); + }); + } + } + return historyList; + } + + @Override + protected void setEventParams() { + super.setEventParams(); + EventParamsHolder.addParam(EventKeys.MACHINE_ID, request.getMachineId()); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/MachineInfoDataExporter.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/MachineInfoDataExporter.java new file mode 100644 index 0000000..43c7e93 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/MachineInfoDataExporter.java @@ -0,0 +1,59 @@ + +package cn.orionsec.ops.handler.exporter; + +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.ExportType; +import cn.orionsec.ops.dao.MachineInfoDAO; +import cn.orionsec.ops.dao.MachineSecretKeyDAO; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.domain.MachineSecretKeyDO; +import cn.orionsec.ops.entity.exporter.MachineInfoExportDTO; +import cn.orionsec.ops.entity.request.data.DataExportRequest; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +public class MachineInfoDataExporter extends AbstractDataExporter { + + private static final MachineInfoDAO machineInfoDAO = SpringHolder.getBean(MachineInfoDAO.class); + + private static final MachineSecretKeyDAO machineSecretKeyDAO = SpringHolder.getBean(MachineSecretKeyDAO.class); + + public MachineInfoDataExporter(DataExportRequest request, HttpServletResponse response) { + super(ExportType.MACHINE_INFO, request, response); + } + + @Override + protected List queryData() { + // 查询机器信息 + List machineList = machineInfoDAO.selectList(null); + List exportList = Converts.toList(machineList, MachineInfoExportDTO.class); + // 查询密钥 + List keyIdList = machineList.stream() + .map(MachineInfoDO::getKeyId) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + if (!keyIdList.isEmpty()) { + // 查询密钥 + Map keyNameMap = machineSecretKeyDAO.selectBatchIds(keyIdList) + .stream() + .collect(Collectors.toMap(MachineSecretKeyDO::getId, MachineSecretKeyDO::getKeyName)); + // 设置密钥名称 + for (MachineInfoExportDTO machine : exportList) { + machine.setKeyName(keyNameMap.get(machine.getId())); + } + } + // 设置密码 + if (!Const.ENABLE.equals(request.getExportPassword())) { + exportList.forEach(s -> s.setEncryptPassword(null)); + } + return exportList; + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/MachineProxyDataExporter.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/MachineProxyDataExporter.java new file mode 100644 index 0000000..88a9d8f --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/MachineProxyDataExporter.java @@ -0,0 +1,35 @@ + +package cn.orionsec.ops.handler.exporter; + +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.ExportType; +import cn.orionsec.ops.dao.MachineProxyDAO; +import cn.orionsec.ops.entity.domain.MachineProxyDO; +import cn.orionsec.ops.entity.exporter.MachineProxyExportDTO; +import cn.orionsec.ops.entity.request.data.DataExportRequest; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +public class MachineProxyDataExporter extends AbstractDataExporter { + + private static final MachineProxyDAO machineProxyDAO = SpringHolder.getBean(MachineProxyDAO.class); + + public MachineProxyDataExporter(DataExportRequest request, HttpServletResponse response) { + super(ExportType.MACHINE_PROXY, request, response); + } + + @Override + protected List queryData() { + // 查询数据 + List proxyList = machineProxyDAO.selectList(null); + List exportList = Converts.toList(proxyList, MachineProxyExportDTO.class); + if (!Const.ENABLE.equals(request.getExportPassword())) { + exportList.forEach(s -> s.setEncryptPassword(null)); + } + return exportList; + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/TailFileDataExporter.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/TailFileDataExporter.java new file mode 100644 index 0000000..cc8fa01 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/TailFileDataExporter.java @@ -0,0 +1,70 @@ + +package cn.orionsec.ops.handler.exporter; + +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.ExportType; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.dao.FileTailListDAO; +import cn.orionsec.ops.dao.MachineInfoDAO; +import cn.orionsec.ops.entity.domain.FileTailListDO; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.exporter.MachineTailFileExportDTO; +import cn.orionsec.ops.entity.request.data.DataExportRequest; +import cn.orionsec.ops.utils.EventParamsHolder; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class TailFileDataExporter extends AbstractDataExporter { + + private static final MachineInfoDAO machineInfoDAO = SpringHolder.getBean(MachineInfoDAO.class); + + private static final FileTailListDAO fileTailListDAO = SpringHolder.getBean(FileTailListDAO.class); + + public TailFileDataExporter(DataExportRequest request, HttpServletResponse response) { + super(ExportType.TAIL_FILE, request, response); + } + + @Override + protected List queryData() { + // 查询数据 + Long queryMachineId = request.getMachineId(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(Objects.nonNull(queryMachineId), FileTailListDO::getMachineId, queryMachineId); + List fileList = fileTailListDAO.selectList(wrapper); + List exportList = Converts.toList(fileList, MachineTailFileExportDTO.class); + // 机器名称 + List machineIdList = fileList.stream() + .map(FileTailListDO::getMachineId) + .collect(Collectors.toList()); + if (!machineIdList.isEmpty()) { + List machineNameList = machineInfoDAO.selectNameByIdList(machineIdList); + // 设置机器名称 + for (MachineTailFileExportDTO export : exportList) { + Long machineId = export.getMachineId(); + if (machineId == null) { + continue; + } + machineNameList.stream() + .filter(s -> s.getId().equals(machineId)) + .findFirst() + .ifPresent(s -> { + export.setMachineName(s.getMachineName()); + export.setMachineTag(s.getMachineTag()); + }); + } + } + return exportList; + } + + @Override + protected void setEventParams() { + super.setEventParams(); + EventParamsHolder.addParam(EventKeys.MACHINE_ID, request.getMachineId()); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/TerminalLogDataExporter.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/TerminalLogDataExporter.java new file mode 100644 index 0000000..e9ccf1e --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/TerminalLogDataExporter.java @@ -0,0 +1,44 @@ + +package cn.orionsec.ops.handler.exporter; + +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.ExportType; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.dao.MachineTerminalLogDAO; +import cn.orionsec.ops.entity.domain.MachineTerminalLogDO; +import cn.orionsec.ops.entity.exporter.MachineTerminalLogExportDTO; +import cn.orionsec.ops.entity.request.data.DataExportRequest; +import cn.orionsec.ops.utils.EventParamsHolder; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Objects; + +public class TerminalLogDataExporter extends AbstractDataExporter { + + private static final MachineTerminalLogDAO machineTerminalLogDAO = SpringHolder.getBean(MachineTerminalLogDAO.class); + + public TerminalLogDataExporter(DataExportRequest request, HttpServletResponse response) { + super(ExportType.TERMINAL_LOG, request, response); + } + + @Override + protected List queryData() { + // 查询数据 + Long machineId = request.getMachineId(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(Objects.nonNull(machineId), MachineTerminalLogDO::getMachineId, machineId) + .orderByDesc(MachineTerminalLogDO::getCreateTime); + List terminalList = machineTerminalLogDAO.selectList(wrapper); + return Converts.toList(terminalList, MachineTerminalLogExportDTO.class); + } + + @Override + protected void setEventParams() { + super.setEventParams(); + EventParamsHolder.addParam(EventKeys.MACHINE_ID, request.getMachineId()); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/UserEventLogDataExporter.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/UserEventLogDataExporter.java new file mode 100644 index 0000000..3c639ed --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/UserEventLogDataExporter.java @@ -0,0 +1,58 @@ + +package cn.orionsec.ops.handler.exporter; + +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.ExportType; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.dao.UserEventLogDAO; +import cn.orionsec.ops.entity.domain.UserEventLogDO; +import cn.orionsec.ops.entity.exporter.EventLogExportDTO; +import cn.orionsec.ops.entity.request.data.DataExportRequest; +import cn.orionsec.ops.utils.Currents; +import cn.orionsec.ops.utils.EventParamsHolder; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Objects; + +public class UserEventLogDataExporter extends AbstractDataExporter { + + private static final UserEventLogDAO userEventLogDAO = SpringHolder.getBean(UserEventLogDAO.class); + + public UserEventLogDataExporter(DataExportRequest request, HttpServletResponse response) { + super(ExportType.USER_EVENT_LOG, request, response); + // 设置用户id + if (Currents.isAdministrator()) { + if (Const.ENABLE.equals(request.getOnlyMyself())) { + request.setUserId(Currents.getUserId()); + } + } else { + request.setUserId(Currents.getUserId()); + } + } + + @Override + protected List queryData() { + // 查询数据 + Long userId = request.getUserId(); + Integer classify = request.getClassify(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(UserEventLogDO::getExecResult, Const.ENABLE) + .eq(Objects.nonNull(userId), UserEventLogDO::getUserId, userId) + .eq(Objects.nonNull(classify), UserEventLogDO::getEventClassify, classify) + .orderByDesc(UserEventLogDO::getCreateTime); + List logList = userEventLogDAO.selectList(wrapper); + return Converts.toList(logList, EventLogExportDTO.class); + } + + @Override + protected void setEventParams() { + super.setEventParams(); + EventParamsHolder.addParam(EventKeys.USER_ID, request.getUserId()); + EventParamsHolder.addParam(EventKeys.CLASSIFY, request.getClassify()); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/WebhookDataExporter.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/WebhookDataExporter.java new file mode 100644 index 0000000..ac5d14a --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/exporter/WebhookDataExporter.java @@ -0,0 +1,43 @@ + +package cn.orionsec.ops.handler.exporter; + +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.ExportType; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.dao.WebhookConfigDAO; +import cn.orionsec.ops.entity.domain.WebhookConfigDO; +import cn.orionsec.ops.entity.exporter.WebhookExportDTO; +import cn.orionsec.ops.entity.request.data.DataExportRequest; +import cn.orionsec.ops.utils.EventParamsHolder; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Objects; + +public class WebhookDataExporter extends AbstractDataExporter { + + private static final WebhookConfigDAO webhookConfigDAO = SpringHolder.getBean(WebhookConfigDAO.class); + + public WebhookDataExporter(DataExportRequest request, HttpServletResponse response) { + super(ExportType.WEBHOOK, request, response); + } + + @Override + protected List queryData() { + // 查询数据 + Integer type = request.getType(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(Objects.nonNull(type), WebhookConfigDO::getWebhookType, type); + List list = webhookConfigDAO.selectList(wrapper); + return Converts.toList(list, WebhookExportDTO.class); + } + + @Override + protected void setEventParams() { + super.setEventParams(); + EventParamsHolder.addParam(EventKeys.TYPE, request.getType()); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/AbstractDataChecker.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/AbstractDataChecker.java new file mode 100644 index 0000000..6eb1629 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/AbstractDataChecker.java @@ -0,0 +1,261 @@ + +package cn.orionsec.ops.handler.importer.checker; + +import cn.orionsec.kit.lang.id.UUIds; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.office.excel.Excels; +import cn.orionsec.kit.office.excel.reader.ExcelBeanReader; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.ImportType; +import cn.orionsec.ops.constant.KeyConst; +import cn.orionsec.ops.entity.importer.BaseDataImportDTO; +import cn.orionsec.ops.entity.importer.DataImportDTO; +import cn.orionsec.ops.entity.vo.data.DataImportCheckRowVO; +import cn.orionsec.ops.entity.vo.data.DataImportCheckVO; +import cn.orionsec.ops.utils.Currents; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import org.apache.poi.ss.usermodel.Workbook; +import org.springframework.data.redis.core.RedisTemplate; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.stream.Collectors; + + +public abstract class AbstractDataChecker implements IDataChecker { + + private static final RedisTemplate redisTemplate = SpringHolder.getBean("redisTemplate"); + + /** + * 导入类型 + */ + private final ImportType importType; + + /** + * workbook + */ + private final Workbook workbook; + + public AbstractDataChecker(ImportType importType, Workbook workbook) { + this.importType = importType; + this.workbook = workbook; + } + + @Override + public DataImportCheckVO doCheck() { + // 解析数据 + List rows; + try { + rows = this.parserImportWorkbook(); + } catch (Exception e) { + throw Exceptions.parse(e); + } finally { + Excels.close(workbook); + } + // 检查数据 + return this.checkImportData(rows); + } + + /** + * 检查数据 + * + * @param rows rows + * @return check + */ + protected abstract DataImportCheckVO checkImportData(List rows); + + /** + * 解析导入数据 + * + * @return rows + */ + @SuppressWarnings("unchecked") + protected List parserImportWorkbook() { + ExcelBeanReader reader = new ExcelBeanReader(workbook, workbook.getSheetAt(0), (Class) importType.getImportClass()); + return reader.skip(2) + .read() + .getRows(); + } + + /** + * 验证对象合法性 + * + * @param rows rows + */ + protected void validImportRows(List rows) { + for (T row : rows) { + try { + importType.getValidator().validData(row); + } catch (Exception e) { + row.setIllegalMessage(e.getMessage()); + } + } + } + + /** + * 获取导入行已存在的数据 + * + * @param rows rows + * @param rowKeyGetter rowKeyGetter + * @param mapper mapper + * @param domainKeyGetter domainKeyGetter + * @return present domain + */ + protected List getImportRowsPresentValues(List rows, + Function rowKeyGetter, + BaseMapper mapper, + SFunction domainKeyGetter) { + List symbolList = rows.stream() + .filter(s -> Objects.isNull(s.getIllegalMessage())) + .map(rowKeyGetter) + .collect(Collectors.toList()); + if (symbolList.isEmpty()) { + return Lists.empty(); + } else { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .in(domainKeyGetter, symbolList); + return mapper.selectList(wrapper); + } + } + + /** + * 检查导入行是否存在 + * + * @param rows rows + * @param rowKeyGetter rowKeyGetter + * @param presentValues presentValues + * @param presentKeyGetter presentKeyGetter + * @param presentIdGetter presentIdGetter + */ + protected void checkImportRowsPresent(List rows, + Function rowKeyGetter, + List presentValues, + Function presentKeyGetter, + Function presentIdGetter) { + for (T row : rows) { + presentValues.stream() + .filter(p -> presentKeyGetter.apply(p).equals(rowKeyGetter.apply(row))) + .findFirst() + .map(presentIdGetter) + .ifPresent(row::setId); + } + } + + /** + * 设置检查行数据缓存 + * + * @param rows rows + * @return checkData + */ + protected DataImportCheckVO setImportCheckRows(List rows) { + // 设置检查对象 + String dataJson = JSON.toJSONString(rows); + List illegalRows = Lists.newList(); + List insertRows = Lists.newList(); + List updateRows = Lists.newList(); + // 设置行 + for (int i = 0; i < rows.size(); i++) { + // 设置检查数据 + T row = rows.get(i); + DataImportCheckRowVO checkRow = Converts.to(row, DataImportCheckRowVO.class); + checkRow.setIndex(i); + checkRow.setRow(i + 3); + // 检查非法数据 + if (checkRow.getIllegalMessage() != null) { + illegalRows.add(checkRow); + continue; + } + if (checkRow.getId() == null) { + // 不存在则新增 + insertRows.add(checkRow); + } else { + // 存在则修改 + updateRows.add(checkRow); + } + } + // 设置缓存并且返回 + String token = UUIds.random32(); + String cacheKey = Strings.format(KeyConst.DATA_IMPORT_TOKEN, Currents.getUserId(), token); + // 返回数据 + DataImportCheckVO check = new DataImportCheckVO(); + check.setImportToken(token); + check.setIllegalRows(illegalRows); + check.setInsertRows(insertRows); + check.setUpdateRows(updateRows); + // 设置缓存 + DataImportDTO cache = new DataImportDTO(); + cache.setImportToken(token); + cache.setType(importType.getType()); + cache.setData(dataJson); + cache.setCheck(check); + redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(cache), + KeyConst.DATA_IMPORT_TOKEN_EXPIRE, TimeUnit.SECONDS); + return check; + } + + /** + * 设置引用id + * + * @param rows rows + * @param keyGetter keyGetter + * @param query dataQuery + * @param domainKeyGetter domainKeyGetter + * @param domainIdGetter domainIdGetter + * @param relIdSetter relIdSetter + * @param notPresentTemplate notPresentTemplate + * @param key type + * @param

domain type + */ + protected void setCheckRowsRelId(List rows, + Function keyGetter, + Function, List

> query, + Function domainKeyGetter, + Function domainIdGetter, + BiConsumer relIdSetter, + String notPresentTemplate) { + // 获取合法数据 + List validImportList = rows.stream() + .filter(s -> Objects.isNull(s.getIllegalMessage())) + .filter(s -> { + K symbol = keyGetter.apply(s); + return symbol instanceof String ? Strings.isNotBlank((String) symbol) : Objects.nonNull(symbol); + }).collect(Collectors.toList()); + if (validImportList.isEmpty()) { + return; + } + // 获取标识 + List symbolList = validImportList.stream() + .map(keyGetter) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (symbolList.isEmpty()) { + return; + } + // 查询id + Map symbolIdMap = Maps.newMap(); + List

dataList = query.apply(symbolList); + dataList.forEach(s -> symbolIdMap.put(domainKeyGetter.apply(s), domainIdGetter.apply(s))); + // 设置id + for (T row : validImportList) { + K symbol = keyGetter.apply(row); + Long relId = symbolIdMap.get(symbol); + if (relId == null) { + row.setIllegalMessage(Strings.format(notPresentTemplate, symbol)); + continue; + } + relIdSetter.accept(row, relId); + } + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/AppProfileDataChecker.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/AppProfileDataChecker.java new file mode 100644 index 0000000..7fafd12 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/AppProfileDataChecker.java @@ -0,0 +1,37 @@ + +package cn.orionsec.ops.handler.importer.checker; + +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.ImportType; +import cn.orionsec.ops.dao.ApplicationProfileDAO; +import cn.orionsec.ops.entity.domain.ApplicationProfileDO; +import cn.orionsec.ops.entity.importer.ApplicationProfileImportDTO; +import cn.orionsec.ops.entity.vo.data.DataImportCheckVO; +import org.apache.poi.ss.usermodel.Workbook; + +import java.util.List; + +public class AppProfileDataChecker extends AbstractDataChecker { + + private static final ApplicationProfileDAO applicationProfileDAO = SpringHolder.getBean(ApplicationProfileDAO.class); + + public AppProfileDataChecker(Workbook workbook) { + super(ImportType.APP_PROFILE, workbook); + } + + @Override + protected DataImportCheckVO checkImportData(List rows) { + // 检查数据合法性 + this.validImportRows(rows); + // 通过唯一标识查询环境 + List presentProfiles = this.getImportRowsPresentValues(rows, + ApplicationProfileImportDTO::getTag, + applicationProfileDAO, ApplicationProfileDO::getProfileTag); + // 检查数据是否存在 + this.checkImportRowsPresent(rows, ApplicationProfileImportDTO::getTag, + presentProfiles, ApplicationProfileDO::getProfileTag, ApplicationProfileDO::getId); + // 设置导入检查数据 + return this.setImportCheckRows(rows); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/AppRepositoryDataChecker.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/AppRepositoryDataChecker.java new file mode 100644 index 0000000..aaece7a --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/AppRepositoryDataChecker.java @@ -0,0 +1,37 @@ + +package cn.orionsec.ops.handler.importer.checker; + +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.ImportType; +import cn.orionsec.ops.dao.ApplicationRepositoryDAO; +import cn.orionsec.ops.entity.domain.ApplicationRepositoryDO; +import cn.orionsec.ops.entity.importer.ApplicationRepositoryImportDTO; +import cn.orionsec.ops.entity.vo.data.DataImportCheckVO; +import org.apache.poi.ss.usermodel.Workbook; + +import java.util.List; + +public class AppRepositoryDataChecker extends AbstractDataChecker { + + private static final ApplicationRepositoryDAO applicationRepositoryDAO = SpringHolder.getBean(ApplicationRepositoryDAO.class); + + public AppRepositoryDataChecker(Workbook workbook) { + super(ImportType.APP_REPOSITORY, workbook); + } + + @Override + protected DataImportCheckVO checkImportData(List rows) { + // 检查数据合法性 + this.validImportRows(rows); + // 通过唯一标识查询应用 + List presentList = this.getImportRowsPresentValues(rows, + ApplicationRepositoryImportDTO::getName, + applicationRepositoryDAO, ApplicationRepositoryDO::getRepoName); + // 检查数据是否存在 + this.checkImportRowsPresent(rows, ApplicationRepositoryImportDTO::getName, + presentList, ApplicationRepositoryDO::getRepoName, ApplicationRepositoryDO::getId); + // 设置导入检查数据 + return this.setImportCheckRows(rows); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/ApplicationDataChecker.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/ApplicationDataChecker.java new file mode 100644 index 0000000..e56acbe --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/ApplicationDataChecker.java @@ -0,0 +1,49 @@ + +package cn.orionsec.ops.handler.importer.checker; + +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.ImportType; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.dao.ApplicationInfoDAO; +import cn.orionsec.ops.dao.ApplicationRepositoryDAO; +import cn.orionsec.ops.entity.domain.ApplicationInfoDO; +import cn.orionsec.ops.entity.domain.ApplicationRepositoryDO; +import cn.orionsec.ops.entity.importer.ApplicationImportDTO; +import cn.orionsec.ops.entity.vo.data.DataImportCheckVO; +import org.apache.poi.ss.usermodel.Workbook; + +import java.util.List; + +public class ApplicationDataChecker extends AbstractDataChecker { + + private static final ApplicationInfoDAO applicationInfoDAO = SpringHolder.getBean(ApplicationInfoDAO.class); + + private static final ApplicationRepositoryDAO applicationRepositoryDAO = SpringHolder.getBean(ApplicationRepositoryDAO.class); + + public ApplicationDataChecker(Workbook workbook) { + super(ImportType.APPLICATION, workbook); + } + + @Override + protected DataImportCheckVO checkImportData(List rows) { + // 检查数据合法性 + this.validImportRows(rows); + // 设置机器id + this.setCheckRowsRelId(rows, ApplicationImportDTO::getRepositoryName, + applicationRepositoryDAO::selectIdByNameList, + ApplicationRepositoryDO::getRepoName, + ApplicationRepositoryDO::getId, + ApplicationImportDTO::setRepositoryId, + MessageConst.UNKNOWN_APP_REPOSITORY); + // 通过唯一标识查询应用 + List presentApps = this.getImportRowsPresentValues(rows, + ApplicationImportDTO::getTag, + applicationInfoDAO, ApplicationInfoDO::getAppTag); + // 检查数据是否存在 + this.checkImportRowsPresent(rows, ApplicationImportDTO::getTag, + presentApps, ApplicationInfoDO::getAppTag, ApplicationInfoDO::getId); + // 设置导入检查数据 + return this.setImportCheckRows(rows); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/CommandTemplateDataChecker.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/CommandTemplateDataChecker.java new file mode 100644 index 0000000..b2005a7 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/CommandTemplateDataChecker.java @@ -0,0 +1,37 @@ + +package cn.orionsec.ops.handler.importer.checker; + +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.ImportType; +import cn.orionsec.ops.dao.CommandTemplateDAO; +import cn.orionsec.ops.entity.domain.CommandTemplateDO; +import cn.orionsec.ops.entity.importer.CommandTemplateImportDTO; +import cn.orionsec.ops.entity.vo.data.DataImportCheckVO; +import org.apache.poi.ss.usermodel.Workbook; + +import java.util.List; + +public class CommandTemplateDataChecker extends AbstractDataChecker { + + private static final CommandTemplateDAO commandTemplateDAO = SpringHolder.getBean(CommandTemplateDAO.class); + + public CommandTemplateDataChecker(Workbook workbook) { + super(ImportType.COMMAND_TEMPLATE, workbook); + } + + @Override + protected DataImportCheckVO checkImportData(List rows) { + // 检查数据合法性 + this.validImportRows(rows); + // 通过名称查询模板 + List presentTemplates = this.getImportRowsPresentValues(rows, + CommandTemplateImportDTO::getName, + commandTemplateDAO, CommandTemplateDO::getTemplateName); + // 检查数据是否存在 + this.checkImportRowsPresent(rows, CommandTemplateImportDTO::getName, + presentTemplates, CommandTemplateDO::getTemplateName, CommandTemplateDO::getId); + // 设置导入检查数据 + return this.setImportCheckRows(rows); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/IDataChecker.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/IDataChecker.java new file mode 100644 index 0000000..4720c1b --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/IDataChecker.java @@ -0,0 +1,48 @@ + +package cn.orionsec.ops.handler.importer.checker; + +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.ops.constant.ImportType; +import cn.orionsec.ops.entity.vo.data.DataImportCheckVO; +import org.apache.poi.ss.usermodel.Workbook; + +public interface IDataChecker { + + /** + * 检查数据 + * + * @return check info + */ + DataImportCheckVO doCheck(); + + /** + * 创建数据检查器 + * + * @param importType importType + * @param workbook workbook + * @return checker + */ + static IDataChecker create(ImportType importType, Workbook workbook) { + switch (importType) { + case MACHINE_INFO: + return new MachineInfoDataChecker(workbook); + case MACHINE_PROXY: + return new MachineProxyDataChecker(workbook); + case TAIL_FILE: + return new TailFileDataChecker(workbook); + case APP_PROFILE: + return new AppProfileDataChecker(workbook); + case APPLICATION: + return new ApplicationDataChecker(workbook); + case APP_REPOSITORY: + return new AppRepositoryDataChecker(workbook); + case COMMAND_TEMPLATE: + return new CommandTemplateDataChecker(workbook); + case WEBHOOK: + return new WebhookDataChecker(workbook); + default: + throw Exceptions.unsupported(); + } + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/MachineInfoDataChecker.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/MachineInfoDataChecker.java new file mode 100644 index 0000000..930c0d6 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/MachineInfoDataChecker.java @@ -0,0 +1,49 @@ + +package cn.orionsec.ops.handler.importer.checker; + +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.ImportType; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.dao.MachineInfoDAO; +import cn.orionsec.ops.dao.MachineSecretKeyDAO; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.domain.MachineSecretKeyDO; +import cn.orionsec.ops.entity.importer.MachineInfoImportDTO; +import cn.orionsec.ops.entity.vo.data.DataImportCheckVO; +import org.apache.poi.ss.usermodel.Workbook; + +import java.util.List; + + +public class MachineInfoDataChecker extends AbstractDataChecker { + + private static final MachineInfoDAO machineInfoDAO = SpringHolder.getBean(MachineInfoDAO.class); + + private static final MachineSecretKeyDAO machineSecretKeyDAO = SpringHolder.getBean(MachineSecretKeyDAO.class); + + public MachineInfoDataChecker(Workbook workbook) { + super(ImportType.MACHINE_INFO, workbook); + } + + @Override + protected DataImportCheckVO checkImportData(List rows) { + // 检查数据合法性 + this.validImportRows(rows); + // 设置机器id + this.setCheckRowsRelId(rows, MachineInfoImportDTO::getKeyName, + machineSecretKeyDAO::selectIdByNameList, + MachineSecretKeyDO::getKeyName, + MachineSecretKeyDO::getId, + MachineInfoImportDTO::setKeyId, + MessageConst.UNKNOWN_MACHINE_KEY); + // 通过唯一标识查询机器 + List presentMachines = this.getImportRowsPresentValues(rows, MachineInfoImportDTO::getTag, + machineInfoDAO, MachineInfoDO::getMachineTag); + // 检查数据是否存在 + this.checkImportRowsPresent(rows, MachineInfoImportDTO::getTag, + presentMachines, MachineInfoDO::getMachineTag, MachineInfoDO::getId); + // 设置导入检查数据 + return this.setImportCheckRows(rows); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/MachineProxyDataChecker.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/MachineProxyDataChecker.java new file mode 100644 index 0000000..5bff5db --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/MachineProxyDataChecker.java @@ -0,0 +1,26 @@ + +package cn.orionsec.ops.handler.importer.checker; + +import cn.orionsec.ops.constant.ImportType; +import cn.orionsec.ops.entity.domain.MachineProxyDO; +import cn.orionsec.ops.entity.importer.MachineProxyImportDTO; +import cn.orionsec.ops.entity.vo.data.DataImportCheckVO; +import org.apache.poi.ss.usermodel.Workbook; + +import java.util.List; + +public class MachineProxyDataChecker extends AbstractDataChecker { + + public MachineProxyDataChecker(Workbook workbook) { + super(ImportType.MACHINE_PROXY, workbook); + } + + @Override + protected DataImportCheckVO checkImportData(List rows) { + // 检查数据合法性 + this.validImportRows(rows); + // 设置导入检查数据 + return this.setImportCheckRows(rows); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/TailFileDataChecker.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/TailFileDataChecker.java new file mode 100644 index 0000000..4867037 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/TailFileDataChecker.java @@ -0,0 +1,49 @@ + +package cn.orionsec.ops.handler.importer.checker; + +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.ImportType; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.dao.FileTailListDAO; +import cn.orionsec.ops.dao.MachineInfoDAO; +import cn.orionsec.ops.entity.domain.FileTailListDO; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.importer.MachineTailFileImportDTO; +import cn.orionsec.ops.entity.vo.data.DataImportCheckVO; +import org.apache.poi.ss.usermodel.Workbook; + +import java.util.List; + + +public class TailFileDataChecker extends AbstractDataChecker { + + private static final MachineInfoDAO machineInfoDAO = SpringHolder.getBean(MachineInfoDAO.class); + + private static final FileTailListDAO fileTailListDAO = SpringHolder.getBean(FileTailListDAO.class); + + public TailFileDataChecker(Workbook workbook) { + super(ImportType.TAIL_FILE, workbook); + } + + @Override + protected DataImportCheckVO checkImportData(List rows) { + // 检查数据合法性 + this.validImportRows(rows); + // 设置机器id + this.setCheckRowsRelId(rows, MachineTailFileImportDTO::getMachineTag, + machineInfoDAO::selectIdByTagList, + MachineInfoDO::getMachineTag, + MachineInfoDO::getId, + MachineTailFileImportDTO::setMachineId, + MessageConst.UNKNOWN_MACHINE_TAG); + // 通过别名查询文件 + List presentFiles = this.getImportRowsPresentValues(rows, MachineTailFileImportDTO::getName, + fileTailListDAO, FileTailListDO::getAliasName); + // 检查数据是否存在 + this.checkImportRowsPresent(rows, MachineTailFileImportDTO::getName, + presentFiles, FileTailListDO::getAliasName, FileTailListDO::getId); + // 设置导入检查数据 + return this.setImportCheckRows(rows); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/WebhookDataChecker.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/WebhookDataChecker.java new file mode 100644 index 0000000..58cb642 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/checker/WebhookDataChecker.java @@ -0,0 +1,37 @@ + +package cn.orionsec.ops.handler.importer.checker; + +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.ImportType; +import cn.orionsec.ops.dao.WebhookConfigDAO; +import cn.orionsec.ops.entity.domain.WebhookConfigDO; +import cn.orionsec.ops.entity.importer.WebhookImportDTO; +import cn.orionsec.ops.entity.vo.data.DataImportCheckVO; +import org.apache.poi.ss.usermodel.Workbook; + +import java.util.List; + +public class WebhookDataChecker extends AbstractDataChecker { + + private static final WebhookConfigDAO webhookConfigDAO = SpringHolder.getBean(WebhookConfigDAO.class); + + public WebhookDataChecker(Workbook workbook) { + super(ImportType.WEBHOOK, workbook); + } + + @Override + protected DataImportCheckVO checkImportData(List rows) { + // 检查数据合法性 + this.validImportRows(rows); + // 通过名称查询模板 + List presentTemplates = this.getImportRowsPresentValues(rows, + WebhookImportDTO::getName, + webhookConfigDAO, WebhookConfigDO::getWebhookName); + // 检查数据是否存在 + this.checkImportRowsPresent(rows, WebhookImportDTO::getName, + presentTemplates, WebhookConfigDO::getWebhookName, WebhookConfigDO::getId); + // 设置导入检查数据 + return this.setImportCheckRows(rows); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/AbstractDataImporter.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/AbstractDataImporter.java new file mode 100644 index 0000000..03587d2 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/AbstractDataImporter.java @@ -0,0 +1,227 @@ + +package cn.orionsec.ops.handler.importer.impl; + +import cn.orionsec.kit.lang.utils.Threads; +import cn.orionsec.kit.lang.utils.Valid; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.lang.utils.io.FileWriters; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.ImportType; +import cn.orionsec.ops.constant.SchedulerPools; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.message.MessageType; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.entity.importer.BaseDataImportDTO; +import cn.orionsec.ops.entity.importer.DataImportDTO; +import cn.orionsec.ops.entity.vo.data.DataImportCheckRowVO; +import cn.orionsec.ops.entity.vo.data.DataImportCheckVO; +import cn.orionsec.ops.service.api.DataImportService; +import cn.orionsec.ops.service.api.WebSideMessageService; +import cn.orionsec.ops.utils.PathBuilders; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +public abstract class AbstractDataImporter implements IDataImporter { + + private static final DataImportService dataImportService = SpringHolder.getBean(DataImportService.class); + + private static final WebSideMessageService webSideMessageService = SpringHolder.getBean(WebSideMessageService.class); + + /** + * 导入数据 + */ + protected final DataImportDTO importData; + + /** + * domain mapper + */ + protected final BaseMapper mapper; + + /** + * 导入类型 + */ + protected ImportType importType; + + public AbstractDataImporter(DataImportDTO importData, BaseMapper mapper) { + this.importData = importData; + this.mapper = mapper; + } + + @Override + public void doImport() { + // 异步执行导入 + Threads.start(this::doImportData, SchedulerPools.ASYNC_IMPORT_SCHEDULER); + } + + /** + * 执行导入数据 + */ + @SuppressWarnings("unchecked") + public void doImportData() { + this.importType = ImportType.of(importData.getType()); + Exception ex = null; + try { + // 获取缓存数据 + DataImportCheckVO dataCheck = importData.getCheck(); + List rows = this.getImportData(); + // 插入 + this.getImportInsertData(dataCheck, rows, (Class) importType.getConvertClass()) + .stream() + .map(s -> (DO) s) + .peek(this::insertFiller) + .peek(mapper::insert) + .forEach(this::insertCallback); + // 更新 + this.getImportUpdateData(dataCheck, rows, (Class) importType.getConvertClass()) + .stream() + .map(s -> (DO) s) + .peek(this::updateFiller) + .peek(mapper::updateById) + .forEach(this::updateCallback); + } catch (Exception e) { + ex = e; + log.error("{}导入失败 token: {}, data: {}", importType.name(), importData.getImportToken(), JSON.toJSONString(importData), e); + } + // 发送站内信 + this.sendImportWebSideMessage(ex == null); + // 保存日志 + this.saveImportDataJson(importData); + // 导入完成回调 + this.importFinishCallback(ex == null); + } + + /** + * 数据插入填充 + * + * @param row row + */ + protected void insertFiller(DO row) { + } + + /** + * 数据修改填充 + * + * @param row row + */ + protected void updateFiller(DO row) { + } + + /** + * 数据插入回调 + * + * @param row row + */ + protected void insertCallback(DO row) { + } + + /** + * 数据更新回调 + * + * @param row row + */ + protected void updateCallback(DO row) { + } + + /** + * 导入完成回调 + * + * @param isSuccess 是否成功 + */ + protected void importFinishCallback(boolean isSuccess) { + } + + /** + * 获取导入数据 + * + * @param T + * @return list + */ + @SuppressWarnings("unchecked") + private List getImportData() { + ImportType type = ImportType.of(importData.getType()); + Valid.notNull(type); + return (List) JSON.parseArray(importData.getData(), type.getImportClass()); + } + + /** + * 获取导入插入数据 + * + * @param dataCheck dataCheck + * @param dataList list + * @param convertClass convertClass + * @param T + * @return rows + */ + private List getImportInsertData(DataImportCheckVO dataCheck, + List dataList, + Class convertClass) { + return dataCheck.getInsertRows().stream() + .map(DataImportCheckRowVO::getIndex) + .map(dataList::get) + .map(s -> Converts.to(s, convertClass)) + .collect(Collectors.toList()); + } + + /** + * 获取导入更新数据 + * + * @param dataCheck dataCheck + * @param dataList list + * @param convertClass convertClass + * @param T + * @return rows + */ + private List getImportUpdateData(DataImportCheckVO dataCheck, + List dataList, + Class convertClass) { + return dataCheck.getUpdateRows().stream() + .map(DataImportCheckRowVO::getIndex) + .map(dataList::get) + .map(s -> Converts.to(s, convertClass)) + .collect(Collectors.toList()); + } + + /** + * 发送站内信 + * + * @param isSuccess 是否导入成功 + */ + private void sendImportWebSideMessage(boolean isSuccess) { + // 消息类型 + MessageType messageType; + if (isSuccess) { + messageType = MessageType.DATA_IMPORT_SUCCESS; + } else { + messageType = MessageType.DATA_IMPORT_FAILURE; + } + // 站内信参数 + Map params = Maps.newMap(); + params.put(EventKeys.TIME, Dates.format(importData.getImportTime())); + params.put(EventKeys.TOKEN, importData.getImportToken()); + params.put(EventKeys.LABEL, importType.getLabel()); + webSideMessageService.addMessage(messageType, importData.getType().longValue(), importData.getUserId(), importData.getUserName(), params); + } + + /** + * 将导入 json 存储到本地 + * + * @param importData json + */ + private void saveImportDataJson(DataImportDTO importData) { + // 将数据存储到本地 log + String importJsonPath = PathBuilders.getImportDataJsonPath(importData.getUserId(), importData.getType(), importData.getImportToken()); + String path = Files1.getPath(SystemEnvAttr.LOG_PATH.getValue(), importJsonPath); + FileWriters.write(path, JSON.toJSONString(importData, SerializerFeature.PrettyFormat)); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/AppProfileDataImporter.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/AppProfileDataImporter.java new file mode 100644 index 0000000..692a9a6 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/AppProfileDataImporter.java @@ -0,0 +1,26 @@ + +package cn.orionsec.ops.handler.importer.impl; + +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.dao.ApplicationProfileDAO; +import cn.orionsec.ops.entity.domain.ApplicationProfileDO; +import cn.orionsec.ops.entity.importer.DataImportDTO; +import cn.orionsec.ops.service.api.ApplicationProfileService; + + +public class AppProfileDataImporter extends AbstractDataImporter { + + private static final ApplicationProfileDAO applicationProfileDAO = SpringHolder.getBean(ApplicationProfileDAO.class); + + private static final ApplicationProfileService applicationProfileService = SpringHolder.getBean(ApplicationProfileService.class); + + public AppProfileDataImporter(DataImportDTO importData) { + super(importData, applicationProfileDAO); + } + + @Override + protected void importFinishCallback(boolean isSuccess) { + applicationProfileService.clearProfileCache(); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/AppRepositoryDataImporter.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/AppRepositoryDataImporter.java new file mode 100644 index 0000000..a91fdfa --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/AppRepositoryDataImporter.java @@ -0,0 +1,41 @@ + +package cn.orionsec.ops.handler.importer.impl; + +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.app.RepositoryStatus; +import cn.orionsec.ops.dao.ApplicationRepositoryDAO; +import cn.orionsec.ops.entity.domain.ApplicationRepositoryDO; +import cn.orionsec.ops.entity.importer.DataImportDTO; +import cn.orionsec.ops.utils.Utils; + +import java.io.File; + + +public class AppRepositoryDataImporter extends AbstractDataImporter { + + private static final ApplicationRepositoryDAO applicationRepositoryDAO = SpringHolder.getBean(ApplicationRepositoryDAO.class); + + public AppRepositoryDataImporter(DataImportDTO importData) { + super(importData, applicationRepositoryDAO); + } + + @Override + protected void insertFiller(ApplicationRepositoryDO row) { + row.setRepoStatus(RepositoryStatus.UNINITIALIZED.getStatus()); + } + + @Override + protected void updateFiller(ApplicationRepositoryDO row) { + Long id = row.getId(); + ApplicationRepositoryDO beforeRepo = applicationRepositoryDAO.selectById(id); + if (beforeRepo != null && !beforeRepo.getRepoUrl().equals(row.getRepoUrl())) { + // 如果修改了url则状态改为未初始化 + row.setRepoStatus(RepositoryStatus.UNINITIALIZED.getStatus()); + // 删除 event 目录 + File clonePath = new File(Utils.getRepositoryEventDir(id)); + Files1.delete(clonePath); + } + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/ApplicationDataImporter.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/ApplicationDataImporter.java new file mode 100644 index 0000000..4c27d63 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/ApplicationDataImporter.java @@ -0,0 +1,26 @@ + +package cn.orionsec.ops.handler.importer.impl; + +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.dao.ApplicationInfoDAO; +import cn.orionsec.ops.entity.domain.ApplicationInfoDO; +import cn.orionsec.ops.entity.importer.DataImportDTO; +import cn.orionsec.ops.service.api.ApplicationInfoService; + + +public class ApplicationDataImporter extends AbstractDataImporter { + + private static final ApplicationInfoDAO applicationInfoDAO = SpringHolder.getBean(ApplicationInfoDAO.class); + + private static final ApplicationInfoService applicationInfoService = SpringHolder.getBean(ApplicationInfoService.class); + + public ApplicationDataImporter(DataImportDTO importData) { + super(importData, applicationInfoDAO); + } + + @Override + protected void insertFiller(ApplicationInfoDO row) { + row.setAppSort(applicationInfoService.getNextSort()); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/CommandTemplateDataImporter.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/CommandTemplateDataImporter.java new file mode 100644 index 0000000..1989de5 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/CommandTemplateDataImporter.java @@ -0,0 +1,31 @@ + +package cn.orionsec.ops.handler.importer.impl; + +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.dao.CommandTemplateDAO; +import cn.orionsec.ops.entity.domain.CommandTemplateDO; +import cn.orionsec.ops.entity.importer.DataImportDTO; + +public class CommandTemplateDataImporter extends AbstractDataImporter { + + private static final CommandTemplateDAO commandTemplateDAO = SpringHolder.getBean(CommandTemplateDAO.class); + + public CommandTemplateDataImporter(DataImportDTO importData) { + super(importData, commandTemplateDAO); + } + + @Override + protected void insertFiller(CommandTemplateDO row) { + row.setCreateUserId(importData.getUserId()); + row.setCreateUserName(importData.getUserName()); + row.setUpdateUserId(importData.getUserId()); + row.setUpdateUserName(importData.getUserName()); + } + + @Override + protected void updateFiller(CommandTemplateDO row) { + row.setUpdateUserId(importData.getUserId()); + row.setUpdateUserName(importData.getUserName()); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/IDataImporter.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/IDataImporter.java new file mode 100644 index 0000000..2458a3a --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/IDataImporter.java @@ -0,0 +1,45 @@ + +package cn.orionsec.ops.handler.importer.impl; + +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.ops.constant.ImportType; +import cn.orionsec.ops.entity.importer.DataImportDTO; + + +public interface IDataImporter { + + /** + * 执行导入 + */ + void doImport(); + + /** + * 创建数据导入器 + * + * @param importData importData + * @return importer + */ + static IDataImporter create(DataImportDTO importData) { + switch (ImportType.of(importData.getType())) { + case MACHINE_INFO: + return new MachineInfoDataImporter(importData); + case MACHINE_PROXY: + return new MachineProxyDataImporter(importData); + case TAIL_FILE: + return new TailFileDataImporter(importData); + case APP_PROFILE: + return new AppProfileDataImporter(importData); + case APPLICATION: + return new ApplicationDataImporter(importData); + case APP_REPOSITORY: + return new AppRepositoryDataImporter(importData); + case COMMAND_TEMPLATE: + return new CommandTemplateDataImporter(importData); + case WEBHOOK: + return new WebhookDataImporter(importData); + default: + throw Exceptions.unsupported(); + } + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/MachineInfoDataImporter.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/MachineInfoDataImporter.java new file mode 100644 index 0000000..c60b43a --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/MachineInfoDataImporter.java @@ -0,0 +1,39 @@ + +package cn.orionsec.ops.handler.importer.impl; + +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.dao.MachineInfoDAO; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.importer.DataImportDTO; +import cn.orionsec.ops.service.api.MachineEnvService; + +import java.util.Optional; + +public class MachineInfoDataImporter extends AbstractDataImporter { + + private static final MachineInfoDAO machineInfoDAO = SpringHolder.getBean(MachineInfoDAO.class); + + private static final MachineEnvService machineEnvService = SpringHolder.getBean(MachineEnvService.class); + + public MachineInfoDataImporter(DataImportDTO importData) { + super(importData, machineInfoDAO); + } + + @Override + protected void updateFiller(MachineInfoDO row) { + // 填充忽略的字段 + Optional.ofNullable(row.getId()) + .map(machineInfoDAO::selectById) + .ifPresent(m -> { + row.setProxyId(m.getProxyId()); + row.setKeyId(m.getKeyId()); + }); + } + + @Override + protected void insertCallback(MachineInfoDO row) { + // 初始化新增机器环境变量 + machineEnvService.initEnv(row.getId()); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/MachineProxyDataImporter.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/MachineProxyDataImporter.java new file mode 100644 index 0000000..3357b18 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/MachineProxyDataImporter.java @@ -0,0 +1,17 @@ + +package cn.orionsec.ops.handler.importer.impl; + +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.dao.MachineProxyDAO; +import cn.orionsec.ops.entity.domain.MachineProxyDO; +import cn.orionsec.ops.entity.importer.DataImportDTO; + +public class MachineProxyDataImporter extends AbstractDataImporter { + + private static final MachineProxyDAO machineProxyDAO = SpringHolder.getBean(MachineProxyDAO.class); + + public MachineProxyDataImporter(DataImportDTO importData) { + super(importData, machineProxyDAO); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/TailFileDataImporter.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/TailFileDataImporter.java new file mode 100644 index 0000000..afb9a5d --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/TailFileDataImporter.java @@ -0,0 +1,17 @@ + +package cn.orionsec.ops.handler.importer.impl; + +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.dao.FileTailListDAO; +import cn.orionsec.ops.entity.domain.FileTailListDO; +import cn.orionsec.ops.entity.importer.DataImportDTO; + +public class TailFileDataImporter extends AbstractDataImporter { + + private static final FileTailListDAO fileTailListDAO = SpringHolder.getBean(FileTailListDAO.class); + + public TailFileDataImporter(DataImportDTO importData) { + super(importData, fileTailListDAO); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/WebhookDataImporter.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/WebhookDataImporter.java new file mode 100644 index 0000000..1b4b952 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/impl/WebhookDataImporter.java @@ -0,0 +1,17 @@ + +package cn.orionsec.ops.handler.importer.impl; + +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.dao.WebhookConfigDAO; +import cn.orionsec.ops.entity.domain.WebhookConfigDO; +import cn.orionsec.ops.entity.importer.DataImportDTO; + +public class WebhookDataImporter extends AbstractDataImporter { + + private static final WebhookConfigDAO webhookConfigDAO = SpringHolder.getBean(WebhookConfigDAO.class); + + public WebhookDataImporter(DataImportDTO importData) { + super(importData, webhookConfigDAO); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/ApplicationProfileValidator.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/ApplicationProfileValidator.java new file mode 100644 index 0000000..9258b3f --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/ApplicationProfileValidator.java @@ -0,0 +1,65 @@ + +package cn.orionsec.ops.handler.importer.validator; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.ops.constant.CnConst; +import cn.orionsec.ops.entity.importer.ApplicationProfileImportDTO; +import cn.orionsec.ops.utils.Valid; + + +public class ApplicationProfileValidator implements DataValidator { + + private ApplicationProfileValidator() { + } + + public static final ApplicationProfileValidator INSTANCE = new ApplicationProfileValidator(); + + public static final int NAME_MAX_LEN = 32; + + public static final int TAG_MAX_LEN = 32; + + public static final int DESCRIPTION_MAX_LEN = 64; + + public static final String NAME_EMPTY_MESSAGE = "环境名称不能为空"; + + public static final String NAME_LEN_MESSAGE = "环境名称长度不能大于 " + NAME_MAX_LEN + "位"; + + public static final String TAG_EMPTY_MESSAGE = "唯一标识不能为空"; + + public static final String TAG_LEN_MESSAGE = "唯一标识长度不能大于 " + TAG_MAX_LEN + "位"; + + public static final String RELEASE_AUDIT_EMPTY_MESSAGE = "发布审核不能为空"; + + public static final String RELEASE_AUDIT_MESSAGE = "发布审核只能为 " + CnConst.OPEN + "和" + CnConst.CLOSE; + + public static final String DESCRIPTION_LEN_MESSAGE = "描述长度不能大于 " + DESCRIPTION_MAX_LEN + "位"; + + @Override + public void validData(Object o) { + if (o instanceof ApplicationProfileImportDTO) { + validImport((ApplicationProfileImportDTO) o); + } + } + + /** + * 验证导入数据 + * + * @param row row + */ + private void validImport(ApplicationProfileImportDTO row) { + String name = row.getName(); + String tag = row.getTag(); + String releaseAudit = row.getReleaseAudit(); + String description = row.getDescription(); + Valid.notBlank(name, NAME_EMPTY_MESSAGE); + Valid.validLengthLte(name, NAME_MAX_LEN, NAME_LEN_MESSAGE); + Valid.notBlank(tag, TAG_EMPTY_MESSAGE); + Valid.validLengthLte(tag, TAG_MAX_LEN, TAG_LEN_MESSAGE); + Valid.notBlank(releaseAudit, RELEASE_AUDIT_EMPTY_MESSAGE); + Valid.in(releaseAudit, new String[]{CnConst.OPEN, CnConst.CLOSE}, RELEASE_AUDIT_MESSAGE); + if (!Strings.isBlank(description)) { + Valid.validLengthLte(description, DESCRIPTION_MAX_LEN, DESCRIPTION_LEN_MESSAGE); + } + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/ApplicationRepositoryValidator.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/ApplicationRepositoryValidator.java new file mode 100644 index 0000000..6e2c982 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/ApplicationRepositoryValidator.java @@ -0,0 +1,126 @@ + +package cn.orionsec.ops.handler.importer.validator; + +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.ops.constant.app.RepositoryAuthType; +import cn.orionsec.ops.constant.app.RepositoryTokenType; +import cn.orionsec.ops.entity.importer.ApplicationRepositoryImportDTO; +import cn.orionsec.ops.utils.Valid; +import cn.orionsec.ops.utils.ValueMix; + +import java.util.Arrays; +import java.util.stream.Collectors; + +public class ApplicationRepositoryValidator implements DataValidator { + + private ApplicationRepositoryValidator() { + } + + public static final ApplicationRepositoryValidator INSTANCE = new ApplicationRepositoryValidator(); + + public static final int NAME_MAX_LEN = 32; + + public static final int URL_MAX_LEN = 1024; + + public static final int USERNAME_MAX_LEN = 128; + + public static final int TOKEN_MAX_LEN = 256; + + public static final int PASSWORD_MAX_LEN = 128; + + public static final int DESCRIPTION_MAX_LEN = 64; + + public static final String NAME_EMPTY_MESSAGE = "名称不能为空"; + + public static final String NAME_LEN_MESSAGE = "名称长度不能大于 " + NAME_MAX_LEN + "位"; + + public static final String URL_EMPTY_MESSAGE = "url 不能为空"; + + public static final String URL_LEN_MESSAGE = "url 长度不能大于 " + URL_MAX_LEN + "位"; + + public static final String AUTH_TYPE_EMPTY_MESSAGE = "认证方式不能为空"; + + public static final String AUTH_TYPE_VALUE_MESSAGE = "认证方式只能为 " + Arrays.stream(RepositoryAuthType.values()).map(RepositoryAuthType::getLabel).collect(Collectors.toList()); + + public static final String TOKEN_TYPE_EMPTY_MESSAGE = "认证方式为" + RepositoryAuthType.TOKEN.getLabel() + "时 令牌类型不能为空"; + + public static final String TOKEN_TYPE_VALUE_MESSAGE = "令牌类型只能为 " + Arrays.stream(RepositoryTokenType.values()).map(RepositoryTokenType::getLabel).collect(Collectors.toList()); + + public static final String PASSWORD_TOKEN_DECRYPT_MESSAGE = "密码/令牌密文解密失败"; + + public static final String USERNAME_PASSWORD_MESSAGE = "用户名和密码必须同时存在"; + + public static final String TOKEN_EMPTY_MESSAGE = "令牌不能为空"; + + public static final String USERNAME_TOKEN_MESSAGE = "令牌类型为 " + RepositoryTokenType.GITEE.getLabel() + " 时 用户名和密钥必须同时存在"; + + public static final String USERNAME_LEN_MESSAGE = "用户名长度不能大于 " + USERNAME_MAX_LEN + "位"; + + public static final String TOKEN_LEN_MESSAGE = "密钥长度不能大于 " + TOKEN_MAX_LEN + "位"; + + public static final String PASSWORD_LEN_MESSAGE = "密码长度不能大于 " + PASSWORD_MAX_LEN + "位"; + + public static final String DESCRIPTION_LEN_MESSAGE = "描述长度不能大于 " + DESCRIPTION_MAX_LEN + "位"; + + @Override + public void validData(Object o) { + if (o instanceof ApplicationRepositoryImportDTO) { + validImport((ApplicationRepositoryImportDTO) o); + } + } + + /** + * 验证导入数据 + * + * @param row row + */ + private void validImport(ApplicationRepositoryImportDTO row) { + String name = row.getName(); + String url = row.getUrl(); + String authTypeValue = row.getAuthType(); + String tokenTypeValue = row.getTokenType(); + String username = row.getUsername(); + String encryptAuthValue = row.getEncryptAuthValue(); + String importAuthValue = row.getImportAuthValue(); + String description = row.getDescription(); + Valid.notBlank(name, NAME_EMPTY_MESSAGE); + Valid.validLengthLte(name, NAME_MAX_LEN, NAME_LEN_MESSAGE); + Valid.notBlank(url, URL_EMPTY_MESSAGE); + Valid.validLengthLte(url, URL_MAX_LEN, URL_LEN_MESSAGE); + Valid.notBlank(authTypeValue, AUTH_TYPE_EMPTY_MESSAGE); + RepositoryAuthType authType = RepositoryAuthType.of(authTypeValue); + Valid.notNull(authType, AUTH_TYPE_VALUE_MESSAGE); + if (!Strings.isEmpty(encryptAuthValue)) { + String decryptValue = ValueMix.decrypt(encryptAuthValue); + Valid.notNull(decryptValue, PASSWORD_TOKEN_DECRYPT_MESSAGE); + row.setDecryptAuthValue(decryptValue); + } + if (RepositoryAuthType.PASSWORD.equals(authType)) { + // 密码验证 + if (!Strings.isEmpty(importAuthValue)) { + Valid.validLengthLte(importAuthValue, PASSWORD_MAX_LEN, PASSWORD_LEN_MESSAGE); + } + if (Strings.isEmpty(username) ^ (Strings.isEmpty(row.getDecryptAuthValue()) && Strings.isEmpty(importAuthValue))) { + throw Exceptions.argument(USERNAME_PASSWORD_MESSAGE); + } + } else { + // 令牌验证 + Valid.notBlank(tokenTypeValue, TOKEN_TYPE_EMPTY_MESSAGE); + RepositoryTokenType tokenType = RepositoryTokenType.of(tokenTypeValue); + Valid.notNull(tokenType, TOKEN_TYPE_VALUE_MESSAGE); + if (!Strings.isEmpty(importAuthValue)) { + Valid.validLengthLte(importAuthValue, TOKEN_MAX_LEN, TOKEN_LEN_MESSAGE); + } + Valid.isTrue(!Strings.isBlank(importAuthValue) || !Strings.isBlank(row.getDecryptAuthValue()), TOKEN_EMPTY_MESSAGE); + if (RepositoryTokenType.GITEE.equals(tokenType)) { + Valid.notBlank(username, USERNAME_TOKEN_MESSAGE); + Valid.validLengthLte(username, USERNAME_MAX_LEN, USERNAME_LEN_MESSAGE); + } + } + if (!Strings.isBlank(description)) { + Valid.validLengthLte(description, DESCRIPTION_MAX_LEN, DESCRIPTION_LEN_MESSAGE); + } + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/ApplicationValidator.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/ApplicationValidator.java new file mode 100644 index 0000000..f41be20 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/ApplicationValidator.java @@ -0,0 +1,57 @@ + +package cn.orionsec.ops.handler.importer.validator; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.ops.entity.importer.ApplicationImportDTO; +import cn.orionsec.ops.utils.Valid; + + +public class ApplicationValidator implements DataValidator { + + private ApplicationValidator() { + } + + public static final ApplicationValidator INSTANCE = new ApplicationValidator(); + + public static final int NAME_MAX_LEN = 32; + + public static final int TAG_MAX_LEN = 32; + + public static final int DESCRIPTION_MAX_LEN = 64; + + public static final String NAME_EMPTY_MESSAGE = "应用名称不能为空"; + + public static final String NAME_LEN_MESSAGE = "应用名称长度不能大于 " + NAME_MAX_LEN + "位"; + + public static final String TAG_EMPTY_MESSAGE = "唯一标识不能为空"; + + public static final String TAG_LEN_MESSAGE = "唯一标识长度不能大于 " + TAG_MAX_LEN + "位"; + + public static final String DESCRIPTION_LEN_MESSAGE = "描述长度不能大于 " + DESCRIPTION_MAX_LEN + "位"; + + @Override + public void validData(Object o) { + if (o instanceof ApplicationImportDTO) { + validImport((ApplicationImportDTO) o); + } + } + + /** + * 验证导入数据 + * + * @param row row + */ + private static void validImport(ApplicationImportDTO row) { + String name = row.getName(); + String tag = row.getTag(); + String description = row.getDescription(); + Valid.notBlank(name, NAME_EMPTY_MESSAGE); + Valid.validLengthLte(name, NAME_MAX_LEN, NAME_LEN_MESSAGE); + Valid.notBlank(tag, TAG_EMPTY_MESSAGE); + Valid.validLengthLte(tag, TAG_MAX_LEN, TAG_LEN_MESSAGE); + if (!Strings.isBlank(description)) { + Valid.validLengthLte(description, DESCRIPTION_MAX_LEN, DESCRIPTION_LEN_MESSAGE); + } + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/CommandTemplateValidator.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/CommandTemplateValidator.java new file mode 100644 index 0000000..2b86f81 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/CommandTemplateValidator.java @@ -0,0 +1,55 @@ + +package cn.orionsec.ops.handler.importer.validator; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.ops.entity.importer.CommandTemplateImportDTO; +import cn.orionsec.ops.utils.Valid; + +public class CommandTemplateValidator implements DataValidator { + + private CommandTemplateValidator() { + } + + public static final CommandTemplateValidator INSTANCE = new CommandTemplateValidator(); + + public static final int NAME_MAX_LEN = 32; + + public static final int TEMPLATE_MAX_LEN = 2048; + + public static final int DESCRIPTION_MAX_LEN = 64; + + public static final String NAME_EMPTY_MESSAGE = "模板名称不能为空"; + + public static final String NAME_LEN_MESSAGE = "模板名称长度不能大于 " + NAME_MAX_LEN + "位"; + + public static final String TEMPLATE_EMPTY_MESSAGE = "模板命令不能为空"; + + public static final String TEMPLATE_LEN_MESSAGE = "模板命令长度不能大于 " + TEMPLATE_MAX_LEN + "位"; + + public static final String DESCRIPTION_LEN_MESSAGE = "描述长度不能大于 " + DESCRIPTION_MAX_LEN + "位"; + + @Override + public void validData(Object o) { + if (o instanceof CommandTemplateImportDTO) { + validImport((CommandTemplateImportDTO) o); + } + } + + /** + * 验证导入数据 + * + * @param row row + */ + private void validImport(CommandTemplateImportDTO row) { + String name = row.getName(); + String template = row.getTemplate(); + String description = row.getDescription(); + Valid.notBlank(name, NAME_EMPTY_MESSAGE); + Valid.validLengthLte(name, NAME_MAX_LEN, NAME_LEN_MESSAGE); + Valid.notBlank(template, TEMPLATE_EMPTY_MESSAGE); + Valid.validLengthLte(template, TEMPLATE_MAX_LEN, TEMPLATE_LEN_MESSAGE); + if (!Strings.isBlank(description)) { + Valid.validLengthLte(description, DESCRIPTION_MAX_LEN, DESCRIPTION_LEN_MESSAGE); + } + } +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/DataValidator.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/DataValidator.java new file mode 100644 index 0000000..d7e3e1a --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/DataValidator.java @@ -0,0 +1,13 @@ + +package cn.orionsec.ops.handler.importer.validator; + +public interface DataValidator { + + /** + * 验证数据 + * + * @param o o + */ + void validData(Object o); + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/FileTailValidator.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/FileTailValidator.java new file mode 100644 index 0000000..13bf1b5 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/FileTailValidator.java @@ -0,0 +1,87 @@ + +package cn.orionsec.ops.handler.importer.validator; + +import cn.orionsec.kit.lang.utils.Charsets; +import cn.orionsec.ops.entity.importer.MachineTailFileImportDTO; +import cn.orionsec.ops.utils.Valid; + +public class FileTailValidator implements DataValidator { + + private FileTailValidator() { + } + + public static final FileTailValidator INSTANCE = new FileTailValidator(); + + public static final int NAME_MAX_LEN = 64; + + public static final int PATH_MAX_LEN = 1024; + + public static final int COMMAND_MAX_LEN = 1024; + + public static final int CHARSET_MAX_LEN = 16; + + public static final int OFFSET_MIN_RANGE = 0; + + public static final int OFFSET_MAX_RANGE = 10000; + + public static final int DESCRIPTION_MAX_LEN = 64; + + public static final String MACHINE_TAG_EMPTY_MESSAGE = "机器标识不能为空"; + + public static final String NAME_EMPTY_MESSAGE = "文件别名不能为空"; + + public static final String NAME_LEN_MESSAGE = "文件别名长度不能大于 " + NAME_MAX_LEN + "位"; + + public static final String PATH_EMPTY_MESSAGE = "文件路径不能为空"; + + public static final String PATH_LEN_MESSAGE = "文件路径长度不能大于 " + PATH_MAX_LEN + "位"; + + public static final String COMMAND_EMPTY_MESSAGE = "执行命令不能为空"; + + public static final String COMMAND_LEN_MESSAGE = "执行命令长度不能大于 " + COMMAND_MAX_LEN + "位"; + + public static final String CHARSET_EMPTY_MESSAGE = "文件编码不能为空"; + + public static final String CHARSET_UNSUPPORTED = "文件编码不合法"; + + public static final String CHARSET_LEN_MESSAGE = "文件编码不能大于 " + CHARSET_MAX_LEN + "位"; + + public static final String OFFSET_EMPTY_MESSAGE = "尾部偏移行不能为空"; + + public static final String OFFSET_LEN_MESSAGE = "尾部偏移行必须在" + OFFSET_MIN_RANGE + "~" + OFFSET_MAX_RANGE + "之间"; + + public static final String DESCRIPTION_LEN_MESSAGE = "描述长度不能大于 " + DESCRIPTION_MAX_LEN + "位"; + + @Override + public void validData(Object o) { + if (o instanceof MachineTailFileImportDTO) { + validImport((MachineTailFileImportDTO) o); + } + } + + /** + * 验证导入数据 + * + * @param row row + */ + private void validImport(MachineTailFileImportDTO row) { + String machineTag = row.getMachineTag(); + String name = row.getName(); + String path = row.getPath(); + String charset = row.getCharset(); + Integer offset = row.getOffset(); + String command = row.getCommand(); + Valid.notBlank(machineTag, MACHINE_TAG_EMPTY_MESSAGE); + Valid.notBlank(name, NAME_EMPTY_MESSAGE); + Valid.validLengthLte(name, NAME_MAX_LEN, NAME_LEN_MESSAGE); + Valid.notBlank(path, PATH_EMPTY_MESSAGE); + Valid.validLengthLte(path, PATH_MAX_LEN, PATH_LEN_MESSAGE); + Valid.notBlank(charset, CHARSET_EMPTY_MESSAGE); + Valid.validLengthLte(charset, CHARSET_MAX_LEN, CHARSET_LEN_MESSAGE); + Valid.isTrue(Charsets.isSupported(charset), CHARSET_UNSUPPORTED); + Valid.notNull(offset, OFFSET_EMPTY_MESSAGE); + Valid.inRange(offset, OFFSET_MIN_RANGE, OFFSET_MAX_RANGE, OFFSET_LEN_MESSAGE); + Valid.notBlank(command, COMMAND_EMPTY_MESSAGE); + Valid.validLengthLte(command, COMMAND_MAX_LEN, COMMAND_LEN_MESSAGE); + } +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/MachineProxyValidator.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/MachineProxyValidator.java new file mode 100644 index 0000000..c7586a4 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/MachineProxyValidator.java @@ -0,0 +1,98 @@ + +package cn.orionsec.ops.handler.importer.validator; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.ops.constant.machine.ProxyType; +import cn.orionsec.ops.entity.importer.MachineProxyImportDTO; +import cn.orionsec.ops.utils.Valid; +import cn.orionsec.ops.utils.ValueMix; + +import java.util.Arrays; +import java.util.stream.Collectors; + +public class MachineProxyValidator implements DataValidator { + + private MachineProxyValidator() { + } + + public static final MachineProxyValidator INSTANCE = new MachineProxyValidator(); + + public static final int HOST_MAX_LEN = 128; + + public static final int USER_NAME_MAX_LEN = 128; + + public static final int PASSWORD_MAX_LEN = 128; + + public static final int PORT_MIN_RANGE = 2; + + public static final int PORT_MAX_RANGE = 65534; + + public static final int DESCRIPTION_MAX_LEN = 64; + + public static final String HOST_EMPTY_MESSAGE = "主机不能为空"; + + public static final String HOST_LEN_MESSAGE = "主机长度不能大于 " + HOST_MAX_LEN + "位"; + + public static final String PROXY_TYPE_EMPTY_MESSAGE = "代理类型不能为空"; + + public static final String PROXY_TYPE_MESSAGE = "代理类型只能为 " + Arrays.stream(ProxyType.values()).map(ProxyType::getLabel).collect(Collectors.toList()); + + public static final String USER_NAME_LEN_MESSAGE = "用户名长度不能大于 " + USER_NAME_MAX_LEN + "位"; + + public static final String PASSWORD_DECRYPT_MESSAGE = "密码密文解密失败"; + + public static final String PASSWORD_LEN_MESSAGE = "密码明文长度不能大于 " + PASSWORD_MAX_LEN + "位"; + + public static final String USERNAME_PASSWORD_ABSENT_MESSAGE = "用户名密码需要同时存在"; + + public static final String PORT_EMPTY_MESSAGE = "端口不能为空"; + + public static final String PORT_LEN_MESSAGE = "端口必须在" + PORT_MIN_RANGE + "~" + PORT_MAX_RANGE + "之间"; + + public static final String DESCRIPTION_LEN_MESSAGE = "描述长度不能大于 " + DESCRIPTION_MAX_LEN + "位"; + + @Override + public void validData(Object o) { + if (o instanceof MachineProxyImportDTO) { + validImport((MachineProxyImportDTO) o); + } + } + + /** + * 验证导入数据 + * + * @param row row + */ + private void validImport(MachineProxyImportDTO row) { + String host = row.getHost(); + Integer port = row.getPort(); + String proxyType = row.getProxyType(); + String username = row.getUsername(); + String encryptPassword = row.getEncryptPassword(); + String password = row.getImportPassword(); + String description = row.getDescription(); + Valid.notBlank(host, HOST_EMPTY_MESSAGE); + Valid.validLengthLte(host, HOST_MAX_LEN, HOST_LEN_MESSAGE); + Valid.notNull(port, PORT_EMPTY_MESSAGE); + Valid.inRange(port, PORT_MIN_RANGE, PORT_MAX_RANGE, PORT_LEN_MESSAGE); + Valid.notBlank(proxyType, PROXY_TYPE_EMPTY_MESSAGE); + Valid.notNull(ProxyType.of(proxyType), PROXY_TYPE_MESSAGE); + if (!Strings.isBlank(username)) { + Valid.validLengthLte(username, USER_NAME_MAX_LEN, USER_NAME_LEN_MESSAGE); + } + if (!Strings.isBlank(password)) { + Valid.validLengthLte(password, PASSWORD_MAX_LEN, PASSWORD_LEN_MESSAGE); + } + if (!Strings.isBlank(encryptPassword)) { + String decryptPassword = ValueMix.decrypt(encryptPassword); + Valid.notNull(decryptPassword, PASSWORD_DECRYPT_MESSAGE); + row.setDecryptPassword(decryptPassword); + } + if (!Strings.isBlank(username)) { + Valid.isTrue(!Strings.isBlank(row.getImportPassword()) || !Strings.isBlank(row.getDecryptPassword()), USERNAME_PASSWORD_ABSENT_MESSAGE); + } + if (!Strings.isBlank(description)) { + Valid.validLengthLte(description, DESCRIPTION_MAX_LEN, DESCRIPTION_LEN_MESSAGE); + } + } +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/MachineValidator.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/MachineValidator.java new file mode 100644 index 0000000..cca35a7 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/MachineValidator.java @@ -0,0 +1,118 @@ + +package cn.orionsec.ops.handler.importer.validator; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.ops.constant.machine.MachineAuthType; +import cn.orionsec.ops.entity.importer.MachineInfoImportDTO; +import cn.orionsec.ops.utils.Valid; +import cn.orionsec.ops.utils.ValueMix; + +import java.util.Arrays; +import java.util.stream.Collectors; + +public class MachineValidator implements DataValidator { + + private MachineValidator() { + } + + public static final MachineValidator INSTANCE = new MachineValidator(); + + public static final int NAME_MAX_LEN = 32; + + public static final int TAG_MAX_LEN = 32; + + public static final int HOST_MAX_LEN = 128; + + public static final int USER_NAME_MAX_LEN = 128; + + public static final int PASSWORD_MAX_LEN = 128; + + public static final int PORT_MIN_RANGE = 2; + + public static final int PORT_MAX_RANGE = 65534; + + public static final int DESCRIPTION_MAX_LEN = 64; + + public static final String NAME_EMPTY_MESSAGE = "名称不能为空"; + + public static final String NAME_LEN_MESSAGE = "名称长度不能大于 " + NAME_MAX_LEN + "位"; + + public static final String TAG_EMPTY_MESSAGE = "唯一标识不能为空"; + + public static final String TAG_LEN_MESSAGE = "唯一标识长度不能大于 " + TAG_MAX_LEN + "位"; + + public static final String HOST_EMPTY_MESSAGE = "主机不能为空"; + + public static final String HOST_LEN_MESSAGE = "主机长度不能大于 " + HOST_MAX_LEN + "位"; + + public static final String AUTH_TYPE_EMPTY_MESSAGE = "认证方式不能为空"; + + public static final String AUTH_TYPE_MESSAGE = "认证方式只能为 " + Arrays.stream(MachineAuthType.values()).map(MachineAuthType::getLabel).collect(Collectors.toList()); + + public static final String USER_NAME_EMPTY_MESSAGE = "用户名不能为空"; + + public static final String USER_NAME_LEN_MESSAGE = "用户名长度不能大于 " + USER_NAME_MAX_LEN + "位"; + + public static final String PASSWORD_DECRYPT_MESSAGE = "密码密文解密失败"; + + public static final String PASSWORD_LEN_MESSAGE = "密码明文长度不能大于 " + PASSWORD_MAX_LEN + "位"; + + public static final String PASSWORD_EMPTY_MESSAGE = "密码验证密码不能为空"; + + public static final String PORT_EMPTY_MESSAGE = "端口不能为空"; + + public static final String PORT_LEN_MESSAGE = "端口必须在" + PORT_MIN_RANGE + "~" + PORT_MAX_RANGE + "之间"; + + public static final String DESCRIPTION_LEN_MESSAGE = "描述长度不能大于 " + DESCRIPTION_MAX_LEN + "位"; + + @Override + public void validData(Object o) { + if (o instanceof MachineInfoImportDTO) { + validImport((MachineInfoImportDTO) o); + } + } + + /** + * 验证导入数据 + * + * @param row row + */ + private void validImport(MachineInfoImportDTO row) { + String name = row.getName(); + String tag = row.getTag(); + String host = row.getHost(); + Integer port = row.getPort(); + String username = row.getUsername(); + String authType = row.getAuthType(); + String encryptPassword = row.getEncryptPassword(); + String password = row.getImportPassword(); + String description = row.getDescription(); + Valid.notBlank(name, NAME_EMPTY_MESSAGE); + Valid.validLengthLte(name, NAME_MAX_LEN, NAME_LEN_MESSAGE); + Valid.notBlank(tag, TAG_EMPTY_MESSAGE); + Valid.validLengthLte(tag, TAG_MAX_LEN, TAG_LEN_MESSAGE); + Valid.notBlank(host, HOST_EMPTY_MESSAGE); + Valid.validLengthLte(host, HOST_MAX_LEN, HOST_LEN_MESSAGE); + Valid.notNull(port, PORT_EMPTY_MESSAGE); + Valid.inRange(port, PORT_MIN_RANGE, PORT_MAX_RANGE, PORT_LEN_MESSAGE); + Valid.notBlank(username, USER_NAME_EMPTY_MESSAGE); + Valid.validLengthLte(username, USER_NAME_MAX_LEN, USER_NAME_LEN_MESSAGE); + Valid.notBlank(authType, AUTH_TYPE_EMPTY_MESSAGE); + MachineAuthType machineAuthType = Valid.notNull(MachineAuthType.of(authType), AUTH_TYPE_MESSAGE); + if (!Strings.isBlank(encryptPassword)) { + String decryptPassword = ValueMix.decrypt(encryptPassword); + Valid.notNull(decryptPassword, PASSWORD_DECRYPT_MESSAGE); + row.setDecryptPassword(decryptPassword); + } + if (!Strings.isBlank(password)) { + Valid.validLengthLte(password, PASSWORD_MAX_LEN, PASSWORD_LEN_MESSAGE); + } + if (MachineAuthType.PASSWORD.equals(machineAuthType)) { + Valid.isTrue(!Strings.isBlank(row.getImportPassword()) || !Strings.isBlank(row.getDecryptPassword()), PASSWORD_EMPTY_MESSAGE); + } + if (!Strings.isBlank(description)) { + Valid.validLengthLte(description, DESCRIPTION_MAX_LEN, DESCRIPTION_LEN_MESSAGE); + } + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/WebhookValidator.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/WebhookValidator.java new file mode 100644 index 0000000..0a18cce --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/handler/importer/validator/WebhookValidator.java @@ -0,0 +1,58 @@ + +package cn.orionsec.ops.handler.importer.validator; + +import cn.orionsec.ops.constant.webhook.WebhookType; +import cn.orionsec.ops.entity.importer.WebhookImportDTO; +import cn.orionsec.ops.utils.Valid; + +import java.util.Arrays; +import java.util.stream.Collectors; + +public class WebhookValidator implements DataValidator { + + private WebhookValidator() { + } + + public static final WebhookValidator INSTANCE = new WebhookValidator(); + + public static final int NAME_MAX_LEN = 64; + + public static final int URL_MAX_LEN = 2048; + + public static final String NAME_EMPTY_MESSAGE = "名称不能为空"; + + public static final String NAME_LEN_MESSAGE = "名称长度不能大于 " + NAME_MAX_LEN + "位"; + + public static final String TYPE_EMPTY_MESSAGE = "类型不能为空"; + + public static final String TYPE_MESSAGE = "类型只能为 " + Arrays.stream(WebhookType.values()).map(WebhookType::getLabel).collect(Collectors.toList()); + + public static final String URL_EMPTY_MESSAGE = "url不能为空"; + + public static final String URL_LEN_MESSAGE = "url长度不能大于 " + URL_MAX_LEN + "位"; + + @Override + public void validData(Object o) { + if (o instanceof WebhookImportDTO) { + validImport((WebhookImportDTO) o); + } + } + + /** + * 验证导入数据 + * + * @param row row + */ + private void validImport(WebhookImportDTO row) { + String name = row.getName(); + String type = row.getType(); + String url = row.getUrl(); + Valid.notBlank(name, NAME_EMPTY_MESSAGE); + Valid.validLengthLte(name, NAME_MAX_LEN, NAME_LEN_MESSAGE); + Valid.notBlank(type, TYPE_EMPTY_MESSAGE); + Valid.notNull(WebhookType.of(type), TYPE_MESSAGE); + Valid.notBlank(url, URL_EMPTY_MESSAGE); + Valid.validLengthLte(url, URL_MAX_LEN, URL_LEN_MESSAGE); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/service/api/DataClearService.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/service/api/DataClearService.java new file mode 100644 index 0000000..1306e04 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/service/api/DataClearService.java @@ -0,0 +1,72 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.entity.request.data.DataClearRequest; + +public interface DataClearService { + + /** + * 清理 批量执行数据 + * + * @param request request + * @return 删除数量 + */ + Integer clearBatchExec(DataClearRequest request); + + /** + * 清理 终端日志 + * + * @param request request + * @return 删除数量 + */ + Integer clearTerminalLog(DataClearRequest request); + + /** + * 清理 调度记录 + * + * @param request request + * @return 删除数量 + */ + Integer clearSchedulerRecord(DataClearRequest request); + + /** + * 清理 应用构建 + * + * @param request request + * @return 删除数量 + */ + Integer clearAppBuild(DataClearRequest request); + + /** + * 清理 应用发布 + * + * @param request request + * @return 删除数量 + */ + Integer clearAppRelease(DataClearRequest request); + + /** + * 清理 应用流水线 + * + * @param request request + * @return 删除数量 + */ + Integer clearAppPipeline(DataClearRequest request); + + /** + * 清理 操作日志 + * + * @param request request + * @return 删除数量 + */ + Integer clearEventLog(DataClearRequest request); + + /** + * 清理 机器报警历史 + * + * @param request request + * @return 删除数量 + */ + Integer clearMachineAlarmHistory(DataClearRequest request); + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/service/api/DataImportService.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/service/api/DataImportService.java new file mode 100644 index 0000000..709994e --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/service/api/DataImportService.java @@ -0,0 +1,23 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.entity.importer.DataImportDTO; + +public interface DataImportService { + + /** + * 检查导入 token + * + * @param token token + * @return 导入数据 + */ + DataImportDTO checkImportToken(String token); + + /** + * 清空导入 token + * + * @param token token + */ + void clearImportToken(String token); + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/service/impl/DataClearServiceImpl.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/service/impl/DataClearServiceImpl.java new file mode 100644 index 0000000..f56cb88 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/service/impl/DataClearServiceImpl.java @@ -0,0 +1,286 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.DataClearRange; +import cn.orionsec.ops.constant.app.BuildStatus; +import cn.orionsec.ops.constant.app.PipelineStatus; +import cn.orionsec.ops.constant.app.ReleaseStatus; +import cn.orionsec.ops.constant.command.ExecStatus; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.scheduler.SchedulerTaskStatus; +import cn.orionsec.ops.dao.*; +import cn.orionsec.ops.entity.domain.*; +import cn.orionsec.ops.entity.request.data.DataClearRequest; +import cn.orionsec.ops.service.api.DataClearService; +import cn.orionsec.ops.utils.Currents; +import cn.orionsec.ops.utils.EventParamsHolder; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Date; + +@Service("dataClearService") +public class DataClearServiceImpl implements DataClearService { + + @Resource + private CommandExecDAO commandExecDAO; + + @Resource + private MachineTerminalLogDAO machineTerminalLogDAO; + + @Resource + private SchedulerTaskRecordDAO schedulerTaskRecordDAO; + + @Resource + private ApplicationBuildDAO applicationBuildDAO; + + @Resource + private ApplicationReleaseDAO applicationReleaseDAO; + + @Resource + private ApplicationPipelineTaskDAO applicationPipelineTaskDAO; + + @Resource + private UserEventLogDAO userEventLogDAO; + + @Resource + private MachineAlarmHistoryDAO machineAlarmHistoryDAO; + + @Override + public Integer clearBatchExec(DataClearRequest request) { + if (!Currents.isAdministrator()) { + request.setICreated(Const.ENABLE); + } + // 基础删除条件 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .ne(CommandExecDO::getExecStatus, ExecStatus.RUNNABLE.getStatus()) + .eq(Const.ENABLE.equals(request.getICreated()), CommandExecDO::getUserId, Currents.getUserId()); + // 设置删除筛选条件 + this.setDeleteWrapper(commandExecDAO, wrapper, + CommandExecDO::getId, + CommandExecDO::getCreateTime, + CommandExecDO::getMachineId, + request); + int count = commandExecDAO.delete(wrapper); + // 设置日志参数 + EventParamsHolder.addParams(request); + EventParamsHolder.addParam(EventKeys.COUNT, count); + return count; + } + + @Override + public Integer clearTerminalLog(DataClearRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + // 设置删除筛选条件 + this.setDeleteWrapper(machineTerminalLogDAO, wrapper, + MachineTerminalLogDO::getId, + MachineTerminalLogDO::getCreateTime, + MachineTerminalLogDO::getMachineId, + request); + int count = machineTerminalLogDAO.delete(wrapper); + // 设置日志参数 + EventParamsHolder.addParams(request); + EventParamsHolder.addParam(EventKeys.COUNT, count); + return count; + } + + @Override + public Integer clearSchedulerRecord(DataClearRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .ne(SchedulerTaskRecordDO::getTaskStatus, SchedulerTaskStatus.RUNNABLE.getStatus()) + .eq(SchedulerTaskRecordDO::getTaskId, request.getRelId()); + // 设置删除筛选条件 + this.setDeleteWrapper(schedulerTaskRecordDAO, wrapper, + SchedulerTaskRecordDO::getId, + SchedulerTaskRecordDO::getCreateTime, + SchedulerTaskRecordDO::getTaskId, + request); + int count = schedulerTaskRecordDAO.delete(wrapper); + // 设置日志参数 + EventParamsHolder.addParams(request); + EventParamsHolder.addParam(EventKeys.COUNT, count); + return count; + } + + @Override + public Integer clearAppBuild(DataClearRequest request) { + Long userId = Currents.getUserId(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .ne(ApplicationBuildDO::getBuildStatus, BuildStatus.RUNNABLE.getStatus()) + .eq(ApplicationBuildDO::getProfileId, request.getProfileId()) + .eq(Const.ENABLE.equals(request.getICreated()), ApplicationBuildDO::getCreateUserId, userId); + // 设置筛选条件 + this.setDeleteWrapper(applicationBuildDAO, wrapper, + ApplicationBuildDO::getId, + ApplicationBuildDO::getCreateTime, + ApplicationBuildDO::getAppId, + request); + int count = applicationBuildDAO.delete(wrapper); + // 设置日志参数 + EventParamsHolder.addParams(request); + EventParamsHolder.addParam(EventKeys.COUNT, count); + return count; + } + + @Override + public Integer clearAppRelease(DataClearRequest request) { + Long userId = Currents.getUserId(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .ne(ApplicationReleaseDO::getReleaseStatus, ReleaseStatus.RUNNABLE.getStatus()) + .eq(ApplicationReleaseDO::getProfileId, request.getProfileId()) + .eq(Const.ENABLE.equals(request.getICreated()), ApplicationReleaseDO::getCreateUserId, userId) + .eq(Const.ENABLE.equals(request.getIAudited()), ApplicationReleaseDO::getAuditUserId, userId) + .eq(Const.ENABLE.equals(request.getIExecute()), ApplicationReleaseDO::getReleaseUserId, userId); + // 设置筛选条件 + this.setDeleteWrapper(applicationReleaseDAO, wrapper, + ApplicationReleaseDO::getId, + ApplicationReleaseDO::getCreateTime, + ApplicationReleaseDO::getAppId, + request); + int count = applicationReleaseDAO.delete(wrapper); + // 设置日志参数 + EventParamsHolder.addParams(request); + EventParamsHolder.addParam(EventKeys.COUNT, count); + return count; + } + + @Override + public Integer clearAppPipeline(DataClearRequest request) { + Long userId = Currents.getUserId(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .ne(ApplicationPipelineTaskDO::getExecStatus, PipelineStatus.RUNNABLE.getStatus()) + .eq(ApplicationPipelineTaskDO::getProfileId, request.getProfileId()) + .eq(Const.ENABLE.equals(request.getICreated()), ApplicationPipelineTaskDO::getCreateUserId, userId) + .eq(Const.ENABLE.equals(request.getIAudited()), ApplicationPipelineTaskDO::getAuditUserId, userId) + .eq(Const.ENABLE.equals(request.getIExecute()), ApplicationPipelineTaskDO::getExecUserId, userId); + // 设置筛选条件 + this.setDeleteWrapper(applicationPipelineTaskDAO, wrapper, + ApplicationPipelineTaskDO::getId, + ApplicationPipelineTaskDO::getCreateTime, + ApplicationPipelineTaskDO::getPipelineId, + request); + int count = applicationPipelineTaskDAO.delete(wrapper); + // 设置日志参数 + EventParamsHolder.addParams(request); + EventParamsHolder.addParam(EventKeys.COUNT, count); + return count; + } + + @Override + public Integer clearEventLog(DataClearRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(UserEventLogDO::getExecResult, Const.ENABLE); + // 设置删除筛选条件 + this.setDeleteWrapper(userEventLogDAO, wrapper, + UserEventLogDO::getId, + UserEventLogDO::getCreateTime, + UserEventLogDO::getEventClassify, + request); + int count = userEventLogDAO.delete(wrapper); + // 设置日志参数 + EventParamsHolder.addParams(request); + EventParamsHolder.addParam(EventKeys.COUNT, count); + return count; + } + + @Override + public Integer clearMachineAlarmHistory(DataClearRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(MachineAlarmHistoryDO::getMachineId, request.getMachineId()); + // 设置删除筛选条件 + this.setDeleteWrapper(machineAlarmHistoryDAO, wrapper, + MachineAlarmHistoryDO::getId, + MachineAlarmHistoryDO::getCreateTime, + MachineAlarmHistoryDO::getMachineId, + request); + int count = machineAlarmHistoryDAO.delete(wrapper); + // 设置日志参数 + EventParamsHolder.addParams(request); + EventParamsHolder.addParam(EventKeys.COUNT, count); + return count; + } + + /** + * 设置删除的筛选条件 + * + * @param mapper mapper + * @param wrapper wrapper + * @param idGetterFun idGetterFun + * @param dateGetterFun dateGetterFun + * @param relGetterFun relGetterFun + * @param request request + * @param + */ + private void setDeleteWrapper(BaseMapper mapper, + LambdaQueryWrapper wrapper, + SFunction idGetterFun, + SFunction dateGetterFun, + SFunction relGetterFun, + DataClearRequest request) { + if (DataClearRange.DAY.getRange().equals(request.getRange())) { + // 仅保留几天 + Integer day = request.getReserveDay(); + if (!day.equals(0)) { + this.setReverseDateWhere(wrapper, dateGetterFun, day); + } + } else if (DataClearRange.TOTAL.getRange().equals(request.getRange())) { + // 保留几条 + Integer total = request.getReserveTotal(); + if (!total.equals(0)) { + this.setReverseTotalWhere(mapper, wrapper, idGetterFun, total); + } + } else if (DataClearRange.REL_ID.getRange().equals(request.getRange())) { + // 删除机器id + wrapper.in(relGetterFun, request.getRelIdList()); + } + } + + /** + * 设置保留多少天的筛选条件 + * + * @param wrapper wrapper + * @param dateGetterFun dateGetterFun + * @param day day + * @param T + */ + private void setReverseDateWhere(LambdaQueryWrapper wrapper, + SFunction dateGetterFun, + Integer day) { + Date lessDayDate = Dates.stream().subDay(day).get(); + wrapper.lt(dateGetterFun, lessDayDate); + } + + /** + * 设置保留条数的筛选条件 + * + * @param mapper mapper + * @param wrapper wrapper + * @param idGetterFun idGetterFun + * @param total total + * @param T + */ + private void setReverseTotalWhere(BaseMapper mapper, + LambdaQueryWrapper wrapper, + SFunction idGetterFun, + Integer total) { + // 查询删除的最大id + LambdaQueryWrapper maxIdWrapper = wrapper.clone() + .orderByDesc(idGetterFun) + .last(Const.LIMIT + Const.SPACE + total + ", 1"); + T maxEntity = mapper.selectOne(maxIdWrapper); + // 未查询到则代表条数不满足, 设置一个 false 条件 从而达到不执行的目的 + if (maxEntity == null) { + wrapper.eq(idGetterFun, Const.L_N_1); + return; + } + Long maxId = idGetterFun.apply(maxEntity); + // 设置最大id阈值 + wrapper.le(idGetterFun, maxId); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/service/impl/DataImportServiceImpl.java b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/service/impl/DataImportServiceImpl.java new file mode 100644 index 0000000..af6c1d9 --- /dev/null +++ b/orion-ops-api/orion-ops-data/src/main/java/cn/orionsec/ops/service/impl/DataImportServiceImpl.java @@ -0,0 +1,48 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.ops.constant.KeyConst; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.entity.importer.DataImportDTO; +import cn.orionsec.ops.service.api.DataImportService; +import cn.orionsec.ops.utils.Currents; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Date; + +@Slf4j +@Service("dataImportService") +public class DataImportServiceImpl implements DataImportService { + + @Resource + private RedisTemplate redisTemplate; + + @Override + public DataImportDTO checkImportToken(String token) { + UserDTO user = Currents.getUser(); + // 查询缓存 + String data = redisTemplate.opsForValue().get(Strings.format(KeyConst.DATA_IMPORT_TOKEN, user.getId(), token)); + if (Strings.isEmpty(data)) { + throw Exceptions.argument(MessageConst.OPERATOR_TIMEOUT); + } + // 设置用户数据 + DataImportDTO importData = JSON.parseObject(data, DataImportDTO.class); + importData.setUserId(user.getId()); + importData.setUserName(user.getUsername()); + importData.setImportTime(new Date()); + return importData; + } + + @Override + public void clearImportToken(String token) { + redisTemplate.delete(Strings.format(KeyConst.DATA_IMPORT_TOKEN, Currents.getUserId(), token)); + } + +} diff --git a/orion-ops-api/orion-ops-data/src/main/resources/templates/import/app-profile-import-template.xlsx b/orion-ops-api/orion-ops-data/src/main/resources/templates/import/app-profile-import-template.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..8fabfc58c5f6ac33864dab91aae24fb7ed0099ab GIT binary patch literal 9276 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 LL2@Ld&wBp{cB2go literal 0 HcmV?d00001 diff --git a/orion-ops-api/orion-ops-data/src/main/resources/templates/import/app-repository-import-template.xlsx b/orion-ops-api/orion-ops-data/src/main/resources/templates/import/app-repository-import-template.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..526bace5e26d2b36dfbce3a125d4f09affaaaeda GIT binary patch literal 9450 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


f#@IPe!f1dog(EsH7J8K31 c%lD7$FDC{4R3AJJ&7gt7KH3cuKmGRq0LTw+WB>pF literal 0 HcmV?d00001 diff --git a/orion-ops-api/orion-ops-data/src/main/resources/templates/import/application-import-template.xlsx b/orion-ops-api/orion-ops-data/src/main/resources/templates/import/application-import-template.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..4a3b74b140a7aa114863aee8c68b8e1c400acb04 GIT binary patch literal 9126 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 P2L=TEqg)ut)3^Tz2#3CF literal 0 HcmV?d00001 diff --git a/orion-ops-api/orion-ops-data/src/main/resources/templates/import/command-template-import-template.xlsx b/orion-ops-api/orion-ops-data/src/main/resources/templates/import/command-template-import-template.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..912a5ff3ff32c150237c9c08afe9d13d2e352e1e GIT binary patch literal 9099 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 literal 0 HcmV?d00001 diff --git a/orion-ops-api/orion-ops-data/src/main/resources/templates/import/tail-file-import-template.xlsx b/orion-ops-api/orion-ops-data/src/main/resources/templates/import/tail-file-import-template.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..a5906c5bab260235cbbe8c9bd4a8c72f5c7345a7 GIT binary patch literal 9285 zcmaJ{1z42Z)}~83rE5UCTS81}RBt>6DbtACC84kKXg& z{X8?@JToiy_pWcPy=T2!Nfs6k1?q7GDy)b+UjIFiA3hjc87bLXS=+HHJ&55xe1Y_r zm}RFz$O~vFC;=EKD9pc$8CqMjfGx~ZqC{n2*>MBTeVmYHYE46fVh3Y=f=UEJcZA+9 zR(U4?03#WvFpF)L^L2D!7~LRnLJb&|lQUh#VSV58lKlSKqeCi`>KP4RDI^@!I+q1k z#g#u+8KkE$BJeC0j4OawKZ-d@0~QFuzyPm6-~&-!AY;u+*@uGyA)>>c{%FA22;uY~!nnKSa30y7uxnoS#rLiE51KUy~V5BLGZ zRwj6EKIJCGwzyJo@abS?nd;%uaa(M?^frN9xI!~{{#kE{)s+*#YU1&1TgLVY{LALSZ}thF3luk{nu)2N^`V0#4c z*02Dbf-e9CK=;mur2ArSw+(px?qwcoa)tf#J*U}T(hB&4?kv~$16k#!iJzn!p9*k(vc12-?I!pg^WpCW70(_pM}NQ^=Qqq9?Ch;99#PMU`VI9M`-Qks$0BmrBeC|G z>*!qV3~8Adg}{q%L-8t2hmx!qo9oNYBf(!wE6&5;aHXgtQ$bTG(v>7B?BSd=&0Wc^ z5o#Ca?pI?Y3n2SWZSTq7@a>2~YH)h&kW8@c%N#Wf%`ZgzX5lQ0=W{2sii(MQ1*a_L zY?MJl(iu#-P|ZMEkCFPI)^9n-hS^N8C5+i575a6$j0E7Aj1Tlq-?q)oiINw`R*SAl_HYl(mau}4 zCSe-rcOKaDKB!EHpWxpzg*{4GQL4`4q=V!G~X2?vH3MN>NnIOzK$mjYujrz7 zDez3TvUT6`qewk-j@yPG^$bztxU9%8jjZcgi)&iCn#`R;fpK3lO{4{x;bl+ibt`+X z7$UjUC~L*q#z@G6)fPm?tVi=_=7xh73_3~$B&2-nP2wi4((e#n*L z2xJ!THG*28^o!ZE>IhEgv50jKQOFFRz?032M_2%(zj!03hQLrS$HdOhv&zWS$kI(^ z*eDAqvq7$JN7LuR1UiM(nW~FtL(2%k=ZlM^t>^o<5p`L%#O_NAtHX$y80Y&J%@K=x zt$Sn**weJ}mq`lQAf!e4BL93)Z{GL!5Wy~W`2fl!5BGg>MAIlwYeKkn*lTRh8~u6P6%#STqVtLvJD`tT>piwFXFBt zyPHj<)CUKztj}k{u;)PKOa2J;`Ua$xZMitp+fq40>%tVdG1zM;gPBSIyd)I7#G~V> zPIPhJU&t}y=pqTA_8s7MBSj$=5aeh~-pILI1n~ufJTxJJWPj)_c)~ zZWaOPr8lO($|I2kbNPhS_BG?HOtaoTO6E8Efc>Q9)xkrw`UcxmL$PL~L3l&c-csFy zD>4OG{AjM73nwYx2$t*ANIEnu6Z-uNJEoh$Msg2p_`SZT&!%CoRllc)lglRTsWMP` z83I_!QS%PXJHS2ww{>NjwJo?C+Nuh*Vjf0hs{Df-I_TP3S{!TxPp9P?#daqKe>);lh=TC5LWTmVaA3;6^Ee{wyru zcCOXDm*~%XV^wOi`2LgUlTy+8`zs<3wk@?fZagoSbhzj6FW%8;TlucP&)t8#OW+BM3>$zI8UtoI>p_D{I z0Mdf|BZqMCO0Bx${qZ;lk>cg5@|FvZk+fnQN$&6L2 z$2LNTkQRr_<>|O^s<8Xz(5C0zVHfnbRgigy6rjDMF<7zbIg@wwcH8veQG9DcH=Sfx zg$VnFW8MD1G|XZ$^-jm0(R1)QA28?%*y5~hMF4Ushuu5KyV3EH1VG=xr$g?{Z?F&(=>qG$C z5)5#*7oIH&Q4L2z`lcDOb4H=1DOl|YM1j?KNm;IoLRlqh=ZEECerVpGd~c9c7$IN3 z$Ypk|+xlUfA>3g0PfOVHZF&dqK0!_hXEV_g?h>mrx3tkHjXEKwAm6VdA9a31T@O(C zIP$V9KKOgNDX*mTMhChyQPOloH?9n>b8UisdqjNm%L%7GSQ*~jYqECrh7o+9#`I*` z924(P&p7w{*0~3Zu1JTs;S7s8D89$(Jp%};H2ol49B}OYw%S$ijIQ40g0nA7H!8>_ zYh^Tzx68cKEY|uObnjXECF$&V?(2FEuCGjCr-8@H)aHTl>XEA$9qDKO?m)4$Hh8Xd zYbf8(3i<*z&ZGlxE1gCgVgnYu<5DH99hd8le}Ih2`+`jX#Tdwszw?~VbCq_u?@ZXkoAa{X=#@&hGNcGJ@iBRsF7IK}N|IGNRyb;pa}OreJIlwl8qay$D@QgwY{8A4B! zJug_RA<(L|RiJBxgKL7MW$mVBQ9hlBi_x6GU3DN-Pt=9XHXD_P?5SncG z;t48B=z`;wK@C2~DVxuORM}#+s7wslJ7R{_*aIapmm-K7n5qLc*Y6U!egwC+)w7;8 z^VU1g4|g4Ly?SQjHtamiKO%TOIaBY*W&dH$kGyl}S70g6gGv9+$GuR}ZJFtw?4aKY5{8&8uQ`TH9Bv5}l*(8NO@Ui!jG% zA-rfW!|CWsZSP^C{=iH7*-X z;=)3T_dz*e7q5?_p`ww~L#lqk#@mzVv(kvut%yxii@Wj5MPk19DH-zn38{BG-Ia(J zEu9K%2zOYcA>Jj1XQR&p!#5GP-UMq<-aC!z`UtXSS&GU>hdA~+GMRZQ?ZEf|=k*}s+?S6!dSHddj;#hEbB2W;oosrAcsY%k$Q z_Rdz#*WRk;IWGYI5Lm$R34Zx8iMxb&rQo%Rd%F$~NVoK$I4T+27yrsB%3k`tOTF{C z%&lq9+tih(CjztGxy;@1GbL)QpP!P&LkvYW>MUQN2U5wFeBXx%7=jQ};q@6$ZiiN} zAgV9o2`HvwBiBARV)zV~=dR91$s2rcR3Ojb1FafTT0~UGIX7ESHR@hTywEFJ_BIHH zO~#wgWd59zSm1}A7#CZ+n`_EbrIj_x78VkKRy-0A8)Ty@`k#bNIc5?~y7VQ=* zvHi+$3u9US+cwFCUax;`4v^|96kzxaoo6A@3>?He)MxvYl)G%Q1-s$HY#*KXkpUke zJQUdPc@~__v71nh{fU?aPhmu|I1f3$H$=!5Thkp5(HJ#|iofc5*chpZ5;?q=%@03_ zv9R+Zk#;4-DBT}D%QALXQmr&9{+$Es@k>dSswd)8%9Xj1F@14j3KT{t{K2nJ%MTPA zN=P?DcqA1vve`>wHP7sFg1W{(aA>;gw$s#;4)~-{H+@FU`R>5SXcF}WU;UbCJ{7?? zPMdB~aL&Oho#T2zRHq$HwIc#yJ^`AK*4r^=v>dL=27@tei&=+eJ<)A?!Mk}0)(YfW zCS`$`G5*qZUho1GW3ipp?xO`uE}@b0dK)6SUk-m(E`5wlQKrZ|4_iL+m73)Hn6@|h zY%?bLHJz_(DawaXC;=@IYN+-lYLQ3}SB~$q-u@UqY*3eP{0M=5SW6rb>s+{A#c&%m z8_q*fh-}mw+og?HA|EQTqK7%qYO{?6^m#hM#kwTH+7u83p#b}tSLirnbjgP`BFk$a zGjeKyA@1Z z3zUul%noLHB<*_i7hHblwlri4e0ll-F3Mi;huICWxN4-*Lw026!^JY8_RCThqHN zsuw_(_iCCfFk-F8H5t1VR`G20viKr41#D1Y?*aWa0>R#sOp&Wkpuw?4*UGY>EMB9Y zo1>qSlLfN}n3-UHlAxU!#BxM@O11V<*m}|XQqZYD#AVn3*Xoq9u4mb#XJ zrxz8F0JV<4=M6m2?m!2fnKW9-1J_=XcaNHH%PS(}-H~xP2=ww^5_4S=70M>lP_)v6Rg0T4p z&qOxC<8J2W!0BD<^8_Jh@Yzw|4}FgU5oro~sTc187}uaTx0+D>1w8KzTps?cco!hY zc+JRCO#xx9q!eI;$;*)JUGDco-<{{RUR=%~9@w#DVv$w)N4T-d5HaeCQ?un}`;4@) zm?hk8a)~|p5YwppvFAe|aeu}#@;gcVxLKJZ&_!G9)8nPP>%|%0%}V`V!uEj`Ihn-H zkDg%k(UjZ0TO^j*>J_==K+CwSGxzlwVp@7Es$Fs#nm61=p-*cZ>`jk_6akU~_+RNpZvXM;S3E z)DdCD;lCXp7!NED0+MPJ!~_ir)pK62W^LboQ#e>iaX)cpVyr-fk~PLDQ;cE1>EYE%;o4LDfj%gH8VSo-bkH(#nrugsqNFR=e$FwDQ8E{OR^9n6!n< znb17NH&{_(4Y6XuQU0;fePRGO6lrEzD&imHsd<}tTVn$8LB9D|(g@J8aQx+^MrPtI zYS||W;yB|uRL5aAYy)qq<-|KDn939sd}BwE?Wf$Vq2mAy(cio9U|f{+ph-Mq>3|SK z73yQ0;S-q&K+l6Q{wL3V0L%dUUFH(+F6$KLA`!T-9wcn1A_KU~Z#cNA-l3V|#Ma?_ zsiWGv_G z&!cvBr+v|m(f7Q0mwB^>{0*M`hh|S4!588U7V(75`~mOWmr6O1lXI_DZ`Bv(=W0)i zCF>`rIT#!r28wFVY6>J9t83pSm^9DdAy06g#JInX;4<2NcO9U>df0nMF~8r=DScO|KQn<4I`|02@zB}pmg?rfFJH6zz zU;@U6PVex;>v4bk(ABU58QPi{tJvF`TE6>hLu)ZQBGbu6f_T`t^xRL!arTv95O59+ zE=`1-!hRbc6f={Z2_X$w!NonTprkWrGi~p5d{x%aJUBL0%Z9G}>G@qt+Bb6G;7<90 zH#IiW=LC3O1_U@i{w4*@P9MZ;*L4ddzpkQi!Qmiy#UL*=SN9=YF&M!CKlr3qHIA6r z3NaTNs^CyIW)=)LCI>>UNDw^SSBV(RZCB7ZTTNsHy>HtxWz1EH5HW|#)98h7Z9MSR z@}_yd&$!96Zx^YYrB{`&_;{nLBI8i48N| z*CBLCmA9jd-`l~)b>y%>*-%PMcqa=BnImb#8~fqf3|nd-Hp)%Ob#J)EghLOpZ=e&Y z=kD?{M&Fy$iH)OGrUl4N1tT#IFsi2?<-siw;2hDZn~xlkd0E&_FQ1&VG8k1SM5dqU#mEj&9SwoZjQiEY682&dsPC z)<3yBcfPs1vwpd|O4_TZxP!fLGO9&qZN%c-x@?Ya*XIYLCnc^bdkd2%n;)`o&y)?z z`6gVD?TN#2Y=06OS=tMNkAt$0 zh*h6si}IlxSYyElSkycT>#SOJQD~l=m!|A=|L8UHKEXG5MUYO z!9flAv}T;_L=`N(4YF~%A(>t^?N>j44u!FqfFk5rNxNFWcq( zvqh2F>t{-VHj!)h%|>d~=-)E0_t_6WAjyZxZwd4Fg8@A}lYI#I>vBP&`hm_mcmcf% zdUm<-C&d>-iAPM&JNVtBT?9h4QM(gS^WwuND2@kh=%dVK1qhxtU)Zew(RTiHq-FQ+ zp@yJ6w4NWSIPI5Kmi8u=_WG*M)+Tnkk9DLtc0d8CD#VRIxUM7#b{_EH_fP$jPFBe#3vRfi}X)5pcPzl7K+}I$9z0a z#f$e-=>Q{gYr|HI9$uNTX(G1y6p&68Nz=cu4zGY_EIWoK8Ip6aq8eoz24mW?ylt~` zZGO(uEW7k7K?{wC|p>l4>ifRCv93rqZ(qQqcKNE}s^N{juD*DFh8Jhct( z=`Al_W8b4JKgSKHwwnF0%wR21c6|0`6j`3SV_On;5ru$)R<-cylkxmG5uzuuSgK!Y zibOtm11M?Hgj4U0WyzOqFIci_uHLM`#MFM|#2|{(+>pEYY;7(McbZC-0{kx5iz!NH z4i(KknR>SWi6pf7G-?XrZb1ZJP*lM_-)qyV)ecH?1MYC3_cHJN$8cF^75(XFl)d4l zO=huK_k!)k)0U{3R;H#O?LUloZV;4Ypf+J?i{)N<#8)oxf7%e>r>1$)DrV+2i=zDTm+v{dvye zQTV67XMb1w*Qtj;A^MZM|D_%E@JRp1>A#u$Kh^)Fp?|5vKk%Nv)c-?D|F4^VaJ$_wTU%uSq`==0C?{ggt&n_&56e_nH4mpg-#Vj9S5e>HebAm1N=nN>;3g O(lPxYGpp1b26LC%6UopaBv*SP~?-1^sn0^S;b5 z^Zftt@6H-uF8cx{@dZ6bzSA9`~&%{s&$S~c5AJ-SmN!m33&9r0(IwrtlrDl|qYB~ew* z$HI5AIZ}azm0D5tu@OAEglLC{@E;lx6k8Te*sQ4@sA%zd8j!iZ_M9m1h*yM)O3-#P zeJO|j@o7(Zq*n)8fzg%Fgx@{rIV-XJ>WhvJHBK%qnwkt3GEKI`%P2o{;vC@lQp*{N6BMq4++p;#R zn_LZV1p}vJBQVo!_FOlxu2|?cH4lISI1Ob!Z zD6%$$NPPzJJw?b}6bMZX zok6xP>})^J|D)%BF(>~r^vZZ8l|CS9@S*H&$nfRdN-Uj?l9%W26tryHm)FblUr4A%C(a#0u!Bcvb0m>dOy~y zx%0V;G#PmhhF6_2OcgDqc?u(I)Y2c0CF?LJ*1tdECO+TavC0DbvlB*F~v!+nB>EQ z8~9V$)`RAYE;L;1pmQVSp8h*zM$$LUO(^rf)7oK?P; z@-61uZsK6)ozmynnPr)n2EZY=l_^Hxwq*q7$fZ%}KwvQqmX#)Ebu&dgurlD?s8tzp z1C3$DxHrL^Lt6KDyrCy9nv-3@H0*HPC`^eu1A=@7FNHK#O? zY4O|8rY4fl!h9!Pg$^x|cuQ=KL<+m(w&K!B4|GP3f+~B3wXTrAwB|^kcI!62pwjNZ z$alS5_U8V^&Om*sx2M~};P4pYsdkgjA z)w^UZY5W$GYy8ONZVAxq_0}~Whd_>CMVyg3I_e&tE60+(`s|82nN~oEUf0N4xIFY5 zxO?cTsSWO47uP6Pd(HR(OI)Qx+~FL~>yQo7(`{}_>mn%u;UhW`*4Gb?^vY3Y2;2@H zc86=b2%enQr0!KkaF+>N2xjZ>qTq$eMQ6 zxRv$?5P3recrg+!Owiu#TvP0p5qWk#!5wQu7nxH0O7Zqi^k);kp$ltQi)UOwKoN{`Zd(T7nEQyW}MSO{kPmxMtpb8 z^R?SDRcd30f(R`}>6-#j3~$7_vhsH4&(g_9Ka)co{hw3_5J^lqh1_X_(193$2nC_S zA0gwfg!oVRfP$zy2%-MFkILst3jGjugK{4Nbjx^!hqVA^r`p%t!$2HqU|gc3Pess91iyW z?q0=r6!df#oK}(eVU}!M{x4Syv_v5V#Z;1Ktuw*g))PQiPHUGbvQfzPIo7b{fBjm0 zzbdeyxtSuJ(R}-%RbuG^J}1zFeEO6KNlpmO12FwzJ?C>^*mh}K#H;#;-y{gPIZadK*t0~#s zVA{cuoQ^N?s#|G1iAQys+TF9+`@z1h*?v>c|5>b&x6~45n}F%dhTPUkTLgIFDA#m^ zTd>U+L4*!vIo&$WNee|6D@&Esb_K#Ft3v-6EnA+{DG5@BC#qczpsxt&_$i;c-7@U> zu(aGlwdH90IK&vIP3~KG=Z}+m-(d_SqBuLcr(Efyf#UBfQdS2z^AzkL;o~Th1h#h=J>SJnqOQ}tEw*!FX=9##MPT($luBK;pk8|rX ztJkm1i|i{*-Y;5sdb}lzhPGCIG;kmqpx>-!oVNWjF|^L^)zR;?sDaA ztnT92(+2*r`UTtc$x)66nM1;v8%bLM+Kn69<1bxdEO_@QKi%S&ZzRSlq4!c?O4o3v z!N}&lGWD0->$SlYuSBWZney~Ouo}$C${5)iyE%bpL1WxD9qw7O^6uVJrJfRA4sAAw z0Gfpi+NPsZP9zTPF&a$J&VI9FdE;Znzf9#QpUL*F&iYtuAcv2>pj)<{ov#Q>C+{&qK)5$ZC`7hkl;F1o5xeQjDFphDaPKzkzQ)HZvU35~PFy zV&w2|d;>l*gjre>Ni8f!-`(u<Gr4R>{F!XD5@yZbO!#I+@vBf`6lAe(eLs zO9TL5oZ@G4{)>`eOOPFi{nz;y8~1heB1rgfec8`MQBM!9csIwe9gcaH%=nOW=!;@> z^)47Hh=}A?qmTmPMUuqxVv{>?AyaZ%&dL_eft&Jpqyi14D3nUg4VPSr) z9m=-7ij~hwUQANIN%@Z2Cx(i0JtEm~M%35o#std_B(7l}%d-W4O`k9ZI~al|OrP_l zk`65pSlamFn#X_CRljSs=G=zahuB}5THDGxqN@Jo4m{C3popBT&FlPSqwPA{-5gKG zQr>mr5pm)!#{5x`7(s{SIuFA@h|N{)hy63#JL1F*8=EW7vK5rwWM8y!208SPQ$@8} z-`QTY2PzV-IQ3>21500UZVMp4HBK_H4o6bhD%Ovfwi`qMs6Gp)krQf7T}L7o<8;HK zMaXn-HzO9?7+39uMNMHU5-US~m~+S#-3iTSjYPEq5olA*2PX<=6FPAwus(2Fo}&$K z%ETxiJh(eQlq6t?3bj5M@HT6KM!x5oS&!bGpO^Ug9S2-~#W1`-|6C8={oroAk>h|zt<-UPc3 z5laDDDt9Tbjh!0B-8G;TSZ4+qExBqKAzxg}t0er_8jz#0;9e-wP zw!XeT4^CH=iLZ1A@ZvJnt?dkT(91cK{x8E6KzzN)X&)KoY0@`f6;ynN&^caJ9{7l} zbREQDSp+H-UWyz)4dplBq(RW4+7=h232tH0hZiqpUE5zdc^cWKwbCcUi`@j5kG>g_ zs%ziz`U~~|TapB>`iJ#)S!p)vO9F4Rjcal7!je?Z8qq=V7PD$6?Ha5z=5F9Y?cubP zEp;nR2wPTrCH_r#d02jr4C<~vBnfq7jB%y=n>nsi^okP43;wa>kGJYbjvy;IHZOwBkSBw4MO0^ zc+we`P zUKvVBW`cap^mHClhkP#bYj3Bg+vxx@Y3EBmtIj8sd;8BPd$Vd#><11#E(rI>me||a z#o)VYQ88~^Zp=eVdBn1T60mT=5%yyJ$bM}36Pm2NZlg;3N*N(g$kY|9VR9$$H@<2t z;M@y>V%q%$f&z!Ct~BMdloH>%ly@(@zQIW55$0B@HszO_7uf447pSqmeelt&w_?xv z$oaMLy$9>2=Zz<8qvFeV&p-)o9^gs()lVmBnDUp~fg1*S?IO^tmV+>av)>t367(AZ z7Fgczi52nfN8^%@`4bMYh`!5M!dKuPgTRoVkK&uX}%62={+I1BLm$z3cz$ETR5E?6*lc>28su`k-<7S(` zez}mQJq^~LR^3g>)0KAGMh4p9;kj>Le3t6m4iQ@v3pOWB{A!9<(va2>S&clACheKP zRh9;&YQoM%5d&6VXmj$gcC{)OmMLVVjs(|70cU`L1nVq<WYprt zZU1oFH5#@2*bzP>kQhzPWB5`Wl7bxF> zmB}~8t+RU?2R%Jh1udk-%@;5&y}1$l2QOo4R@R2b^wLqN@d?u2<*-}#o!|MiZnSlg zmuGmaQ9pa?W|JgVzqmh(JZ|l!is+doowrcsirlH&yh?J`W?Uf9^3_X*n6#l{C3oHd z*LvH&8FN;C^j5lF6@w}1m8t{Zwb;!|t-I`4G7b^%WXExzyT%L?0$;EQ!nO4(Myx1O6**79>BN1ATU=`QCM8!J4ZDUDL zH8NcVuAf_qfJ-E?gqc`NJokXi;%9=2nl5=_m#wo`DrI5-$_(7YsIrvmr7bTeS(hSf zrgep~1`7D5Hs=go_2=9d@t^HKd(<%_CbYmjik)RheBY9v?XM-5VW;%1XtESG|Okc}`UV|1I@;JZ`4_V(W z&mNdHy*tIiskyW4g|l_d=S)__i4{TIjL`@^OjfJ;UcHo^BClS^RkzH9js!L}$R00` z(qJ`+p58Ybu0Gs{Bqd1#efNt3@W5$g(Ma%4P2wMS_jw-FrxDvIGrx1+tW6$P&S7MB zEf){aI;FWnt>COYBR}KFexou_j-ms@VVap!&r`nu-NO{{mRDm8mDf^xjm(C@5{c|n zNUh*y|Bie@0l8#~p(U=xo5uV_FkXIzL3lmOOZI9$1K@HPi1#=PZv`cDfzbdEl^H&X zko&Sho}U$sM!eqIVNH7OXqnM8cTP*LN4AJ&y(w9lFBq=-OX8`X0?%3sM(q;zNYL#NGox)aakq>1vtu}D(qfHJ&6WRHXa5X*O zV7Q##%buYa!B>Mah1EYef@GgnZ2C4I%y1smsBjQU)iiQ)^W9+pguH1Kzac2?3NF4v zrZ7JL!L-5TXS&nBhdeQOqMi+F7)f8xKA7Nm20O!LmmyOz<~-}CVdGV_pvSNxr|#RJ zl%wgKOp$GwJ+=ddXxyuSp}q6|$?(0!^V_fOZhm*yT&SopFu5xMPJL#R2+7A2e1LjO zndrU6;ZI`4rd{>;M}@hx0*)?j_o*9BcdV?R#GavtsMjoXa*|1g51lhB4EWmW;ido3 zgb{UgF9o6K^QrnbPUYF5UzpI@?TI)ZjM{a}`PP0**ocL$|@ zwf=S;=)BXj&~vq@9wASU*}!9!3mP>Qek*YF1=NO&oLQfCW~?2z_cIF>P>X zx2|H4hJIKaRwcGdA)(4Mi}ItRq~!2P=TL~lm%UG=^zTWrb@+2|b7My};BM4y%P{wZDWlOq%DGVW@~AiR3jQ*=(^~bo(ajZ&Zr)!N56!BuP|;WZRixmP&Z8= z#qnVp0a~*-y1y4!LuS3MZ%_19a1hsqmivp6v#Bn>^CgUe0irt`U(<(cXYZw)#m)8l zHQ}5^HogaWjKh_GAHJX+r$P^8*y4~v5A`3z|5*z9GrRwzHuO8k|5X`!9n%xu3&fSW zgt?b+_ez+5gDRoUN7bVF6~^Rx5q>Ep_7eBbtDY3py}j)?xy_q#BUDCWjvmJ{+Xnq+ z%ap*f_Q-3IwM(pye|H%diB%}p_ygO!bQoo2`LLl_Z9J5^H)=F@gnhpK3w6;*Ww9b( z7UaS_fFnO5NTw>wt?f<>5gOQ)8xL8wd|m`S6@IRBzaQ;GE@WTOH6eR4`C3jO)<`5@ zR$y4_`-#7a*q+-(wv7)uY_Z+@vC|8re{%q(8h}j&WaP1sPAB~zGvEkWc`(RX9Rvpd zbOH89zd6`UtnB5@hK+~K7>pR8<2_JmQOlm;Gy2E9N&Al8hoAiU0WC5TH50YIMT!48 zAtBKf5mhQ-02mgBD;8`9q#V(>4JXQ*p-4vxHTDJ@K#64=E5mCv5Lzd~rsAt2h{3~S z2K&QA>S;SJ45FrB8X*FW&8NOS(+b}S%ID+LgIo7Cg}pu%Js0M*-pIv((zhZ>>B9i< zz}^JA2W-I+^2V0?a7&>&{%qv_i^DT?Nu(%1R@)4cKVv~kfMyP+s?H9MF6^cb&Y(a0 zy8o;EL8dM;-cX?rh&!+b*CjFHmC@8EAhEPg4jyWBWUhb9MJYUZEw7=Nf^2G zop({WjZnld&@cr<#b8dg^wkSg)i@{VRH$8Ib^V(rJlKk%8`PM=@5naRD>DvW)c$Mx z5xN~Hx@wx{SIytz41%&zgpSf=*FKkaDB2&+qi7U5r3W8trYg=KM;4}RnOx7m^KtGP z|Aetc+4B&_72i+2fu#y=Re6v)hRC6RZn@#RA3nrGJn&~r6Z5YgXzb|tKL`BD+CPrW zcrp7WASCQSc_IXOWjGTTmH5~%Gnw*@IRv>ROKPyam=_%Wgie() zszPt_#2XF@t^YNP{(J2(Z6*Ggw%3i}67gh=Z!m1$%m(9KN*QzOdv@vsN%*E0o1*K% zrbvjCUIbUVWX@i76}NswGwd)pV1iEGP|(yVz?)&+b}I;qdDp$Qrxk?P!H%4-=f4UY zB$$T0>M$VAj6z_>f&!vOyyw8=QdP2a0f{H(56Ynt`^e^d&k|u@62cf`8675a0yrwl=AD_qCQ2(Mn_74XYJoJsfNCX zai2x7CwjV!{uN<4X?2Yps_8{>!98ae=YhzohJs%i=4!nlX}DAw^cL==$kN@j_Y6%& zBnc7ETk*CI1&72>XPQ^sk_(Q3cbPNP7v_O}-ugcv#kN2E7z&ydA`|}oUo!t$xBp!K z s$skhTu>^Q*{`VdGujcv`e=+}a|5jCigZLl-fC~B0LEM<;r?3S4A6wYBBme*a literal 0 HcmV?d00001 diff --git a/orion-ops-api/orion-ops-mapping/pom.xml b/orion-ops-api/orion-ops-mapping/pom.xml new file mode 100644 index 0000000..55449e2 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/pom.xml @@ -0,0 +1,38 @@ + + + + cn.orionsec.ops + orion-ops-api + 1.3.1 + ../pom.xml + + + orion-ops-mapping + orion-ops-mapping + 4.0.0 + + + + + cn.orionsec.ops + orion-ops-dao + ${project.version} + + + + + cn.orionsec.ops + orion-ops-model + ${project.version} + + + + + cn.orionsec.ops + orion-ops-data + ${project.version} + + + + diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/MappingConversionProvider.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/MappingConversionProvider.java new file mode 100644 index 0000000..ce90736 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/MappingConversionProvider.java @@ -0,0 +1,33 @@ + +package cn.orionsec.ops; + +import cn.orionsec.kit.lang.support.Attempt; +import cn.orionsec.kit.lang.utils.reflect.PackageScanner; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +public class MappingConversionProvider implements InitializingBean { + + private final String CONVERSION_PACKAGE = this.getClass().getPackage().getName() + ".mapping.*"; + + private static final String EXPORTER_PACKAGE = "cn.orionsec.ops.entity.exporter"; + + private static final String IMPORTER_PACKAGE = "cn.orionsec.ops.entity.importer"; + + @Override + public void afterPropertiesSet() throws Exception { + new PackageScanner(CONVERSION_PACKAGE, EXPORTER_PACKAGE, IMPORTER_PACKAGE) + .with(MappingConversionProvider.class) + .with(DataModuleConversionProvider.class) + .scan() + .getClasses() + .forEach(Attempt.rethrows(s -> { + log.info("register type conversion {}", s.getName()); + Class.forName(s.getName()); + })); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/alarm/AlarmGroupConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/alarm/AlarmGroupConversion.java new file mode 100644 index 0000000..87d5b59 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/alarm/AlarmGroupConversion.java @@ -0,0 +1,33 @@ + +package cn.orionsec.ops.mapping.alarm; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.ops.entity.domain.AlarmGroupDO; +import cn.orionsec.ops.entity.domain.AlarmGroupUserDO; +import cn.orionsec.ops.entity.vo.alarm.AlarmGroupUserVO; +import cn.orionsec.ops.entity.vo.alarm.AlarmGroupVO; + + +public class AlarmGroupConversion { + + static { + TypeStore.STORE.register(AlarmGroupDO.class, AlarmGroupVO.class, p -> { + AlarmGroupVO vo = new AlarmGroupVO(); + vo.setId(p.getId()); + vo.setName(p.getGroupName()); + vo.setDescription(p.getGroupDescription()); + return vo; + }); + } + + static { + TypeStore.STORE.register(AlarmGroupUserDO.class, AlarmGroupUserVO.class, p -> { + AlarmGroupUserVO vo = new AlarmGroupUserVO(); + vo.setId(p.getId()); + vo.setUserId(p.getUserId()); + vo.setUsername(p.getUsername()); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationActionConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationActionConversion.java new file mode 100644 index 0000000..9b8f169 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationActionConversion.java @@ -0,0 +1,99 @@ + +package cn.orionsec.ops.mapping.app; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.ops.entity.domain.ApplicationActionDO; +import cn.orionsec.ops.entity.domain.ApplicationActionLogDO; +import cn.orionsec.ops.entity.vo.app.*; +import cn.orionsec.ops.utils.Utils; + +import java.util.Date; +import java.util.Optional; + + +public class ApplicationActionConversion { + + static { + TypeStore.STORE.register(ApplicationActionDO.class, ApplicationActionVO.class, p -> { + ApplicationActionVO vo = new ApplicationActionVO(); + vo.setId(p.getId()); + vo.setName(p.getActionName()); + vo.setType(p.getActionType()); + vo.setCommand(p.getActionCommand()); + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationActionLogDO.class, ApplicationActionLogVO.class, p -> { + ApplicationActionLogVO vo = new ApplicationActionLogVO(); + vo.setId(p.getId()); + vo.setRelId(p.getRelId()); + vo.setActionId(p.getActionId()); + vo.setActionName(p.getActionName()); + vo.setActionType(p.getActionType()); + vo.setActionCommand(p.getActionCommand()); + vo.setStatus(p.getRunStatus()); + vo.setExitCode(p.getExitCode()); + vo.setCreateTime(p.getCreateTime()); + vo.setUpdateTime(p.getUpdateTime()); + Date startTime = p.getStartTime(); + Date endTime = p.getEndTime(); + vo.setStartTime(startTime); + vo.setStartTimeAgo(Optional.ofNullable(startTime).map(Dates::ago).orElse(null)); + vo.setEndTime(endTime); + vo.setEndTimeAgo(Optional.ofNullable(endTime).map(Dates::ago).orElse(null)); + if (startTime != null && endTime != null) { + vo.setUsed(endTime.getTime() - startTime.getTime()); + vo.setKeepTime(Utils.interval(vo.getUsed())); + } + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationActionLogDO.class, ApplicationActionStatusVO.class, p -> { + ApplicationActionStatusVO vo = new ApplicationActionStatusVO(); + vo.setId(p.getId()); + vo.setStatus(p.getRunStatus()); + Date startTime = p.getStartTime(), endTime = p.getEndTime(); + vo.setStartTime(startTime); + vo.setStartTimeAgo(Optional.ofNullable(startTime).map(Dates::ago).orElse(null)); + vo.setEndTime(endTime); + vo.setEndTimeAgo(Optional.ofNullable(endTime).map(Dates::ago).orElse(null)); + vo.setExitCode(p.getExitCode()); + if (startTime != null && endTime != null) { + vo.setUsed(endTime.getTime() - startTime.getTime()); + vo.setKeepTime(Utils.interval(vo.getUsed())); + } + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationActionLogDO.class, ApplicationActionLogStatisticsVO.class, p -> { + ApplicationActionLogStatisticsVO vo = new ApplicationActionLogStatisticsVO(); + vo.setId(p.getId()); + vo.setStatus(p.getRunStatus()); + Date startTime = p.getStartTime(); + Date endTime = p.getEndTime(); + if (startTime != null && endTime != null) { + long used = endTime.getTime() - startTime.getTime(); + vo.setUsed(used); + vo.setUsedInterval(Utils.interval(used)); + } + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationActionDO.class, ApplicationActionStatisticsVO.class, p -> { + ApplicationActionStatisticsVO vo = new ApplicationActionStatisticsVO(); + vo.setId(p.getId()); + vo.setName(p.getActionName()); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationBuildConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationBuildConversion.java new file mode 100644 index 0000000..90a1075 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationBuildConversion.java @@ -0,0 +1,123 @@ + +package cn.orionsec.ops.mapping.app; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.ops.constant.app.BuildStatus; +import cn.orionsec.ops.entity.domain.ApplicationBuildDO; +import cn.orionsec.ops.entity.dto.ApplicationBuildStatisticsDTO; +import cn.orionsec.ops.entity.vo.app.*; +import cn.orionsec.ops.utils.Utils; + +import java.util.Date; +import java.util.Optional; + + +public class ApplicationBuildConversion { + + static { + TypeStore.STORE.register(ApplicationBuildDO.class, ApplicationBuildReleaseListVO.class, p -> { + ApplicationBuildReleaseListVO vo = new ApplicationBuildReleaseListVO(); + vo.setId(p.getId()); + vo.setSeq(p.getBuildSeq()); + vo.setDescription(p.getDescription()); + vo.setCreateTime(p.getCreateTime()); + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationBuildDO.class, ApplicationBuildStatisticsRecordVO.class, p -> { + ApplicationBuildStatisticsRecordVO vo = new ApplicationBuildStatisticsRecordVO(); + vo.setBuildId(p.getId()); + vo.setSeq(p.getBuildSeq()); + vo.setBuildDate(p.getBuildStartTime()); + vo.setStatus(p.getBuildStatus()); + // 设置构建用时 + if (BuildStatus.FINISH.getStatus().equals(p.getBuildStatus()) + && p.getBuildStartTime() != null + && p.getBuildEndTime() != null) { + long used = p.getBuildEndTime().getTime() - p.getBuildStartTime().getTime(); + vo.setUsed(used); + vo.setUsedInterval(Utils.interval(used)); + } + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationBuildDO.class, ApplicationBuildStatusVO.class, p -> { + ApplicationBuildStatusVO vo = new ApplicationBuildStatusVO(); + vo.setId(p.getId()); + vo.setStatus(p.getBuildStatus()); + Date startTime = p.getBuildStartTime(), endTime = p.getBuildEndTime(); + vo.setStartTime(startTime); + vo.setStartTimeAgo(Optional.ofNullable(startTime).map(Dates::ago).orElse(null)); + vo.setEndTime(endTime); + vo.setEndTimeAgo(Optional.ofNullable(endTime).map(Dates::ago).orElse(null)); + if (startTime != null && endTime != null) { + vo.setUsed(endTime.getTime() - startTime.getTime()); + vo.setKeepTime(Utils.interval(vo.getUsed())); + } + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationBuildDO.class, ApplicationBuildVO.class, p -> { + ApplicationBuildVO vo = new ApplicationBuildVO(); + vo.setId(p.getId()); + vo.setAppId(p.getAppId()); + vo.setAppName(p.getAppName()); + vo.setAppTag(p.getAppTag()); + vo.setProfileId(p.getProfileId()); + vo.setProfileName(p.getProfileName()); + vo.setProfileTag(p.getProfileTag()); + vo.setSeq(p.getBuildSeq()); + vo.setRepoId(p.getRepoId()); + vo.setBranchName(p.getBranchName()); + vo.setCommitId(p.getCommitId()); + vo.setStatus(p.getBuildStatus()); + vo.setDescription(p.getDescription()); + vo.setCreateUserId(p.getCreateUserId()); + vo.setCreateUserName(p.getCreateUserName()); + vo.setCreateTime(p.getCreateTime()); + vo.setUpdateTime(p.getUpdateTime()); + Date startTime = p.getBuildStartTime(); + Date endTime = p.getBuildEndTime(); + vo.setStartTime(startTime); + vo.setStartTimeAgo(Optional.ofNullable(startTime).map(Dates::ago).orElse(null)); + vo.setEndTime(endTime); + vo.setEndTimeAgo(Optional.ofNullable(endTime).map(Dates::ago).orElse(null)); + if (startTime != null && endTime != null) { + vo.setUsed(endTime.getTime() - startTime.getTime()); + vo.setKeepTime(Utils.interval(vo.getUsed())); + } + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationBuildStatisticsDTO.class, ApplicationBuildStatisticsChartVO.class, p -> { + ApplicationBuildStatisticsChartVO vo = new ApplicationBuildStatisticsChartVO(); + vo.setDate(Dates.format(p.getDate(), Dates.YMD)); + vo.setBuildCount(p.getBuildCount()); + vo.setSuccessCount(p.getSuccessCount()); + vo.setFailureCount(p.getFailureCount()); + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationBuildStatisticsDTO.class, ApplicationBuildStatisticsMetricsVO.class, p -> { + ApplicationBuildStatisticsMetricsVO vo = new ApplicationBuildStatisticsMetricsVO(); + vo.setBuildCount(p.getBuildCount()); + vo.setSuccessCount(p.getSuccessCount()); + vo.setFailureCount(p.getFailureCount()); + vo.setAvgUsed(p.getAvgUsed()); + vo.setAvgUsedInterval(Utils.interval(p.getAvgUsed())); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationConversion.java new file mode 100644 index 0000000..5186b2c --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationConversion.java @@ -0,0 +1,37 @@ + +package cn.orionsec.ops.mapping.app; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.ops.entity.domain.ApplicationInfoDO; +import cn.orionsec.ops.entity.vo.app.ApplicationDetailVO; +import cn.orionsec.ops.entity.vo.app.ApplicationInfoVO; + + +public class ApplicationConversion { + + static { + TypeStore.STORE.register(ApplicationInfoDO.class, ApplicationInfoVO.class, p -> { + ApplicationInfoVO vo = new ApplicationInfoVO(); + vo.setId(p.getId()); + vo.setName(p.getAppName()); + vo.setTag(p.getAppTag()); + vo.setSort(p.getAppSort()); + vo.setRepoId(p.getRepoId()); + vo.setDescription(p.getDescription()); + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationInfoDO.class, ApplicationDetailVO.class, p -> { + ApplicationDetailVO vo = new ApplicationDetailVO(); + vo.setId(p.getId()); + vo.setName(p.getAppName()); + vo.setTag(p.getAppTag()); + vo.setDescription(p.getDescription()); + vo.setRepoId(p.getRepoId()); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationEnvConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationEnvConversion.java new file mode 100644 index 0000000..4e47efa --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationEnvConversion.java @@ -0,0 +1,28 @@ + +package cn.orionsec.ops.mapping.app; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.app.ApplicationEnvAttr; +import cn.orionsec.ops.entity.domain.ApplicationEnvDO; +import cn.orionsec.ops.entity.vo.app.ApplicationEnvVO; + +public class ApplicationEnvConversion { + + static { + TypeStore.STORE.register(ApplicationEnvDO.class, ApplicationEnvVO.class, p -> { + ApplicationEnvVO vo = new ApplicationEnvVO(); + vo.setId(p.getId()); + vo.setAppId(p.getAppId()); + vo.setProfileId(p.getProfileId()); + vo.setKey(p.getAttrKey()); + vo.setValue(p.getAttrValue()); + vo.setDescription(p.getDescription()); + vo.setUpdateTime(p.getUpdateTime()); + Integer forbidDelete = ApplicationEnvAttr.of(p.getAttrKey()) == null ? Const.FORBID_DELETE_CAN : Const.FORBID_DELETE_NOT; + vo.setForbidDelete(forbidDelete); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationMachineConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationMachineConversion.java new file mode 100644 index 0000000..5276825 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationMachineConversion.java @@ -0,0 +1,22 @@ + +package cn.orionsec.ops.mapping.app; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.ops.entity.domain.ApplicationMachineDO; +import cn.orionsec.ops.entity.vo.app.ApplicationMachineVO; + +public class ApplicationMachineConversion { + + static { + TypeStore.STORE.register(ApplicationMachineDO.class, ApplicationMachineVO.class, p -> { + ApplicationMachineVO vo = new ApplicationMachineVO(); + vo.setId(p.getId()); + vo.setMachineId(p.getMachineId()); + vo.setReleaseId(p.getReleaseId()); + vo.setBuildId(p.getBuildId()); + vo.setBuildSeq(p.getBuildSeq()); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationPipelineConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationPipelineConversion.java new file mode 100644 index 0000000..28f72c5 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationPipelineConversion.java @@ -0,0 +1,48 @@ + +package cn.orionsec.ops.mapping.app; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.ops.entity.domain.ApplicationPipelineDO; +import cn.orionsec.ops.entity.domain.ApplicationPipelineDetailDO; +import cn.orionsec.ops.entity.vo.app.ApplicationPipelineDetailVO; +import cn.orionsec.ops.entity.vo.app.ApplicationPipelineStatisticsDetailVO; +import cn.orionsec.ops.entity.vo.app.ApplicationPipelineVO; + +public class ApplicationPipelineConversion { + + static { + TypeStore.STORE.register(ApplicationPipelineDO.class, ApplicationPipelineVO.class, p -> { + ApplicationPipelineVO vo = new ApplicationPipelineVO(); + vo.setId(p.getId()); + vo.setName(p.getPipelineName()); + vo.setDescription(p.getDescription()); + vo.setProfileId(p.getProfileId()); + vo.setCreateTime(p.getCreateTime()); + vo.setUpdateTime(p.getUpdateTime()); + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationPipelineDetailDO.class, ApplicationPipelineDetailVO.class, p -> { + ApplicationPipelineDetailVO vo = new ApplicationPipelineDetailVO(); + vo.setId(p.getId()); + vo.setPipelineId(p.getPipelineId()); + vo.setAppId(p.getAppId()); + vo.setProfileId(p.getProfileId()); + vo.setStageType(p.getStageType()); + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationPipelineDetailDO.class, ApplicationPipelineStatisticsDetailVO.class, p -> { + ApplicationPipelineStatisticsDetailVO vo = new ApplicationPipelineStatisticsDetailVO(); + vo.setId(p.getId()); + vo.setAppId(p.getAppId()); + vo.setStageType(p.getStageType()); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationPipelineTaskConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationPipelineTaskConversion.java new file mode 100644 index 0000000..2eceae0 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationPipelineTaskConversion.java @@ -0,0 +1,276 @@ + +package cn.orionsec.ops.mapping.app; + +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.ops.constant.app.PipelineStatus; +import cn.orionsec.ops.entity.domain.ApplicationPipelineTaskDO; +import cn.orionsec.ops.entity.domain.ApplicationPipelineTaskDetailDO; +import cn.orionsec.ops.entity.domain.ApplicationPipelineTaskLogDO; +import cn.orionsec.ops.entity.dto.ApplicationPipelineTaskStatisticsDTO; +import cn.orionsec.ops.entity.dto.app.ApplicationPipelineStageConfigDTO; +import cn.orionsec.ops.entity.request.app.ApplicationPipelineTaskDetailRequest; +import cn.orionsec.ops.entity.vo.app.*; +import cn.orionsec.ops.utils.Utils; +import com.alibaba.fastjson.JSON; + +import java.util.Date; +import java.util.Optional; + +public class ApplicationPipelineTaskConversion { + + static { + TypeStore.STORE.register(ApplicationPipelineTaskDetailRequest.class, ApplicationPipelineStageConfigDTO.class, p -> { + ApplicationPipelineStageConfigDTO dto = new ApplicationPipelineStageConfigDTO(); + dto.setBranchName(p.getBranchName()); + dto.setCommitId(p.getCommitId()); + dto.setBuildId(p.getBuildId()); + dto.setTitle(p.getTitle()); + dto.setDescription(p.getDescription()); + dto.setMachineIdList(p.getMachineIdList()); + return dto; + }); + } + + static { + TypeStore.STORE.register(ApplicationPipelineTaskDO.class, ApplicationPipelineTaskStatisticsTaskVO.class, p -> { + ApplicationPipelineTaskStatisticsTaskVO vo = new ApplicationPipelineTaskStatisticsTaskVO(); + vo.setId(p.getId()); + vo.setTitle(p.getExecTitle()); + vo.setExecDate(p.getExecStartTime()); + vo.setStatus(p.getExecStatus()); + // 设置构建用时 + if (PipelineStatus.FINISH.getStatus().equals(p.getExecStatus()) + && p.getExecStartTime() != null + && p.getExecEndTime() != null) { + long used = p.getExecEndTime().getTime() - p.getExecStartTime().getTime(); + vo.setUsed(used); + vo.setUsedInterval(Utils.interval(used)); + } + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationPipelineTaskDO.class, ApplicationPipelineTaskVO.class, p -> { + ApplicationPipelineTaskVO vo = new ApplicationPipelineTaskVO(); + vo.setId(p.getId()); + vo.setPipelineId(p.getPipelineId()); + vo.setPipelineName(p.getPipelineName()); + vo.setProfileId(p.getProfileId()); + vo.setProfileName(p.getProfileName()); + vo.setProfileTag(p.getProfileTag()); + vo.setTitle(p.getExecTitle()); + vo.setDescription(p.getExecDescription()); + vo.setStatus(p.getExecStatus()); + vo.setTimedExec(p.getTimedExec()); + vo.setTimedExecTime(p.getTimedExecTime()); + vo.setCreateUserId(p.getCreateUserId()); + vo.setCreateUserName(p.getCreateUserName()); + vo.setAuditUserId(p.getAuditUserId()); + vo.setAuditUserName(p.getAuditUserName()); + vo.setAuditTime(p.getAuditTime()); + Optional.ofNullable(p.getAuditTime()) + .map(Dates::ago) + .ifPresent(vo::setAuditTimeAgo); + vo.setAuditReason(p.getAuditReason()); + vo.setExecUserId(p.getCreateUserId()); + vo.setExecUserName(p.getCreateUserName()); + vo.setCreateTime(p.getCreateTime()); + Optional.ofNullable(p.getCreateTime()) + .map(Dates::ago) + .ifPresent(vo::setCreateTimeAgo); + Date startTime = p.getExecStartTime(); + Date endTime = p.getExecEndTime(); + vo.setExecStartTime(startTime); + vo.setExecEndTime(endTime); + Optional.ofNullable(startTime) + .map(Dates::ago) + .ifPresent(vo::setExecStartTimeAgo); + Optional.ofNullable(endTime) + .map(Dates::ago) + .ifPresent(vo::setExecEndTimeAgo); + if (startTime != null && endTime != null) { + vo.setUsed(endTime.getTime() - startTime.getTime()); + vo.setKeepTime(Utils.interval(vo.getUsed())); + } + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationPipelineTaskDO.class, ApplicationPipelineTaskListVO.class, p -> { + ApplicationPipelineTaskListVO vo = new ApplicationPipelineTaskListVO(); + vo.setId(p.getId()); + vo.setPipelineId(p.getPipelineId()); + vo.setPipelineName(p.getPipelineName()); + vo.setTitle(p.getExecTitle()); + vo.setDescription(p.getExecDescription()); + vo.setStatus(p.getExecStatus()); + vo.setTimedExec(p.getTimedExec()); + vo.setTimedExecTime(p.getTimedExecTime()); + vo.setCreateUserId(p.getCreateUserId()); + vo.setCreateUserName(p.getCreateUserName()); + vo.setExecUserId(p.getExecUserId()); + vo.setExecUserName(p.getExecUserName()); + vo.setCreateTime(p.getCreateTime()); + Date startTime = p.getExecStartTime(); + Date endTime = p.getExecEndTime(); + vo.setExecStartTime(startTime); + vo.setExecEndTime(endTime); + if (startTime != null && endTime != null) { + vo.setUsed(endTime.getTime() - startTime.getTime()); + vo.setKeepTime(Utils.interval(vo.getUsed())); + } + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationPipelineTaskDetailDO.class, ApplicationPipelineTaskDetailVO.class, p -> { + ApplicationPipelineTaskDetailVO vo = new ApplicationPipelineTaskDetailVO(); + vo.setId(p.getId()); + vo.setTaskId(p.getTaskId()); + vo.setAppId(p.getAppId()); + vo.setRelId(p.getRelId()); + vo.setAppName(p.getAppName()); + vo.setAppTag(p.getAppTag()); + vo.setStageType(p.getStageType()); + ApplicationPipelineStageConfigDTO config = JSON.parseObject(p.getStageConfig(), ApplicationPipelineStageConfigDTO.class); + vo.setConfig(Converts.to(config, ApplicationPipelineStageConfigVO.class)); + vo.setStatus(p.getExecStatus()); + Date startTime = p.getExecStartTime(); + Date endTime = p.getExecEndTime(); + vo.setStartTime(startTime); + vo.setEndTime(endTime); + Optional.ofNullable(startTime).map(Dates::ago).ifPresent(vo::setStartTimeAgo); + Optional.ofNullable(endTime).map(Dates::ago).ifPresent(vo::setEndTimeAgo); + if (startTime != null && endTime != null) { + vo.setUsed(endTime.getTime() - startTime.getTime()); + vo.setKeepTime(Utils.interval(vo.getUsed())); + } + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationPipelineTaskDetailDO.class, ApplicationPipelineTaskDetailStatusVO.class, p -> { + ApplicationPipelineTaskDetailStatusVO vo = new ApplicationPipelineTaskDetailStatusVO(); + vo.setId(p.getId()); + vo.setTaskId(p.getTaskId()); + vo.setRelId(p.getRelId()); + vo.setStatus(p.getExecStatus()); + Date startTime = p.getExecStartTime(); + Date endTime = p.getExecEndTime(); + vo.setStartTime(startTime); + vo.setEndTime(endTime); + Optional.ofNullable(startTime).map(Dates::ago).ifPresent(vo::setStartTimeAgo); + Optional.ofNullable(endTime).map(Dates::ago).ifPresent(vo::setEndTimeAgo); + if (startTime != null && endTime != null) { + vo.setUsed(endTime.getTime() - startTime.getTime()); + vo.setKeepTime(Utils.interval(vo.getUsed())); + } + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationPipelineStageConfigDTO.class, ApplicationPipelineStageConfigVO.class, p -> { + ApplicationPipelineStageConfigVO dto = new ApplicationPipelineStageConfigVO(); + dto.setBranchName(p.getBranchName()); + dto.setCommitId(p.getCommitId()); + dto.setBuildId(p.getBuildId()); + dto.setMachineIdList(p.getMachineIdList()); + return dto; + }); + } + + static { + TypeStore.STORE.register(ApplicationPipelineStageConfigDTO.class, ApplicationPipelineTaskDetailRequest.class, p -> { + ApplicationPipelineTaskDetailRequest req = new ApplicationPipelineTaskDetailRequest(); + req.setBranchName(p.getBranchName()); + req.setCommitId(p.getCommitId()); + req.setBuildId(p.getBuildId()); + req.setTitle(p.getTitle()); + req.setDescription(p.getDescription()); + req.setMachineIdList(p.getMachineIdList()); + return req; + }); + } + + static { + TypeStore.STORE.register(ApplicationPipelineTaskStatisticsDTO.class, ApplicationPipelineTaskStatisticsChartVO.class, p -> { + ApplicationPipelineTaskStatisticsChartVO vo = new ApplicationPipelineTaskStatisticsChartVO(); + vo.setDate(Dates.format(p.getDate(), Dates.YMD)); + vo.setExecCount(p.getExecCount()); + vo.setSuccessCount(p.getSuccessCount()); + vo.setFailureCount(p.getFailureCount()); + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationPipelineTaskStatisticsDTO.class, ApplicationPipelineTaskStatisticsMetricsVO.class, p -> { + ApplicationPipelineTaskStatisticsMetricsVO vo = new ApplicationPipelineTaskStatisticsMetricsVO(); + vo.setExecCount(p.getExecCount()); + vo.setSuccessCount(p.getSuccessCount()); + vo.setFailureCount(p.getFailureCount()); + vo.setAvgUsed(p.getAvgUsed()); + vo.setAvgUsedInterval(Utils.interval(p.getAvgUsed())); + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationPipelineTaskDO.class, ApplicationPipelineTaskStatusVO.class, p -> { + ApplicationPipelineTaskStatusVO vo = new ApplicationPipelineTaskStatusVO(); + vo.setId(p.getId()); + vo.setStatus(p.getExecStatus()); + Date startTime = p.getExecStartTime(), endTime = p.getExecEndTime(); + vo.setStartTime(startTime); + vo.setStartTimeAgo(Optional.ofNullable(startTime).map(Dates::ago).orElse(null)); + vo.setEndTime(endTime); + vo.setEndTimeAgo(Optional.ofNullable(endTime).map(Dates::ago).orElse(null)); + if (startTime != null && endTime != null) { + vo.setUsed(endTime.getTime() - startTime.getTime()); + vo.setKeepTime(Utils.interval(vo.getUsed())); + } + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationPipelineTaskDetailDO.class, ApplicationPipelineTaskStatisticsDetailVO.class, p -> { + ApplicationPipelineTaskStatisticsDetailVO vo = new ApplicationPipelineTaskStatisticsDetailVO(); + vo.setId(p.getId()); + vo.setRelId(p.getRelId()); + vo.setStageType(p.getStageType()); + vo.setStatus(p.getExecStatus()); + Date startTime = p.getExecStartTime(); + Date endTime = p.getExecEndTime(); + if (startTime != null && endTime != null) { + long used = endTime.getTime() - startTime.getTime(); + vo.setUsed(used); + vo.setUsedInterval(Utils.interval(used)); + } + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationPipelineTaskLogDO.class, ApplicationPipelineTaskLogVO.class, p -> { + ApplicationPipelineTaskLogVO vo = new ApplicationPipelineTaskLogVO(); + vo.setId(p.getId()); + vo.setTaskId(p.getTaskId()); + vo.setTaskDetailId(p.getTaskDetailId()); + vo.setStatus(p.getLogStatus()); + vo.setType(p.getStageType()); + vo.setLog(p.getLogInfo()); + vo.setCreateTime(p.getCreateTime()); + vo.setCreateTimeAgo(Dates.ago(p.getCreateTime())); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationProfileConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationProfileConversion.java new file mode 100644 index 0000000..4253a64 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationProfileConversion.java @@ -0,0 +1,44 @@ + +package cn.orionsec.ops.mapping.app; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.ops.entity.domain.ApplicationProfileDO; +import cn.orionsec.ops.entity.dto.app.ApplicationProfileDTO; +import cn.orionsec.ops.entity.vo.app.ApplicationProfileFastVO; +import cn.orionsec.ops.entity.vo.app.ApplicationProfileVO; + +public class ApplicationProfileConversion { + + static { + TypeStore.STORE.register(ApplicationProfileDO.class, ApplicationProfileVO.class, p -> { + ApplicationProfileVO vo = new ApplicationProfileVO(); + vo.setId(p.getId()); + vo.setName(p.getProfileName()); + vo.setTag(p.getProfileTag()); + vo.setDescription(p.getDescription()); + vo.setReleaseAudit(p.getReleaseAudit()); + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationProfileDO.class, ApplicationProfileDTO.class, p -> { + ApplicationProfileDTO dto = new ApplicationProfileDTO(); + dto.setId(p.getId()); + dto.setProfileName(p.getProfileName()); + dto.setProfileTag(p.getProfileTag()); + return dto; + }); + } + + static { + TypeStore.STORE.register(ApplicationProfileDTO.class, ApplicationProfileFastVO.class, p -> { + ApplicationProfileFastVO vo = new ApplicationProfileFastVO(); + vo.setId(p.getId()); + vo.setName(p.getProfileName()); + vo.setTag(p.getProfileTag()); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationReleaseConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationReleaseConversion.java new file mode 100644 index 0000000..9eed83a --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationReleaseConversion.java @@ -0,0 +1,224 @@ + +package cn.orionsec.ops.mapping.app; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.ops.constant.app.ReleaseStatus; +import cn.orionsec.ops.entity.domain.ApplicationReleaseDO; +import cn.orionsec.ops.entity.domain.ApplicationReleaseMachineDO; +import cn.orionsec.ops.entity.dto.ApplicationReleaseStatisticsDTO; +import cn.orionsec.ops.entity.vo.app.*; +import cn.orionsec.ops.utils.Utils; + +import java.util.Date; +import java.util.Optional; + +public class ApplicationReleaseConversion { + + static { + TypeStore.STORE.register(ApplicationReleaseDO.class, ApplicationReleaseDetailVO.class, p -> { + ApplicationReleaseDetailVO vo = new ApplicationReleaseDetailVO(); + vo.setId(p.getId()); + vo.setTitle(p.getReleaseTitle()); + vo.setDescription(p.getReleaseDescription()); + vo.setBuildId(p.getBuildId()); + vo.setBuildSeq(p.getBuildSeq()); + vo.setAppId(p.getAppId()); + vo.setAppName(p.getAppName()); + vo.setAppTag(p.getAppTag()); + vo.setProfileId(p.getProfileId()); + vo.setProfileName(p.getProfileName()); + vo.setProfileTag(p.getProfileTag()); + vo.setType(p.getReleaseType()); + vo.setStatus(p.getReleaseStatus()); + vo.setSerializer(p.getReleaseSerialize()); + vo.setExceptionHandler(p.getExceptionHandler()); + vo.setTimedRelease(p.getTimedRelease()); + vo.setTimedReleaseTime(p.getTimedReleaseTime()); + vo.setCreateUserId(p.getCreateUserId()); + vo.setCreateUserName(p.getCreateUserName()); + vo.setAuditUserId(p.getAuditUserId()); + vo.setAuditUserName(p.getAuditUserName()); + vo.setAuditTime(p.getAuditTime()); + vo.setAuditReason(p.getAuditReason()); + vo.setStartTime(p.getReleaseStartTime()); + vo.setEndTime(p.getReleaseEndTime()); + vo.setReleaseUserName(p.getReleaseUserName()); + vo.setCreateTime(p.getCreateTime()); + vo.setCreateTimeAgo(Dates.ago(p.getCreateTime())); + Date startTime = p.getReleaseStartTime(); + Date endTime = p.getReleaseEndTime(); + if (startTime != null && endTime != null) { + vo.setUsed(endTime.getTime() - startTime.getTime()); + vo.setKeepTime(Utils.interval(vo.getUsed())); + } + Optional.ofNullable(p.getTimedReleaseTime()) + .map(Dates::ago) + .ifPresent(vo::setTimedReleaseTimeAgo); + Optional.ofNullable(p.getAuditTime()) + .map(Dates::ago) + .ifPresent(vo::setAuditTimeAgo); + Optional.ofNullable(startTime) + .map(Dates::ago) + .ifPresent(vo::setStartTimeAgo); + Optional.ofNullable(endTime) + .map(Dates::ago) + .ifPresent(vo::setEndTimeAgo); + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationReleaseDO.class, ApplicationReleaseListVO.class, p -> { + ApplicationReleaseListVO vo = new ApplicationReleaseListVO(); + vo.setId(p.getId()); + vo.setTitle(p.getReleaseTitle()); + vo.setDescription(p.getReleaseDescription()); + vo.setBuildId(p.getBuildId()); + vo.setBuildSeq(p.getBuildSeq()); + vo.setAppId(p.getAppId()); + vo.setAppName(p.getAppName()); + vo.setAppTag(p.getAppTag()); + vo.setType(p.getReleaseType()); + vo.setStatus(p.getReleaseStatus()); + vo.setSerializer(p.getReleaseSerialize()); + vo.setExceptionHandler(p.getExceptionHandler()); + vo.setTimedRelease(p.getTimedRelease()); + vo.setTimedReleaseTime(p.getTimedReleaseTime()); + vo.setCreateUserName(p.getCreateUserName()); + vo.setCreateTime(p.getCreateTime()); + vo.setAuditUserName(p.getAuditUserName()); + vo.setAuditReason(p.getAuditReason()); + vo.setAuditTime(p.getAuditTime()); + Date startTime = p.getReleaseStartTime(); + vo.setReleaseUserName(p.getReleaseUserName()); + vo.setReleaseTime(startTime); + Date endTime = p.getReleaseEndTime(); + if (startTime != null && endTime != null) { + vo.setUsed(endTime.getTime() - startTime.getTime()); + vo.setKeepTime(Utils.interval(vo.getUsed())); + } + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationReleaseMachineDO.class, ApplicationReleaseMachineVO.class, p -> { + ApplicationReleaseMachineVO vo = new ApplicationReleaseMachineVO(); + vo.setId(p.getId()); + vo.setReleaseId(p.getReleaseId()); + vo.setMachineId(p.getMachineId()); + vo.setMachineName(p.getMachineName()); + vo.setMachineTag(p.getMachineTag()); + vo.setMachineHost(p.getMachineHost()); + vo.setStatus(p.getRunStatus()); + Date startTime = p.getStartTime(); + Date endTime = p.getEndTime(); + vo.setStartTime(startTime); + vo.setEndTime(endTime); + vo.setStartTimeAgo(Optional.ofNullable(startTime).map(Dates::ago).orElse(null)); + vo.setEndTimeAgo(Optional.ofNullable(endTime).map(Dates::ago).orElse(null)); + if (startTime != null && endTime != null) { + vo.setUsed(endTime.getTime() - startTime.getTime()); + vo.setKeepTime(Utils.interval(vo.getUsed())); + } + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationReleaseMachineDO.class, ApplicationReleaseMachineStatusVO.class, p -> { + ApplicationReleaseMachineStatusVO vo = new ApplicationReleaseMachineStatusVO(); + vo.setId(p.getId()); + vo.setStatus(p.getRunStatus()); + Date startTime = p.getStartTime(), endTime = p.getEndTime(); + vo.setStartTime(startTime); + vo.setStartTimeAgo(Optional.ofNullable(startTime).map(Dates::ago).orElse(null)); + vo.setEndTime(endTime); + vo.setEndTimeAgo(Optional.ofNullable(endTime).map(Dates::ago).orElse(null)); + if (startTime != null && endTime != null) { + vo.setUsed(endTime.getTime() - startTime.getTime()); + vo.setKeepTime(Utils.interval(vo.getUsed())); + } + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationReleaseStatisticsDTO.class, ApplicationReleaseStatisticsChartVO.class, p -> { + ApplicationReleaseStatisticsChartVO vo = new ApplicationReleaseStatisticsChartVO(); + vo.setDate(Dates.format(p.getDate(), Dates.YMD)); + vo.setReleaseCount(p.getReleaseCount()); + vo.setSuccessCount(p.getSuccessCount()); + vo.setFailureCount(p.getFailureCount()); + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationReleaseMachineDO.class, ApplicationReleaseStatisticsMachineVO.class, p -> { + ApplicationReleaseStatisticsMachineVO vo = new ApplicationReleaseStatisticsMachineVO(); + vo.setId(p.getId()); + vo.setMachineId(p.getMachineId()); + vo.setMachineName(p.getMachineName()); + vo.setStatus(p.getRunStatus()); + // 设置构建用时 + if (p.getStartTime() != null && p.getEndTime() != null) { + long used = p.getEndTime().getTime() - p.getStartTime().getTime(); + vo.setUsed(used); + vo.setUsedInterval(Utils.interval(used)); + } + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationReleaseStatisticsDTO.class, ApplicationReleaseStatisticsMetricsVO.class, p -> { + ApplicationReleaseStatisticsMetricsVO vo = new ApplicationReleaseStatisticsMetricsVO(); + vo.setReleaseCount(p.getReleaseCount()); + vo.setSuccessCount(p.getSuccessCount()); + vo.setFailureCount(p.getFailureCount()); + vo.setAvgUsed(p.getAvgUsed()); + vo.setAvgUsedInterval(Utils.interval(p.getAvgUsed())); + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationReleaseDO.class, ApplicationReleaseStatisticsRecordVO.class, p -> { + ApplicationReleaseStatisticsRecordVO vo = new ApplicationReleaseStatisticsRecordVO(); + vo.setReleaseId(p.getId()); + vo.setReleaseTitle(p.getReleaseTitle()); + vo.setReleaseDate(p.getReleaseStartTime()); + vo.setStatus(p.getReleaseStatus()); + // 设置构建用时 + if (ReleaseStatus.FINISH.getStatus().equals(p.getReleaseStatus()) + && p.getReleaseStartTime() != null + && p.getReleaseEndTime() != null) { + long used = p.getReleaseEndTime().getTime() - p.getReleaseStartTime().getTime(); + vo.setUsed(used); + vo.setUsedInterval(Utils.interval(used)); + } + return vo; + }); + } + + static { + TypeStore.STORE.register(ApplicationReleaseDO.class, ApplicationReleaseStatusVO.class, p -> { + ApplicationReleaseStatusVO vo = new ApplicationReleaseStatusVO(); + vo.setId(p.getId()); + vo.setStatus(p.getReleaseStatus()); + Date startTime = p.getReleaseStartTime(), endTime = p.getReleaseEndTime(); + vo.setStartTime(startTime); + vo.setStartTimeAgo(Optional.ofNullable(startTime).map(Dates::ago).orElse(null)); + vo.setEndTime(endTime); + vo.setEndTimeAgo(Optional.ofNullable(endTime).map(Dates::ago).orElse(null)); + if (startTime != null && endTime != null) { + vo.setUsed(endTime.getTime() - startTime.getTime()); + vo.setKeepTime(Utils.interval(vo.getUsed())); + } + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationRepositoryConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationRepositoryConversion.java new file mode 100644 index 0000000..2f047a9 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/app/ApplicationRepositoryConversion.java @@ -0,0 +1,57 @@ + +package cn.orionsec.ops.mapping.app; + +import cn.orionsec.kit.ext.vcs.git.info.BranchInfo; +import cn.orionsec.kit.ext.vcs.git.info.LogInfo; +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.ops.entity.domain.ApplicationRepositoryDO; +import cn.orionsec.ops.entity.vo.app.ApplicationRepositoryBranchVO; +import cn.orionsec.ops.entity.vo.app.ApplicationRepositoryCommitVO; +import cn.orionsec.ops.entity.vo.app.ApplicationRepositoryVO; + +import java.util.Date; +import java.util.Optional; + +public class ApplicationRepositoryConversion { + + static { + TypeStore.STORE.register(ApplicationRepositoryDO.class, ApplicationRepositoryVO.class, p -> { + ApplicationRepositoryVO vo = new ApplicationRepositoryVO(); + vo.setId(p.getId()); + vo.setName(p.getRepoName()); + vo.setDescription(p.getRepoDescription()); + vo.setType(p.getRepoType()); + vo.setUrl(p.getRepoUrl()); + vo.setUsername(p.getRepoUsername()); + vo.setStatus(p.getRepoStatus()); + vo.setAuthType(p.getRepoAuthType()); + vo.setTokenType(p.getRepoTokenType()); + vo.setCreateTime(p.getCreateTime()); + vo.setUpdateTime(p.getUpdateTime()); + return vo; + }); + } + + static { + TypeStore.STORE.register(BranchInfo.class, ApplicationRepositoryBranchVO.class, p -> { + ApplicationRepositoryBranchVO vo = new ApplicationRepositoryBranchVO(); + vo.setName(p.toString()); + return vo; + }); + } + + static { + TypeStore.STORE.register(LogInfo.class, ApplicationRepositoryCommitVO.class, p -> { + ApplicationRepositoryCommitVO vo = new ApplicationRepositoryCommitVO(); + vo.setId(p.getId()); + vo.setMessage(p.getMessage()); + vo.setName(p.getName()); + Date time = p.getTime(); + vo.setTime(time); + Optional.ofNullable(time).map(Dates::ago).ifPresent(vo::setTimeAgo); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/exec/CommandExecConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/exec/CommandExecConversion.java new file mode 100644 index 0000000..8825058 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/exec/CommandExecConversion.java @@ -0,0 +1,62 @@ + +package cn.orionsec.ops.mapping.exec; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.ops.entity.domain.CommandExecDO; +import cn.orionsec.ops.entity.vo.exec.CommandExecStatusVO; +import cn.orionsec.ops.entity.vo.exec.CommandExecVO; +import cn.orionsec.ops.utils.Utils; + +import java.util.Date; +import java.util.Optional; + +public class CommandExecConversion { + + static { + TypeStore.STORE.register(CommandExecDO.class, CommandExecStatusVO.class, p -> { + CommandExecStatusVO vo = new CommandExecStatusVO(); + vo.setId(p.getId()); + vo.setExitCode(p.getExitCode()); + vo.setStatus(p.getExecStatus()); + if (p.getStartDate() != null && p.getEndDate() != null) { + vo.setUsed(p.getEndDate().getTime() - p.getStartDate().getTime()); + vo.setKeepTime(Utils.interval(vo.getUsed())); + } + return vo; + }); + } + + static { + TypeStore.STORE.register(CommandExecDO.class, CommandExecVO.class, p -> { + CommandExecVO vo = new CommandExecVO(); + Date startDate = p.getStartDate(); + Date endDate = p.getEndDate(); + Date createTime = p.getCreateTime(); + vo.setId(p.getId()); + vo.setUserId(p.getUserId()); + vo.setUsername(p.getUserName()); + vo.setType(p.getExecType()); + vo.setStatus(p.getExecStatus()); + vo.setMachineId(p.getMachineId()); + vo.setMachineName(p.getMachineName()); + vo.setMachineHost(p.getMachineHost()); + vo.setMachineTag(p.getMachineTag()); + vo.setExitCode(p.getExitCode()); + vo.setCommand(p.getExecCommand()); + vo.setDescription(p.getDescription()); + vo.setStartDate(startDate); + vo.setEndDate(endDate); + vo.setCreateTime(createTime); + Optional.ofNullable(startDate).map(Dates::ago).ifPresent(vo::setStartDateAgo); + Optional.ofNullable(endDate).map(Dates::ago).ifPresent(vo::setEndDateAgo); + Optional.ofNullable(createTime).map(Dates::ago).ifPresent(vo::setCreateTimeAgo); + if (startDate != null && endDate != null) { + vo.setUsed(endDate.getTime() - startDate.getTime()); + vo.setKeepTime(Utils.interval(vo.getUsed())); + } + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/file/FileTailConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/file/FileTailConversion.java new file mode 100644 index 0000000..f49f316 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/file/FileTailConversion.java @@ -0,0 +1,42 @@ + +package cn.orionsec.ops.mapping.file; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.ops.entity.domain.FileTailListDO; +import cn.orionsec.ops.entity.dto.file.FileTailDTO; +import cn.orionsec.ops.entity.vo.tail.FileTailVO; + +public class FileTailConversion { + + static { + TypeStore.STORE.register(FileTailVO.class, FileTailDTO.class, p -> { + FileTailDTO dto = new FileTailDTO(); + dto.setMachineId(p.getMachineId()); + dto.setFilePath(p.getPath()); + dto.setOffset(p.getOffset()); + dto.setCharset(p.getCharset()); + dto.setCommand(p.getCommand()); + return dto; + }); + } + + static { + TypeStore.STORE.register(FileTailListDO.class, FileTailVO.class, p -> { + FileTailVO vo = new FileTailVO(); + vo.setId(p.getId()); + vo.setName(p.getAliasName()); + vo.setMachineId(p.getMachineId()); + vo.setPath(p.getFilePath()); + vo.setFileName(Files1.getFileName(p.getFilePath())); + vo.setOffset(p.getFileOffset()); + vo.setCharset(p.getFileCharset()); + vo.setCommand(p.getTailCommand()); + vo.setTailMode(p.getTailMode()); + vo.setUpdateTime(p.getUpdateTime()); + vo.setUpdateTimeAgo(Dates.ago(p.getUpdateTime())); + return vo; + }); + } +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/file/FileTransferLogConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/file/FileTransferLogConversion.java new file mode 100644 index 0000000..2c12a25 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/file/FileTransferLogConversion.java @@ -0,0 +1,27 @@ + +package cn.orionsec.ops.mapping.file; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.ops.entity.domain.FileTransferLogDO; +import cn.orionsec.ops.entity.vo.sftp.FileTransferLogVO; + +public class FileTransferLogConversion { + + static { + TypeStore.STORE.register(FileTransferLogDO.class, FileTransferLogVO.class, p -> { + FileTransferLogVO vo = new FileTransferLogVO(); + vo.setId(p.getId()); + vo.setMachineId(p.getMachineId()); + vo.setFileToken(p.getFileToken()); + vo.setType(p.getTransferType()); + vo.setRemoteFile(p.getRemoteFile()); + vo.setCurrent(Files1.getSize(p.getCurrentSize())); + vo.setSize(Files1.getSize(p.getFileSize())); + vo.setProgress(p.getNowProgress()); + vo.setStatus(p.getTransferStatus()); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/history/HistoryValueConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/history/HistoryValueConversion.java new file mode 100644 index 0000000..3fd87f4 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/history/HistoryValueConversion.java @@ -0,0 +1,26 @@ + +package cn.orionsec.ops.mapping.history; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.ops.entity.domain.HistoryValueSnapshotDO; +import cn.orionsec.ops.entity.vo.history.HistoryValueVO; + +public class HistoryValueConversion { + + static { + TypeStore.STORE.register(HistoryValueSnapshotDO.class, HistoryValueVO.class, p -> { + HistoryValueVO vo = new HistoryValueVO(); + vo.setId(p.getId()); + vo.setType(p.getOperatorType()); + vo.setBeforeValue(p.getBeforeValue()); + vo.setAfterValue(p.getAfterValue()); + vo.setUpdateUserId(p.getUpdateUserId()); + vo.setUpdateUserName(p.getUpdateUserName()); + vo.setCreateTime(p.getCreateTime()); + vo.setCreateTimeAgo(Dates.ago(p.getCreateTime())); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/home/StatisticsConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/home/StatisticsConversion.java new file mode 100644 index 0000000..ae24b3d --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/home/StatisticsConversion.java @@ -0,0 +1,21 @@ + +package cn.orionsec.ops.mapping.home; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.ops.entity.dto.statistic.StatisticsCountDTO; +import cn.orionsec.ops.entity.vo.home.HomeStatisticsCountVO; + +public class StatisticsConversion { + + static { + TypeStore.STORE.register(StatisticsCountDTO.class, HomeStatisticsCountVO.class, p -> { + HomeStatisticsCountVO vo = new HomeStatisticsCountVO(); + vo.setMachineCount(p.getMachineCount()); + vo.setProfileCount(p.getProfileCount()); + vo.setAppCount(p.getAppCount()); + vo.setPipelineCount(p.getPipelineCount()); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineAlarmConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineAlarmConversion.java new file mode 100644 index 0000000..959c453 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineAlarmConversion.java @@ -0,0 +1,36 @@ + +package cn.orionsec.ops.mapping.machine; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.ops.entity.domain.MachineAlarmConfigDO; +import cn.orionsec.ops.entity.domain.MachineAlarmHistoryDO; +import cn.orionsec.ops.entity.vo.machine.MachineAlarmConfigVO; +import cn.orionsec.ops.entity.vo.machine.MachineAlarmHistoryVO; + +public class MachineAlarmConversion { + + static { + TypeStore.STORE.register(MachineAlarmConfigDO.class, MachineAlarmConfigVO.class, p -> { + MachineAlarmConfigVO vo = new MachineAlarmConfigVO(); + vo.setType(p.getAlarmType()); + vo.setAlarmThreshold(p.getAlarmThreshold()); + vo.setTriggerThreshold(p.getTriggerThreshold()); + vo.setNotifySilence(p.getNotifySilence()); + return vo; + }); + } + + static { + TypeStore.STORE.register(MachineAlarmHistoryDO.class, MachineAlarmHistoryVO.class, p -> { + MachineAlarmHistoryVO vo = new MachineAlarmHistoryVO(); + vo.setId(p.getId()); + vo.setType(p.getAlarmType()); + vo.setAlarmValue(p.getAlarmValue()); + vo.setAlarmTime(p.getAlarmTime()); + vo.setAlarmTimeAgo(Dates.ago(p.getAlarmTime())); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineConversion.java new file mode 100644 index 0000000..e17d102 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineConversion.java @@ -0,0 +1,49 @@ + +package cn.orionsec.ops.mapping.machine; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.request.machine.MachineInfoRequest; +import cn.orionsec.ops.entity.vo.machine.MachineInfoVO; + +public class MachineConversion { + + static { + TypeStore.STORE.register(MachineInfoDO.class, MachineInfoVO.class, p -> { + MachineInfoVO vo = new MachineInfoVO(); + vo.setId(p.getId()); + vo.setProxyId(p.getProxyId()); + vo.setKeyId(p.getKeyId()); + vo.setHost(p.getMachineHost()); + vo.setSshPort(p.getSshPort()); + vo.setName(p.getMachineName()); + vo.setTag(p.getMachineTag()); + vo.setDescription(p.getDescription()); + vo.setUsername(p.getUsername()); + vo.setAuthType(p.getAuthType()); + vo.setStatus(p.getMachineStatus()); + vo.setCreateTime(p.getCreateTime()); + vo.setUpdateTime(p.getUpdateTime()); + return vo; + }); + } + + static { + TypeStore.STORE.register(MachineInfoRequest.class, MachineInfoDO.class, p -> { + MachineInfoDO d = new MachineInfoDO(); + d.setId(p.getId()); + d.setProxyId(p.getProxyId()); + d.setKeyId(p.getKeyId()); + d.setMachineHost(p.getHost()); + d.setSshPort(p.getSshPort()); + d.setMachineName(p.getName()); + d.setMachineTag(p.getTag()); + d.setDescription(p.getDescription()); + d.setUsername(p.getUsername()); + d.setAuthType(p.getAuthType()); + d.setMachineStatus(p.getStatus()); + return d; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineEnvConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineEnvConversion.java new file mode 100644 index 0000000..dc55c28 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineEnvConversion.java @@ -0,0 +1,28 @@ + +package cn.orionsec.ops.mapping.machine; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.machine.MachineEnvAttr; +import cn.orionsec.ops.entity.domain.MachineEnvDO; +import cn.orionsec.ops.entity.vo.machine.MachineEnvVO; + +public class MachineEnvConversion { + + static { + TypeStore.STORE.register(MachineEnvDO.class, MachineEnvVO.class, p -> { + MachineEnvVO vo = new MachineEnvVO(); + vo.setId(p.getId()); + vo.setMachineId(p.getMachineId()); + vo.setKey(p.getAttrKey()); + vo.setValue(p.getAttrValue()); + vo.setDescription(p.getDescription()); + vo.setCreateTime(p.getCreateTime()); + vo.setUpdateTime(p.getUpdateTime()); + Integer forbidDelete = MachineEnvAttr.of(p.getAttrKey()) == null ? Const.FORBID_DELETE_CAN : Const.FORBID_DELETE_NOT; + vo.setForbidDelete(forbidDelete); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineGroupConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineGroupConversion.java new file mode 100644 index 0000000..ba5d2a6 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineGroupConversion.java @@ -0,0 +1,21 @@ + +package cn.orionsec.ops.mapping.machine; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.ops.entity.domain.MachineGroupDO; +import cn.orionsec.ops.entity.vo.machine.MachineGroupTreeVO; + +public class MachineGroupConversion { + + static { + TypeStore.STORE.register(MachineGroupDO.class, MachineGroupTreeVO.class, p -> { + MachineGroupTreeVO vo = new MachineGroupTreeVO(); + vo.setId(p.getId()); + vo.setParentId(p.getParentId()); + vo.setTitle(p.getGroupName()); + vo.setSort(p.getSort()); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineMonitorConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineMonitorConversion.java new file mode 100644 index 0000000..560bc4d --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineMonitorConversion.java @@ -0,0 +1,42 @@ + +package cn.orionsec.ops.mapping.machine; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.ops.constant.monitor.MonitorConst; +import cn.orionsec.ops.entity.domain.MachineMonitorDO; +import cn.orionsec.ops.entity.dto.MachineMonitorDTO; +import cn.orionsec.ops.entity.vo.machine.MachineMonitorVO; + +public class MachineMonitorConversion { + + static { + TypeStore.STORE.register(MachineMonitorDO.class, MachineMonitorVO.class, p -> { + MachineMonitorVO vo = new MachineMonitorVO(); + vo.setId(p.getId()); + vo.setMachineId(p.getMachineId()); + vo.setStatus(p.getMonitorStatus()); + vo.setUrl(p.getMonitorUrl()); + vo.setAccessToken(p.getAccessToken()); + vo.setCurrentVersion(p.getAgentVersion()); + vo.setLatestVersion(MonitorConst.LATEST_VERSION); + return vo; + }); + } + + static { + TypeStore.STORE.register(MachineMonitorDTO.class, MachineMonitorVO.class, p -> { + MachineMonitorVO vo = new MachineMonitorVO(); + vo.setId(p.getId()); + vo.setMachineId(p.getMachineId()); + vo.setMachineName(p.getMachineName()); + vo.setMachineHost(p.getMachineHost()); + vo.setStatus(p.getMonitorStatus()); + vo.setUrl(p.getMonitorUrl()); + vo.setAccessToken(p.getAccessToken()); + vo.setCurrentVersion(p.getAgentVersion()); + vo.setLatestVersion(MonitorConst.LATEST_VERSION); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineProxyConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineProxyConversion.java new file mode 100644 index 0000000..da11656 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineProxyConversion.java @@ -0,0 +1,24 @@ + +package cn.orionsec.ops.mapping.machine; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.ops.entity.domain.MachineProxyDO; +import cn.orionsec.ops.entity.vo.machine.MachineProxyVO; + +public class MachineProxyConversion { + + static { + TypeStore.STORE.register(MachineProxyDO.class, MachineProxyVO.class, p -> { + MachineProxyVO vo = new MachineProxyVO(); + vo.setId(p.getId()); + vo.setHost(p.getProxyHost()); + vo.setPort(p.getProxyPort()); + vo.setUsername(p.getProxyUsername()); + vo.setType(p.getProxyType()); + vo.setDescription(p.getDescription()); + vo.setCreateTime(p.getCreateTime()); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineSecretKeyConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineSecretKeyConversion.java new file mode 100644 index 0000000..35124d6 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineSecretKeyConversion.java @@ -0,0 +1,22 @@ + +package cn.orionsec.ops.mapping.machine; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.ops.entity.domain.MachineSecretKeyDO; +import cn.orionsec.ops.entity.vo.machine.MachineSecretKeyVO; + +public class MachineSecretKeyConversion { + + static { + TypeStore.STORE.register(MachineSecretKeyDO.class, MachineSecretKeyVO.class, p -> { + MachineSecretKeyVO vo = new MachineSecretKeyVO(); + vo.setId(p.getId()); + vo.setName(p.getKeyName()); + vo.setPath(p.getSecretKeyPath()); + vo.setDescription(p.getDescription()); + vo.setCreateTime(p.getCreateTime()); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineTerminalConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineTerminalConversion.java new file mode 100644 index 0000000..39bd49d --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/machine/MachineTerminalConversion.java @@ -0,0 +1,77 @@ + +package cn.orionsec.ops.mapping.machine; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.ops.entity.config.TerminalConnectConfig; +import cn.orionsec.ops.entity.domain.MachineTerminalDO; +import cn.orionsec.ops.entity.domain.MachineTerminalLogDO; +import cn.orionsec.ops.entity.vo.machine.MachineTerminalLogVO; +import cn.orionsec.ops.entity.vo.machine.MachineTerminalManagerVO; +import cn.orionsec.ops.entity.vo.machine.MachineTerminalVO; + +import java.util.Optional; + +public class MachineTerminalConversion { + + static { + TypeStore.STORE.register(MachineTerminalDO.class, MachineTerminalVO.class, p -> { + MachineTerminalVO vo = new MachineTerminalVO(); + vo.setId(p.getId()); + vo.setMachineId(p.getMachineId()); + vo.setTerminalType(p.getTerminalType()); + vo.setBackgroundColor(p.getBackgroundColor()); + vo.setFontColor(p.getFontColor()); + vo.setFontSize(p.getFontSize()); + vo.setFontFamily(p.getFontFamily()); + vo.setEnableWebLink(p.getEnableWebLink()); + vo.setCreateTime(p.getCreateTime()); + vo.setUpdateTime(p.getUpdateTime()); + return vo; + }); + } + + static { + TypeStore.STORE.register(TerminalConnectConfig.class, MachineTerminalManagerVO.class, p -> { + MachineTerminalManagerVO vo = new MachineTerminalManagerVO(); + vo.setUserId(p.getUserId()); + vo.setUserName(p.getUsername()); + vo.setMachineId(p.getMachineId()); + vo.setMachineName(p.getMachineName()); + vo.setMachineHost(p.getMachineHost()); + vo.setMachineTag(p.getMachineTag()); + vo.setLogId(p.getLogId()); + vo.setConnectedTime(p.getConnectedTime()); + Optional.ofNullable(p.getConnectedTime()) + .map(Dates::ago) + .ifPresent(vo::setConnectedTimeAgo); + return vo; + }); + } + + static { + TypeStore.STORE.register(MachineTerminalLogDO.class, MachineTerminalLogVO.class, p -> { + MachineTerminalLogVO vo = new MachineTerminalLogVO(); + vo.setId(p.getId()); + vo.setUserId(p.getUserId()); + vo.setUsername(p.getUsername()); + vo.setMachineId(p.getMachineId()); + vo.setMachineName(p.getMachineName()); + vo.setMachineTag(p.getMachineTag()); + vo.setMachineHost(p.getMachineHost()); + vo.setAccessToken(p.getAccessToken()); + vo.setConnectedTime(p.getConnectedTime()); + vo.setDisconnectedTime(p.getDisconnectedTime()); + Optional.ofNullable(p.getConnectedTime()) + .map(Dates::ago) + .ifPresent(vo::setConnectedTimeAgo); + Optional.ofNullable(p.getDisconnectedTime()) + .map(Dates::ago) + .ifPresent(vo::setDisconnectedTimeAgo); + vo.setCloseCode(p.getCloseCode()); + vo.setCreateTime(p.getCreateTime()); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/message/WebSideMessageConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/message/WebSideMessageConversion.java new file mode 100644 index 0000000..320544a --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/message/WebSideMessageConversion.java @@ -0,0 +1,28 @@ + +package cn.orionsec.ops.mapping.message; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.ops.entity.domain.WebSideMessageDO; +import cn.orionsec.ops.entity.vo.message.WebSideMessageVO; + +public class WebSideMessageConversion { + + static { + TypeStore.STORE.register(WebSideMessageDO.class, WebSideMessageVO.class, p -> { + WebSideMessageVO vo = new WebSideMessageVO(); + vo.setId(p.getId()); + vo.setClassify(p.getMessageClassify()); + vo.setType(p.getMessageType()); + vo.setStatus(p.getReadStatus()); + vo.setToUserId(p.getToUserId()); + vo.setToUserName(p.getToUserName()); + vo.setMessage(p.getSendMessage()); + vo.setRelId(p.getRelId()); + vo.setCreateTime(p.getCreateTime()); + vo.setCreateTimeAgo(Dates.ago(p.getCreateTime())); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/scheduler/SchedulerTaskConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/scheduler/SchedulerTaskConversion.java new file mode 100644 index 0000000..6145844 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/scheduler/SchedulerTaskConversion.java @@ -0,0 +1,35 @@ + +package cn.orionsec.ops.mapping.scheduler; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.lang.utils.time.cron.Cron; +import cn.orionsec.kit.lang.utils.time.cron.CronSupport; +import cn.orionsec.ops.entity.domain.SchedulerTaskDO; +import cn.orionsec.ops.entity.vo.scheduler.SchedulerTaskVO; + +public class SchedulerTaskConversion { + + static { + TypeStore.STORE.register(SchedulerTaskDO.class, SchedulerTaskVO.class, p -> { + SchedulerTaskVO vo = new SchedulerTaskVO(); + vo.setId(p.getId()); + vo.setName(p.getTaskName()); + vo.setDescription(p.getDescription()); + vo.setCommand(p.getTaskCommand()); + vo.setExpression(p.getExpression()); + vo.setEnableStatus(p.getEnableStatus()); + vo.setLatelyStatus(p.getLatelyStatus()); + vo.setSerializeType(p.getSerializeType()); + vo.setExceptionHandler(p.getExceptionHandler()); + vo.setLatelyScheduleTime(p.getLatelyScheduleTime()); + vo.setUpdateTime(p.getUpdateTime()); + try { + vo.setNextTime(CronSupport.getNextTime(new Cron(p.getExpression()), 5)); + } catch (Exception e) { + e.printStackTrace(); + } + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/scheduler/SchedulerTaskRecordConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/scheduler/SchedulerTaskRecordConversion.java new file mode 100644 index 0000000..7ed4959 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/scheduler/SchedulerTaskRecordConversion.java @@ -0,0 +1,157 @@ + +package cn.orionsec.ops.mapping.scheduler; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.ops.entity.domain.SchedulerTaskMachineRecordDO; +import cn.orionsec.ops.entity.domain.SchedulerTaskRecordDO; +import cn.orionsec.ops.entity.dto.SchedulerTaskRecordStatisticsDTO; +import cn.orionsec.ops.entity.vo.scheduler.*; +import cn.orionsec.ops.utils.Utils; + +import java.util.Date; +import java.util.Optional; + +public class SchedulerTaskRecordConversion { + + static { + TypeStore.STORE.register(SchedulerTaskRecordDO.class, SchedulerTaskRecordVO.class, p -> { + SchedulerTaskRecordVO vo = new SchedulerTaskRecordVO(); + vo.setId(p.getId()); + vo.setTaskId(p.getTaskId()); + vo.setTaskName(p.getTaskName()); + vo.setStatus(p.getTaskStatus()); + Date startTime = p.getStartTime(); + Date endTime = p.getEndTime(); + vo.setStartTime(startTime); + vo.setEndTime(endTime); + if (startTime != null && endTime != null) { + vo.setUsed(endTime.getTime() - startTime.getTime()); + vo.setKeepTime(Utils.interval(vo.getUsed())); + } + Optional.ofNullable(startTime) + .map(Dates::ago) + .ifPresent(vo::setStartTimeAgo); + Optional.ofNullable(endTime) + .map(Dates::ago) + .ifPresent(vo::setEndTimeAgo); + return vo; + }); + } + + static { + TypeStore.STORE.register(SchedulerTaskRecordDO.class, SchedulerTaskRecordStatusVO.class, p -> { + SchedulerTaskRecordStatusVO vo = new SchedulerTaskRecordStatusVO(); + vo.setId(p.getId()); + vo.setStatus(p.getTaskStatus()); + Date startTime = p.getStartTime(); + Date endTime = p.getEndTime(); + vo.setStartTime(startTime); + vo.setEndTime(endTime); + if (startTime != null && endTime != null) { + vo.setUsed(endTime.getTime() - startTime.getTime()); + vo.setKeepTime(Utils.interval(vo.getUsed())); + } + Optional.ofNullable(startTime) + .map(Dates::ago) + .ifPresent(vo::setStartTimeAgo); + Optional.ofNullable(endTime) + .map(Dates::ago) + .ifPresent(vo::setEndTimeAgo); + return vo; + }); + } + + static { + TypeStore.STORE.register(SchedulerTaskRecordStatisticsDTO.class, SchedulerTaskRecordStatisticsChartVO.class, p -> { + SchedulerTaskRecordStatisticsChartVO vo = new SchedulerTaskRecordStatisticsChartVO(); + vo.setDate(Dates.format(p.getDate(), Dates.YMD)); + vo.setScheduledCount(p.getScheduledCount()); + vo.setSuccessCount(p.getSuccessCount()); + vo.setFailureCount(p.getFailureCount()); + return vo; + }); + } + + static { + TypeStore.STORE.register(SchedulerTaskRecordStatisticsDTO.class, SchedulerTaskRecordStatisticsVO.class, p -> { + SchedulerTaskRecordStatisticsVO vo = new SchedulerTaskRecordStatisticsVO(); + vo.setScheduledCount(p.getScheduledCount()); + vo.setSuccessCount(p.getSuccessCount()); + vo.setFailureCount(p.getFailureCount()); + vo.setAvgUsed(p.getAvgUsed()); + vo.setAvgUsedInterval(Utils.interval(p.getAvgUsed())); + return vo; + }); + } + + static { + TypeStore.STORE.register(SchedulerTaskMachineRecordDO.class, SchedulerTaskMachineRecordVO.class, p -> { + SchedulerTaskMachineRecordVO vo = new SchedulerTaskMachineRecordVO(); + vo.setId(p.getId()); + vo.setRecordId(p.getRecordId()); + vo.setMachineId(p.getMachineId()); + vo.setMachineName(p.getMachineName()); + vo.setMachineHost(p.getMachineHost()); + vo.setMachineTag(p.getMachineTag()); + vo.setCommand(p.getExecCommand()); + vo.setStatus(p.getExecStatus()); + vo.setExitCode(p.getExitCode()); + Date startTime = p.getStartTime(); + Date endTime = p.getEndTime(); + vo.setStartTime(startTime); + vo.setEndTime(endTime); + if (startTime != null && endTime != null) { + vo.setUsed(endTime.getTime() - startTime.getTime()); + vo.setKeepTime(Utils.interval(vo.getUsed())); + } + Optional.ofNullable(startTime) + .map(Dates::ago) + .ifPresent(vo::setStartTimeAgo); + Optional.ofNullable(endTime) + .map(Dates::ago) + .ifPresent(vo::setEndTimeAgo); + return vo; + }); + } + + static { + TypeStore.STORE.register(SchedulerTaskMachineRecordDO.class, SchedulerTaskMachineRecordStatusVO.class, p -> { + SchedulerTaskMachineRecordStatusVO vo = new SchedulerTaskMachineRecordStatusVO(); + vo.setId(p.getId()); + vo.setRecordId(p.getRecordId()); + vo.setStatus(p.getExecStatus()); + vo.setExitCode(p.getExitCode()); + Date startTime = p.getStartTime(); + Date endTime = p.getEndTime(); + vo.setStartTime(startTime); + vo.setEndTime(endTime); + if (startTime != null && endTime != null) { + vo.setUsed(endTime.getTime() - startTime.getTime()); + vo.setKeepTime(Utils.interval(vo.getUsed())); + } + Optional.ofNullable(startTime) + .map(Dates::ago) + .ifPresent(vo::setStartTimeAgo); + Optional.ofNullable(endTime) + .map(Dates::ago) + .ifPresent(vo::setEndTimeAgo); + return vo; + }); + } + + static { + TypeStore.STORE.register(SchedulerTaskRecordStatisticsDTO.class, SchedulerTaskMachineRecordStatisticsVO.class, p -> { + SchedulerTaskMachineRecordStatisticsVO vo = new SchedulerTaskMachineRecordStatisticsVO(); + vo.setMachineId(p.getMachineId()); + vo.setMachineName(p.getMachineName()); + vo.setScheduledCount(p.getScheduledCount()); + vo.setSuccessCount(p.getSuccessCount()); + vo.setFailureCount(p.getFailureCount()); + vo.setAvgUsed(p.getAvgUsed()); + vo.setAvgUsedInterval(Utils.interval(p.getAvgUsed())); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/sftp/SftpConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/sftp/SftpConversion.java new file mode 100644 index 0000000..6e61072 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/sftp/SftpConversion.java @@ -0,0 +1,46 @@ + +package cn.orionsec.ops.mapping.sftp; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.lang.utils.io.FileType; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.net.host.sftp.SftpFile; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.entity.dto.sftp.SftpUploadInfoDTO; +import cn.orionsec.ops.entity.request.sftp.FileUploadRequest; +import cn.orionsec.ops.entity.vo.sftp.FileDetailVO; + +import java.util.Optional; + +public class SftpConversion { + + static { + TypeStore.STORE.register(SftpFile.class, FileDetailVO.class, s -> { + FileDetailVO vo = new FileDetailVO(); + vo.setName(s.getName()); + vo.setPath(s.getPath()); + vo.setSize(Files1.getSize(s.getSize())); + vo.setSizeByte(s.getSize()); + vo.setPermission(s.getPermission()); + vo.setUid(s.getUid()); + vo.setGid(s.getGid()); + vo.setAttr(s.getPermissionString()); + vo.setModifyTime(s.getModifyTime()); + Boolean isDir = Optional.ofNullable(FileType.of(vo.getAttr())) + .map(FileType.DIRECTORY::equals) + .orElse(false); + vo.setIsDir(isDir); + vo.setIsSafe(!Const.UNSAFE_FS_DIR.contains(s.getPath())); + return vo; + }); + } + + static { + TypeStore.STORE.register(FileUploadRequest.class, SftpUploadInfoDTO.class, p -> { + SftpUploadInfoDTO dto = new SftpUploadInfoDTO(); + dto.setRemotePath(p.getRemotePath()); + return dto; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/system/SystemAnalysisConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/system/SystemAnalysisConversion.java new file mode 100644 index 0000000..c225b94 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/system/SystemAnalysisConversion.java @@ -0,0 +1,29 @@ + +package cn.orionsec.ops.mapping.system; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.ops.entity.dto.system.SystemSpaceAnalysisDTO; +import cn.orionsec.ops.entity.vo.system.SystemAnalysisVO; + +public class SystemAnalysisConversion { + + static { + TypeStore.STORE.register(SystemSpaceAnalysisDTO.class, SystemAnalysisVO.class, p -> { + SystemAnalysisVO vo = new SystemAnalysisVO(); + vo.setTempFileCount(p.getTempFileCount()); + vo.setTempFileSize(p.getTempFileSize()); + vo.setLogFileCount(p.getLogFileCount()); + vo.setLogFileSize(p.getLogFileSize()); + vo.setSwapFileCount(p.getSwapFileCount()); + vo.setSwapFileSize(p.getSwapFileSize()); + vo.setDistVersionCount(p.getDistVersionCount()); + vo.setDistFileSize(p.getDistFileSize()); + vo.setRepoVersionCount(p.getRepoVersionCount()); + vo.setRepoVersionFileSize(p.getRepoVersionFileSize()); + vo.setScreenFileCount(p.getScreenFileCount()); + vo.setScreenFileSize(p.getScreenFileSize()); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/system/SystemEnvConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/system/SystemEnvConversion.java new file mode 100644 index 0000000..5bbdcf4 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/system/SystemEnvConversion.java @@ -0,0 +1,27 @@ + +package cn.orionsec.ops.mapping.system; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.entity.domain.SystemEnvDO; +import cn.orionsec.ops.entity.vo.system.SystemEnvVO; + +public class SystemEnvConversion { + + static { + TypeStore.STORE.register(SystemEnvDO.class, SystemEnvVO.class, p -> { + SystemEnvVO vo = new SystemEnvVO(); + vo.setId(p.getId()); + vo.setKey(p.getAttrKey()); + vo.setValue(p.getAttrValue()); + vo.setDescription(p.getDescription()); + vo.setCreateTime(p.getCreateTime()); + vo.setUpdateTime(p.getUpdateTime()); + Integer forbidDelete = SystemEnvAttr.of(p.getAttrKey()) == null ? Const.FORBID_DELETE_CAN : Const.FORBID_DELETE_NOT; + vo.setForbidDelete(forbidDelete); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/template/CommandTemplateConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/template/CommandTemplateConversion.java new file mode 100644 index 0000000..13669e7 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/template/CommandTemplateConversion.java @@ -0,0 +1,30 @@ + +package cn.orionsec.ops.mapping.template; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.ops.entity.domain.CommandTemplateDO; +import cn.orionsec.ops.entity.vo.template.CommandTemplateVO; + +public class CommandTemplateConversion { + + static { + TypeStore.STORE.register(CommandTemplateDO.class, CommandTemplateVO.class, p -> { + CommandTemplateVO vo = new CommandTemplateVO(); + vo.setId(p.getId()); + vo.setName(p.getTemplateName()); + vo.setValue(p.getTemplateValue()); + vo.setDescription(p.getDescription()); + vo.setCreateUserId(p.getCreateUserId()); + vo.setCreateUserName(p.getCreateUserName()); + vo.setUpdateUserId(p.getUpdateUserId()); + vo.setUpdateUserName(p.getUpdateUserName()); + vo.setCreateTime(p.getCreateTime()); + vo.setUpdateTime(p.getUpdateTime()); + vo.setCreateTimeAgo(Dates.ago(p.getCreateTime())); + vo.setUpdateTimeAgo(Dates.ago(p.getUpdateTime())); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/upload/BatchUploadConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/upload/BatchUploadConversion.java new file mode 100644 index 0000000..cea173b --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/upload/BatchUploadConversion.java @@ -0,0 +1,42 @@ + +package cn.orionsec.ops.mapping.upload; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.dto.sftp.SftpUploadInfoDTO; +import cn.orionsec.ops.entity.request.upload.BatchUploadRequest; +import cn.orionsec.ops.entity.vo.upload.BatchUploadCheckFileVO; +import cn.orionsec.ops.entity.vo.upload.BatchUploadCheckMachineVO; + +public class BatchUploadConversion { + + static { + TypeStore.STORE.register(BatchUploadRequest.class, SftpUploadInfoDTO.class, p -> { + SftpUploadInfoDTO dto = new SftpUploadInfoDTO(); + dto.setRemotePath(p.getRemotePath()); + dto.setMachineIdList(p.getMachineIds()); + return dto; + }); + } + + static { + TypeStore.STORE.register(MachineInfoDO.class, BatchUploadCheckFileVO.class, p -> { + BatchUploadCheckFileVO vo = new BatchUploadCheckFileVO(); + vo.setId(p.getId()); + vo.setName(p.getMachineName()); + vo.setHost(p.getMachineHost()); + return vo; + }); + } + + static { + TypeStore.STORE.register(MachineInfoDO.class, BatchUploadCheckMachineVO.class, p -> { + BatchUploadCheckMachineVO vo = new BatchUploadCheckMachineVO(); + vo.setId(p.getId()); + vo.setName(p.getMachineName()); + vo.setHost(p.getMachineHost()); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/user/UserEventLogConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/user/UserEventLogConversion.java new file mode 100644 index 0000000..a2ed8fe --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/user/UserEventLogConversion.java @@ -0,0 +1,28 @@ + +package cn.orionsec.ops.mapping.user; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.ops.entity.domain.UserEventLogDO; +import cn.orionsec.ops.entity.vo.user.UserEventLogVO; + +public class UserEventLogConversion { + + static { + TypeStore.STORE.register(UserEventLogDO.class, UserEventLogVO.class, p -> { + UserEventLogVO vo = new UserEventLogVO(); + vo.setId(p.getId()); + vo.setUserId(p.getUserId()); + vo.setUsername(p.getUsername()); + vo.setClassify(p.getEventClassify()); + vo.setType(p.getEventType()); + vo.setLog(p.getLogInfo()); + vo.setParams(p.getParamsJson()); + vo.setResult(p.getExecResult()); + vo.setCreateTime(p.getCreateTime()); + vo.setCreateTimeAgo(Dates.ago(p.getCreateTime())); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/user/UserInfoConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/user/UserInfoConversion.java new file mode 100644 index 0000000..63be130 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/user/UserInfoConversion.java @@ -0,0 +1,45 @@ + +package cn.orionsec.ops.mapping.user; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.ops.entity.domain.UserInfoDO; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.entity.vo.user.UserInfoVO; + +import java.util.Date; +import java.util.Optional; + +public class UserInfoConversion { + + static { + TypeStore.STORE.register(UserInfoDO.class, UserInfoVO.class, d -> { + UserInfoVO vo = new UserInfoVO(); + vo.setId(d.getId()); + vo.setUsername(d.getUsername()); + vo.setNickname(d.getNickname()); + vo.setRole(d.getRoleType()); + vo.setStatus(d.getUserStatus()); + vo.setLocked(d.getLockStatus()); + vo.setPhone(d.getContactPhone()); + vo.setEmail(d.getContactEmail()); + Date lastLoginTime = d.getLastLoginTime(); + vo.setLastLoginTime(lastLoginTime); + Optional.ofNullable(lastLoginTime).map(Dates::ago).ifPresent(vo::setLastLoginAgo); + return vo; + }); + } + + static { + TypeStore.STORE.register(UserDTO.class, UserInfoVO.class, d -> { + UserInfoVO vo = new UserInfoVO(); + vo.setId(d.getId()); + vo.setUsername(d.getUsername()); + vo.setNickname(d.getNickname()); + vo.setRole(d.getRoleType()); + vo.setStatus(d.getUserStatus()); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/webhook/WebhookConfigConversion.java b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/webhook/WebhookConfigConversion.java new file mode 100644 index 0000000..b905bd1 --- /dev/null +++ b/orion-ops-api/orion-ops-mapping/src/main/java/cn/orionsec/ops/mapping/webhook/WebhookConfigConversion.java @@ -0,0 +1,22 @@ + +package cn.orionsec.ops.mapping.webhook; + +import cn.orionsec.kit.lang.utils.convert.TypeStore; +import cn.orionsec.ops.entity.domain.WebhookConfigDO; +import cn.orionsec.ops.entity.vo.webhook.WebhookConfigVO; + +public class WebhookConfigConversion { + + static { + TypeStore.STORE.register(WebhookConfigDO.class, WebhookConfigVO.class, p -> { + WebhookConfigVO vo = new WebhookConfigVO(); + vo.setId(p.getId()); + vo.setName(p.getWebhookName()); + vo.setUrl(p.getWebhookUrl()); + vo.setType(p.getWebhookType()); + vo.setConfig(p.getWebhookConfig()); + return vo; + }); + } + +} diff --git a/orion-ops-api/orion-ops-model/pom.xml b/orion-ops-api/orion-ops-model/pom.xml new file mode 100644 index 0000000..677ef71 --- /dev/null +++ b/orion-ops-api/orion-ops-model/pom.xml @@ -0,0 +1,41 @@ + + + + cn.orionsec.ops + orion-ops-api + 1.3.1 + ../pom.xml + + + orion-ops-model + orion-ops-model + 4.0.0 + + + + + org.projectlombok + lombok + + + + + io.springfox + springfox-swagger2 + + + + + org.aspectj + aspectjweaver + + + + + cn.orionsec.kit + orion-all + + + + diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/config/TerminalConnectConfig.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/config/TerminalConnectConfig.java new file mode 100644 index 0000000..211027c --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/config/TerminalConnectConfig.java @@ -0,0 +1,50 @@ + +package cn.orionsec.ops.entity.config; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "终端连接配置项") +public class TerminalConnectConfig { + + @ApiModelProperty(value = "行数") + private Integer rows; + + @ApiModelProperty(value = "列数") + private Integer cols; + + /** + * @see cn.orionsec.kit.net.host.ssh.TerminalType + */ + @ApiModelProperty(value = "终端类型") + private String terminalType; + + @ApiModelProperty(value = "用户id") + private Long userId; + + @ApiModelProperty(value = "用户名") + private String username; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "机器名称") + private String machineName; + + @ApiModelProperty(value = "机器host") + private String machineHost; + + @ApiModelProperty(value = "机器唯一标识") + private String machineTag; + + @ApiModelProperty(value = "连接时间") + private Date connectedTime; + + @ApiModelProperty(value = "日志id") + private Long logId; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/app/ApplicationPipelineStageConfigDTO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/app/ApplicationPipelineStageConfigDTO.java new file mode 100644 index 0000000..d2d4e45 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/app/ApplicationPipelineStageConfigDTO.java @@ -0,0 +1,32 @@ + +package cn.orionsec.ops.entity.dto.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "应用操作流水线配置") +public class ApplicationPipelineStageConfigDTO { + + @ApiModelProperty(value = "分支名称") + private String branchName; + + @ApiModelProperty(value = "提交id") + private String commitId; + + @ApiModelProperty(value = "构建id") + private Long buildId; + + @ApiModelProperty(value = "发布标题") + private String title; + + @ApiModelProperty(value = "描述") + private String description; + + @ApiModelProperty(value = "发布机器id") + private List machineIdList; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/app/ApplicationProfileDTO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/app/ApplicationProfileDTO.java new file mode 100644 index 0000000..df9fb32 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/app/ApplicationProfileDTO.java @@ -0,0 +1,21 @@ + +package cn.orionsec.ops.entity.dto.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "应用环境缓存") +public class ApplicationProfileDTO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "环境名称") + private String profileName; + + @ApiModelProperty(value = "环境唯一标识") + private String profileTag; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/file/FileDownloadDTO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/file/FileDownloadDTO.java new file mode 100644 index 0000000..29ed7da --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/file/FileDownloadDTO.java @@ -0,0 +1,31 @@ + +package cn.orionsec.ops.entity.dto.file; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "文件下载缓存对象") +@SuppressWarnings("ALL") +public class FileDownloadDTO { + + @ApiModelProperty(value = "文件绝对路径") + private String filePath; + + @ApiModelProperty(value = "文件名称") + private String fileName; + + @ApiModelProperty(value = "下载用户id") + private Long userId; + + /** + * @see cn.orionsec.ops.constant.download.FileDownloadType + */ + @ApiModelProperty(value = "下载类型") + private Integer type; + + @ApiModelProperty(value = "机器id") + private Long machineId; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/file/FileTailDTO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/file/FileTailDTO.java new file mode 100644 index 0000000..b257d8e --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/file/FileTailDTO.java @@ -0,0 +1,46 @@ + +package cn.orionsec.ops.entity.dto.file; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "文件 tail 对象") +@SuppressWarnings("ALL") +public class FileTailDTO { + + @ApiModelProperty(value = "文件绝对路径") + private String filePath; + + @ApiModelProperty(value = "用户id") + private Long userId; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + /** + * @see cn.orionsec.ops.constant.system.SystemEnvAttr#TAIL_MODE + * @see cn.orionsec.ops.constant.tail.FileTailMode + */ + @ApiModelProperty(value = "tail 模式") + private String mode; + + /** + * @see cn.orionsec.ops.constant.machine.MachineEnvAttr#TAIL_OFFSET + * @see cn.orionsec.ops.constant.Const#TAIL_OFFSET_LINE + */ + @ApiModelProperty(value = "尾行偏移量") + private Integer offset; + + /** + * @see cn.orionsec.ops.constant.machine.MachineEnvAttr#TAIL_CHARSET + * @see cn.orionsec.ops.constant.Const#UTF_8 + */ + @ApiModelProperty(value = "编码集") + private String charset; + + @ApiModelProperty(value = "命令") + private String command; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/sftp/FileTransferNotifyDTO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/sftp/FileTransferNotifyDTO.java new file mode 100644 index 0000000..d3a461c --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/sftp/FileTransferNotifyDTO.java @@ -0,0 +1,25 @@ + +package cn.orionsec.ops.entity.dto.sftp; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "sftp 文件传输通知") +@SuppressWarnings("ALL") +public class FileTransferNotifyDTO { + + /** + * @see cn.orionsec.ops.constant.sftp.SftpNotifyType + */ + @ApiModelProperty(value = "通知类型") + private Integer type; + + @ApiModelProperty(value = "fileToken") + private String fileToken; + + @ApiModelProperty(value = "body") + private Object body; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/sftp/FileTransferNotifyProgressDTO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/sftp/FileTransferNotifyProgressDTO.java new file mode 100644 index 0000000..09aac9f --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/sftp/FileTransferNotifyProgressDTO.java @@ -0,0 +1,23 @@ + +package cn.orionsec.ops.entity.dto.sftp; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +@ApiModel(value = "文件传输进度") +public class FileTransferNotifyProgressDTO { + + @ApiModelProperty(value = "速度") + private String rate; + + @ApiModelProperty(value = "当前位置") + private String current; + + @ApiModelProperty(value = "进度") + private String progress; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/sftp/SftpSessionTokenDTO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/sftp/SftpSessionTokenDTO.java new file mode 100644 index 0000000..65b2450 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/sftp/SftpSessionTokenDTO.java @@ -0,0 +1,23 @@ + +package cn.orionsec.ops.entity.dto.sftp; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "sftp 会话信息") +public class SftpSessionTokenDTO { + + @ApiModelProperty(value = "用户id") + private Long userId; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "机器id (批量上传用)") + private List machineIdList; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/sftp/SftpUploadInfoDTO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/sftp/SftpUploadInfoDTO.java new file mode 100644 index 0000000..d889f68 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/sftp/SftpUploadInfoDTO.java @@ -0,0 +1,26 @@ + +package cn.orionsec.ops.entity.dto.sftp; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "sftp 文件上传对象") +public class SftpUploadInfoDTO { + + @ApiModelProperty(value = "远程路径") + private String remotePath; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "机器id") + private List machineIdList; + + @ApiModelProperty(value = "用户id") + private Long userId; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/statistic/StatisticsCountDTO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/statistic/StatisticsCountDTO.java new file mode 100644 index 0000000..7e3ddbb --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/statistic/StatisticsCountDTO.java @@ -0,0 +1,24 @@ + +package cn.orionsec.ops.entity.dto.statistic; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "首页统计数量缓存") +public class StatisticsCountDTO { + + @ApiModelProperty(value = "机器数量") + private Integer machineCount; + + @ApiModelProperty(value = "环境数量") + private Integer profileCount; + + @ApiModelProperty(value = "应用数量") + private Integer appCount; + + @ApiModelProperty(value = "流水线数量") + private Integer pipelineCount; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/system/SystemSpaceAnalysisDTO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/system/SystemSpaceAnalysisDTO.java new file mode 100644 index 0000000..87104d6 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/system/SystemSpaceAnalysisDTO.java @@ -0,0 +1,48 @@ + +package cn.orionsec.ops.entity.dto.system; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "系统磁盘占用分析") +public class SystemSpaceAnalysisDTO { + + @ApiModelProperty(value = "临时文件数量") + private Integer tempFileCount; + + @ApiModelProperty(value = "临时文件大小") + private String tempFileSize; + + @ApiModelProperty(value = "日志文件数量") + private Integer logFileCount; + + @ApiModelProperty(value = "日志文件大小") + private String logFileSize; + + @ApiModelProperty(value = "交换文件数量") + private Integer swapFileCount; + + @ApiModelProperty(value = "交换文件大小") + private String swapFileSize; + + @ApiModelProperty(value = "构建产物版本数") + private Integer distVersionCount; + + @ApiModelProperty(value = "构建产物大小") + private String distFileSize; + + @ApiModelProperty(value = "应用仓库版本数") + private Integer repoVersionCount; + + @ApiModelProperty(value = "应用仓库大小") + private String repoVersionFileSize; + + @ApiModelProperty(value = "录屏文件数") + private Integer screenFileCount; + + @ApiModelProperty(value = "录屏文件大小") + private String screenFileSize; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/terminal/TerminalConnectDTO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/terminal/TerminalConnectDTO.java new file mode 100644 index 0000000..6a0ca4f --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/terminal/TerminalConnectDTO.java @@ -0,0 +1,22 @@ + +package cn.orionsec.ops.entity.dto.terminal; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + + +@Data +@ApiModel(value = "终端连接参数") +public class TerminalConnectDTO { + + @ApiModelProperty(value = "列数") + private Integer cols; + + @ApiModelProperty(value = "行数") + private Integer rows; + + @ApiModelProperty(value = "登录 token") + private String loginToken; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/terminal/TerminalSizeDTO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/terminal/TerminalSizeDTO.java new file mode 100644 index 0000000..525fc63 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/terminal/TerminalSizeDTO.java @@ -0,0 +1,18 @@ + +package cn.orionsec.ops.entity.dto.terminal; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "终端大小参数") +public class TerminalSizeDTO { + + @ApiModelProperty(value = "列数") + private Integer cols; + + @ApiModelProperty(value = "行数") + private Integer rows; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/terminal/TerminalWatcherDTO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/terminal/TerminalWatcherDTO.java new file mode 100644 index 0000000..8bc3d0b --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/terminal/TerminalWatcherDTO.java @@ -0,0 +1,31 @@ + +package cn.orionsec.ops.entity.dto.terminal; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@ApiModel(value = "终端监视信息") +@SuppressWarnings("ALL") +public class TerminalWatcherDTO { + + @ApiModelProperty(value = "用户id") + private Long userId; + + @ApiModelProperty(value = "会话token") + private String token; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + */ + @ApiModelProperty(value = "是否只读") + private Integer readonly; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/user/LoginBindDTO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/user/LoginBindDTO.java new file mode 100644 index 0000000..dfdd165 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/user/LoginBindDTO.java @@ -0,0 +1,22 @@ + +package cn.orionsec.ops.entity.dto.user; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "登录绑定信息") +public class LoginBindDTO implements Serializable { + + @ApiModelProperty(value = "登录时间戳") + private Long timestamp; + + @ApiModelProperty(value = "登录 IP") + private String loginIp; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/user/UserDTO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/user/UserDTO.java new file mode 100644 index 0000000..f27ce82 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/dto/user/UserDTO.java @@ -0,0 +1,45 @@ + +package cn.orionsec.ops.entity.dto.user; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serializable; + +@Data +@EqualsAndHashCode(callSuper = false) +@ApiModel(value = "用户信息") +@SuppressWarnings("ALL") +public class UserDTO implements Serializable { + + @ApiModelProperty(value = "用户id") + private Long id; + + @ApiModelProperty(value = "用户名") + private String username; + + @ApiModelProperty(value = "昵称") + private String nickname; + + /** + * @see cn.orionsec.ops.constant.user.RoleType + */ + @ApiModelProperty(value = "角色类型") + private Integer roleType; + + @ApiModelProperty(value = "登录时间戳") + private Long timestamp; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "用户状态 1启用 2禁用") + private Integer userStatus; + + @ApiModelProperty(value = "当前用户绑定时间戳 无需设置") + private Long currentBindTimestamp; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/alarm/AlarmGroupNotifyRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/alarm/AlarmGroupNotifyRequest.java new file mode 100644 index 0000000..d98ae24 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/alarm/AlarmGroupNotifyRequest.java @@ -0,0 +1,26 @@ + +package cn.orionsec.ops.entity.request.alarm; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "报警组通知方式请求") +@SuppressWarnings("ALL") +public class AlarmGroupNotifyRequest extends PageRequest { + + @ApiModelProperty(value = "通知id") + private Long notifyId; + + /** + * @see cn.orionsec.ops.constant.alarm.AlarmGroupNotifyType + */ + @ApiModelProperty(value = "通知类型") + private Integer notifyType; + +} \ No newline at end of file diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/alarm/AlarmGroupRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/alarm/AlarmGroupRequest.java new file mode 100644 index 0000000..ce5ec43 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/alarm/AlarmGroupRequest.java @@ -0,0 +1,32 @@ + +package cn.orionsec.ops.entity.request.alarm; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "报警组请求") +public class AlarmGroupRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "报警组名称") + private String name; + + @ApiModelProperty(value = "报警组描述") + private String description; + + @ApiModelProperty(value = "报警组员id") + private List userIdList; + + @ApiModelProperty(value = "报警通知方式") + private List notifyIdList; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/AppBuildStatisticsRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/AppBuildStatisticsRequest.java new file mode 100644 index 0000000..adccdf7 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/AppBuildStatisticsRequest.java @@ -0,0 +1,19 @@ + +package cn.orionsec.ops.entity.request.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + + +@Data +@ApiModel(value = "应用构建统计") +public class AppBuildStatisticsRequest { + + @ApiModelProperty(value = "appId") + private Long appId; + + @ApiModelProperty(value = "profileId") + private Long profileId; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/AppPipelineTaskStatisticsRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/AppPipelineTaskStatisticsRequest.java new file mode 100644 index 0000000..5cf05b8 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/AppPipelineTaskStatisticsRequest.java @@ -0,0 +1,15 @@ + +package cn.orionsec.ops.entity.request.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "应用流水线统计请求") +public class AppPipelineTaskStatisticsRequest { + + @ApiModelProperty(value = "pipelineId") + private Long pipelineId; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/AppReleaseStatisticsRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/AppReleaseStatisticsRequest.java new file mode 100644 index 0000000..92bfba6 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/AppReleaseStatisticsRequest.java @@ -0,0 +1,18 @@ + +package cn.orionsec.ops.entity.request.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "应用发布统计请求") +public class AppReleaseStatisticsRequest { + + @ApiModelProperty(value = "appId") + private Long appId; + + @ApiModelProperty(value = "profileId") + private Long profileId; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationActionLogRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationActionLogRequest.java new file mode 100644 index 0000000..62702c7 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationActionLogRequest.java @@ -0,0 +1,15 @@ + +package cn.orionsec.ops.entity.request.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "应用操作日志请求") +public class ApplicationActionLogRequest { + + @ApiModelProperty(value = "id") + private Long id; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationBuildRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationBuildRequest.java new file mode 100644 index 0000000..9908dde --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationBuildRequest.java @@ -0,0 +1,60 @@ + +package cn.orionsec.ops.entity.request.app; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "应用构建请求") +@SuppressWarnings("ALL") +public class ApplicationBuildRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "id") + private List idList; + + @ApiModelProperty(value = "应用id") + private Long appId; + + @ApiModelProperty(value = "应用名称") + private String appName; + + @ApiModelProperty(value = "环境id") + private Long profileId; + + @ApiModelProperty(value = "构建序列") + private Integer seq; + + @ApiModelProperty(value = "构建分支") + private String branchName; + + @ApiModelProperty(value = "构建提交id") + private String commitId; + + /** + * @see cn.orionsec.ops.constant.app.BuildStatus + */ + @ApiModelProperty(value = "状态 10未开始 20执行中 30已完成 40执行失败 50已取消") + private Integer status; + + @ApiModelProperty(value = "描述") + private String description; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + */ + @ApiModelProperty(value = "只看自己") + private Integer onlyMyself; + + @ApiModelProperty(value = "命令") + private String command; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationConfigActionRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationConfigActionRequest.java new file mode 100644 index 0000000..5233ce6 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationConfigActionRequest.java @@ -0,0 +1,25 @@ + +package cn.orionsec.ops.entity.request.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "应用发布操作配置请求") +@SuppressWarnings("ALL") +public class ApplicationConfigActionRequest { + + @ApiModelProperty(value = "名称") + private String name; + + /** + * @see cn.orionsec.ops.constant.app.ActionType + */ + @ApiModelProperty(value = "类型") + private Integer type; + + @ApiModelProperty(value = "执行命令") + private String command; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationConfigEnvRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationConfigEnvRequest.java new file mode 100644 index 0000000..41cc4af --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationConfigEnvRequest.java @@ -0,0 +1,52 @@ + +package cn.orionsec.ops.entity.request.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "应用环境配置请求") +@SuppressWarnings("ALL") +public class ApplicationConfigEnvRequest { + + /** + * @see cn.orionsec.ops.constant.app.ApplicationEnvAttr#BUNDLE_PATH + */ + @ApiModelProperty(value = "构建产物路径") + private String bundlePath; + + /** + * @see cn.orionsec.ops.constant.app.ApplicationEnvAttr#TRANSFER_PATH + */ + @ApiModelProperty(value = "产物传输绝对路径") + private String transferPath; + + /** + * @see cn.orionsec.ops.constant.app.ApplicationEnvAttr#TRANSFER_MODE + */ + @ApiModelProperty(value = "产物传输方式 (sftp/scp)") + private String transferMode; + + /** + * @see cn.orionsec.ops.constant.app.ApplicationEnvAttr#TRANSFER_FILE_TYPE + */ + @ApiModelProperty(value = "产物传输文件类型 (normal/zip)") + private String transferFileType; + + /** + * @see cn.orionsec.ops.constant.app.ApplicationEnvAttr#RELEASE_SERIAL + * @see cn.orionsec.ops.constant.common.SerialType + */ + @ApiModelProperty(value = "发布序列 10串行 20并行") + private Integer releaseSerial; + + /** + * @see cn.orionsec.ops.constant.app.ApplicationEnvAttr#EXCEPTION_HANDLER + * @see cn.orionsec.ops.constant.common.ExceptionHandlerType + * @see cn.orionsec.ops.constant.common.SerialType#SERIAL + */ + @ApiModelProperty(value = "异常处理 10跳过所有 20跳过错误") + private Integer exceptionHandler; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationConfigRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationConfigRequest.java new file mode 100644 index 0000000..a9ee4fc --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationConfigRequest.java @@ -0,0 +1,39 @@ + +package cn.orionsec.ops.entity.request.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "应用配置请求") +@SuppressWarnings("ALL") +public class ApplicationConfigRequest { + + @ApiModelProperty(value = "应用id") + private Long appId; + + @ApiModelProperty(value = "环境id") + private Long profileId; + + /** + * @see cn.orionsec.ops.constant.app.StageType + */ + @ApiModelProperty(value = "阶段类型") + private Integer stageType; + + @ApiModelProperty(value = "应用环境变量") + private ApplicationConfigEnvRequest env; + + @ApiModelProperty(value = "机器id") + private List machineIdList; + + @ApiModelProperty(value = "构建操作") + private List buildActions; + + @ApiModelProperty(value = "发布操作") + private List releaseActions; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationEnvRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationEnvRequest.java new file mode 100644 index 0000000..36d5cae --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationEnvRequest.java @@ -0,0 +1,48 @@ + +package cn.orionsec.ops.entity.request.app; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "应用环境变量请求") +@SuppressWarnings("ALL") +public class ApplicationEnvRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "id集合") + private List idList; + + @ApiModelProperty(value = "应用id") + private Long appId; + + @ApiModelProperty(value = "环境id") + private Long profileId; + + @ApiModelProperty(value = "key") + private String key; + + @ApiModelProperty(value = "value") + private String value; + + @ApiModelProperty(value = "描述") + private String description; + + /** + * @see cn.orionsec.ops.constant.env.EnvViewType + */ + @ApiModelProperty(value = "视图类型") + private Integer viewType; + + @ApiModelProperty(value = "目标环境id") + private List targetProfileIdList; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationInfoRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationInfoRequest.java new file mode 100644 index 0000000..996c5ae --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationInfoRequest.java @@ -0,0 +1,50 @@ + +package cn.orionsec.ops.entity.request.app; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "应用请求") +@SuppressWarnings("ALL") +public class ApplicationInfoRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "环境id") + private Long profileId; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "唯一标识") + private String tag; + + @ApiModelProperty(value = "版本仓库id") + private Long repoId; + + @ApiModelProperty(value = "描述") + private String description; + + /** + * @see cn.orionsec.ops.constant.Const#INCREMENT + * @see cn.orionsec.ops.constant.Const#DECREMENT + */ + @ApiModelProperty(value = "排序调整方向") + private Integer sortAdjust; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + */ + @ApiModelProperty(value = "是否查询机器") + private Integer queryMachine; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationPipelineDetailRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationPipelineDetailRequest.java new file mode 100644 index 0000000..d87b1fa --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationPipelineDetailRequest.java @@ -0,0 +1,22 @@ + +package cn.orionsec.ops.entity.request.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "应用流水线详情请求") +@SuppressWarnings("ALL") +public class ApplicationPipelineDetailRequest { + + @ApiModelProperty(value = "应用id") + private Long appId; + + /** + * @see cn.orionsec.ops.constant.app.StageType + */ + @ApiModelProperty(value = "阶段类型 10构建 20发布") + private Integer stageType; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationPipelineRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationPipelineRequest.java new file mode 100644 index 0000000..3fc2bcb --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationPipelineRequest.java @@ -0,0 +1,45 @@ + +package cn.orionsec.ops.entity.request.app; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "应用流水线请求") +@SuppressWarnings("ALL") +public class ApplicationPipelineRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "idList") + private List idList; + + @ApiModelProperty(value = "环境id") + private Long profileId; + + @ApiModelProperty(value = "环境id") + private List profileIdList; + + @ApiModelProperty(value = "流水线名称") + private String name; + + @ApiModelProperty(value = "描述") + private String description; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + */ + @ApiModelProperty(value = "是否查询详情") + private Integer queryDetail; + + @ApiModelProperty(value = "详情") + private List details; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationPipelineTaskDetailRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationPipelineTaskDetailRequest.java new file mode 100644 index 0000000..42167cc --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationPipelineTaskDetailRequest.java @@ -0,0 +1,48 @@ + +package cn.orionsec.ops.entity.request.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "流水线详情明细请求") +@SuppressWarnings("ALL") +public class ApplicationPipelineTaskDetailRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "分支名称") + private String branchName; + + @ApiModelProperty(value = "提交id") + private String commitId; + + @ApiModelProperty(value = "构建id") + private Long buildId; + + @ApiModelProperty(value = "发布标题") + private String title; + + @ApiModelProperty(value = "描述") + private String description; + + @ApiModelProperty(value = "发布机器id") + private List machineIdList; + + @ApiModelProperty(value = "应用id", hidden = true) + private Long appId; + + @ApiModelProperty(value = "环境id", hidden = true) + private Long profileId; + + /** + * @see cn.orionsec.ops.constant.app.StageType + */ + @ApiModelProperty(value = "阶段类型 10构建 20发布", hidden = true) + private Integer stageType; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationPipelineTaskRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationPipelineTaskRequest.java new file mode 100644 index 0000000..bd5865f --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationPipelineTaskRequest.java @@ -0,0 +1,73 @@ + +package cn.orionsec.ops.entity.request.app; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "流水线明细请求") +@SuppressWarnings("ALL") +public class ApplicationPipelineTaskRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "id") + private List idList; + + @ApiModelProperty(value = "环境id") + private Long profileId; + + @ApiModelProperty(value = "detailId") + private Long detailId; + + @ApiModelProperty(value = "detailId") + private List detailIdList; + + @ApiModelProperty(value = "流水线id") + private Long pipelineId; + + @ApiModelProperty(value = "流水线名称") + private String pipelineName; + + @ApiModelProperty(value = "执行标题") + private String title; + + @ApiModelProperty(value = "执行描述") + private String description; + + /** + * @see cn.orionsec.ops.constant.app.TimedType + */ + @ApiModelProperty(value = "是否是定时执行 10普通执行 20定时执行") + private Integer timedExec; + + @ApiModelProperty(value = "定时执行时间") + private Date timedExecTime; + + /** + * @see cn.orionsec.ops.constant.app.PipelineStatus + */ + @ApiModelProperty(value = "执行状态 10待审核 20审核驳回 30待执行 35待调度 40执行中 50执行完成 60执行停止 70执行失败") + private Integer status; + + /** + * @see cn.orionsec.ops.constant.common.AuditStatus + */ + @ApiModelProperty(value = "审核状态 10通过 20驳回") + private Integer auditStatus; + + @ApiModelProperty(value = "审核描述") + private String auditReason; + + @ApiModelProperty(value = "执行明细") + private List details; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationProfileRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationProfileRequest.java new file mode 100644 index 0000000..a3cd565 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationProfileRequest.java @@ -0,0 +1,35 @@ + +package cn.orionsec.ops.entity.request.app; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "应用环境配置请求") +@SuppressWarnings("ALL") +public class ApplicationProfileRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "唯一标识") + private String tag; + + @ApiModelProperty(value = "描述") + private String description; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "发布是否需要审核 1需要 2无需") + private Integer releaseAudit; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationReleaseAuditRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationReleaseAuditRequest.java new file mode 100644 index 0000000..c84ffdb --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationReleaseAuditRequest.java @@ -0,0 +1,25 @@ + +package cn.orionsec.ops.entity.request.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "应用发布审核请求") +@SuppressWarnings("ALL") +public class ApplicationReleaseAuditRequest { + + @ApiModelProperty(value = "id") + private Long id; + + /** + * @see cn.orionsec.ops.constant.common.AuditStatus + */ + @ApiModelProperty(value = "状态 10通过 20驳回") + private Integer status; + + @ApiModelProperty(value = "描述") + private String reason; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationReleaseRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationReleaseRequest.java new file mode 100644 index 0000000..2769bc6 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationReleaseRequest.java @@ -0,0 +1,82 @@ + +package cn.orionsec.ops.entity.request.app; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "应用发布请求") +@SuppressWarnings("ALL") +public class ApplicationReleaseRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "标题") + private String title; + + @ApiModelProperty(value = "描述") + private String description; + + @ApiModelProperty(value = "应用id") + private Long appId; + + @ApiModelProperty(value = "应用名称") + private String appName; + + @ApiModelProperty(value = "环境id") + private Long profileId; + + @ApiModelProperty(value = "构建id") + private Long buildId; + + @ApiModelProperty(value = "应用机器id列表") + private List machineIdList; + + /** + * @see cn.orionsec.ops.constant.app.ReleaseStatus + */ + @ApiModelProperty(value = "状态") + private Integer status; + + /** + * @see cn.orionsec.ops.constant.app.TimedType + */ + @ApiModelProperty(value = "是否是定时发布 10普通发布 20定时发布") + private Integer timedRelease; + + @ApiModelProperty(value = "定时发布时间") + private Date timedReleaseTime; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + */ + @ApiModelProperty(value = "只看自己") + private Integer onlyMyself; + + @ApiModelProperty(value = "发布机器id") + private Long releaseMachineId; + + @ApiModelProperty(value = "发布机器id") + private List releaseMachineIdList; + + @ApiModelProperty(value = "id列表") + private List idList; + + @ApiModelProperty(value = "是否查询机器") + private Integer queryMachine; + + @ApiModelProperty(value = "是否查询操作") + private Integer queryAction; + + @ApiModelProperty(value = "命令") + private String command; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationRepositoryRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationRepositoryRequest.java new file mode 100644 index 0000000..5640cb8 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationRepositoryRequest.java @@ -0,0 +1,70 @@ + +package cn.orionsec.ops.entity.request.app; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "应用版本仓库请求") +@SuppressWarnings("ALL") +public class ApplicationRepositoryRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "url") + private String url; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "描述") + private String description; + + /** + * @see cn.orionsec.ops.constant.app.RepositoryType + */ + @ApiModelProperty(value = "类型 1git") + private Integer type; + + /** + * @see cn.orionsec.ops.constant.app.RepositoryStatus + */ + @ApiModelProperty(value = "状态 10未初始化 20初始化中 30正常 40失败") + private Integer status; + + /** + * @see cn.orionsec.ops.constant.app.RepositoryAuthType + */ + @ApiModelProperty(value = "认证类型 10密码 20令牌") + private Integer authType; + + /** + * @see cn.orionsec.ops.constant.app.RepositoryTokenType + */ + @ApiModelProperty(value = "令牌类型 10github 20gitee 30gitlab") + private Integer tokenType; + + @ApiModelProperty(value = "用户名") + private String username; + + @ApiModelProperty(value = "密码") + private String password; + + @ApiModelProperty(value = "令牌") + private String privateToken; + + @ApiModelProperty(value = "分支名称") + private String branchName; + + @ApiModelProperty(value = "应用id") + private Long appId; + + @ApiModelProperty(value = "环境id") + private Long profileId; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationSyncConfigRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationSyncConfigRequest.java new file mode 100644 index 0000000..d98932d --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/app/ApplicationSyncConfigRequest.java @@ -0,0 +1,23 @@ + +package cn.orionsec.ops.entity.request.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "应用同步配置请求") +public class ApplicationSyncConfigRequest { + + @ApiModelProperty(value = "应用id") + private Long appId; + + @ApiModelProperty(value = "环境id") + private Long profileId; + + @ApiModelProperty(value = "需要同步到的环境id") + private List targetProfileIdList; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/data/DataClearRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/data/DataClearRequest.java new file mode 100644 index 0000000..52fc121 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/data/DataClearRequest.java @@ -0,0 +1,73 @@ + +package cn.orionsec.ops.entity.request.data; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "数据清理请求") +@SuppressWarnings("ALL") +public class DataClearRequest { + + @ApiModelProperty(value = "保留天数") + private Integer reserveDay; + + @ApiModelProperty(value = "保留条数") + private Integer reserveTotal; + + /** + * @see cn.orionsec.ops.constant.DataClearType + */ + @ApiModelProperty(value = "清理类型") + private Integer clearType; + + /** + * @see cn.orionsec.ops.constant.DataClearRange + */ + @ApiModelProperty(value = "清理区间") + private Integer range; + + @ApiModelProperty(value = "清理的引用id") + private List relIdList; + + @ApiModelProperty(value = "引用id") + private Long relId; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "环境id") + private Long profileId; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "只清理我创建的") + private Integer iCreated; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "只清理我审核的") + private Integer iAudited; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "只清理我执行的") + private Integer iExecute; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "只清理未读的") + private Integer onlyRead; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/data/DataExportRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/data/DataExportRequest.java new file mode 100644 index 0000000..9d0279a --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/data/DataExportRequest.java @@ -0,0 +1,50 @@ + +package cn.orionsec.ops.entity.request.data; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "数据导出请求") +@SuppressWarnings("ALL") +public class DataExportRequest { + + /** + * @see cn.orionsec.ops.constant.ExportType + */ + @ApiModelProperty(value = "导出类型") + private Integer exportType; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "是否导出密码") + private Integer exportPassword; + + @ApiModelProperty(value = "保护密码") + private String protectPassword; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "分类") + private Integer classify; + + @ApiModelProperty(value = "类型") + private Integer type; + + @ApiModelProperty(value = "用户id") + private Long userId; + + @ApiModelProperty(value = "状态") + private Integer status; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + */ + @ApiModelProperty(value = "只看自己") + private Integer onlyMyself; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/data/DataImportRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/data/DataImportRequest.java new file mode 100644 index 0000000..992b194 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/data/DataImportRequest.java @@ -0,0 +1,15 @@ + +package cn.orionsec.ops.entity.request.data; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "数据导入请求") +public class DataImportRequest { + + @ApiModelProperty(value = "导入token") + private String importToken; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/exec/CommandExecRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/exec/CommandExecRequest.java new file mode 100644 index 0000000..73884a4 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/exec/CommandExecRequest.java @@ -0,0 +1,66 @@ + +package cn.orionsec.ops.entity.request.exec; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "批量执行请求") +@SuppressWarnings("ALL") +public class CommandExecRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "id") + private List idList; + + @ApiModelProperty(value = "执行机器id") + private Long machineId; + + @ApiModelProperty(value = "机器名称") + private String machineName; + + @ApiModelProperty(value = "执行机器id") + private List machineIdList; + + @ApiModelProperty(value = "执行主机") + private String host; + + @ApiModelProperty(value = "命令") + private String command; + + @ApiModelProperty(value = "执行人") + private Long userId; + + @ApiModelProperty(value = "用户名") + private String username; + + /** + * @see cn.orionsec.ops.constant.command.ExecStatus + */ + @ApiModelProperty(value = "状态") + private Integer status; + + /** + * @see cn.orionsec.ops.constant.command.ExecType + */ + @ApiModelProperty(value = "类型") + private Integer type; + + @ApiModelProperty(value = "退出码") + private Integer exitCode; + + @ApiModelProperty(value = "描述") + private String description; + + @ApiModelProperty(value = "是否省略命令") + private boolean omitCommand; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/file/FileDownloadRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/file/FileDownloadRequest.java new file mode 100644 index 0000000..18cf84f --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/file/FileDownloadRequest.java @@ -0,0 +1,23 @@ + +package cn.orionsec.ops.entity.request.file; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + + +@Data +@ApiModel(value = "文件下载请求") +@SuppressWarnings("ALL") +public class FileDownloadRequest { + + @ApiModelProperty(value = "id") + private Long id; + + /** + * @see cn.orionsec.ops.constant.download.FileDownloadType + */ + @ApiModelProperty(value = "下载类型") + private Integer type; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/file/FileTailRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/file/FileTailRequest.java new file mode 100644 index 0000000..af1b8ad --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/file/FileTailRequest.java @@ -0,0 +1,66 @@ + +package cn.orionsec.ops.entity.request.file; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "文件tail请求") +@SuppressWarnings("ALL") +public class FileTailRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "idList") + private List idList; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "文件路径") + private String path; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + /** + * @see cn.orionsec.ops.constant.Const#TAIL_OFFSET_LINE + */ + @ApiModelProperty(value = "文件尾部偏移行") + private Integer offset; + + /** + * @see cn.orionsec.ops.constant.Const#UTF_8 + */ + @ApiModelProperty(value = "编码集") + private String charset; + + @ApiModelProperty(value = "tail命令") + private String command; + + /** + * @see cn.orionsec.ops.constant.tail.FileTailMode + */ + @ApiModelProperty(value = "宿主机文件追踪类型 tracker/tail") + private String tailMode; + + @ApiModelProperty(value = "relId") + private Long relId; + + /** + * @see cn.orionsec.ops.constant.tail.FileTailType + */ + @ApiModelProperty(value = "类型") + private Integer type; + + @ApiModelProperty(value = "token") + private String token; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/history/HistoryValueRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/history/HistoryValueRequest.java new file mode 100644 index 0000000..e122bbc --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/history/HistoryValueRequest.java @@ -0,0 +1,28 @@ + +package cn.orionsec.ops.entity.request.history; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "历史值快照请求") +@SuppressWarnings("ALL") +public class HistoryValueRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "值id") + private Long valueId; + + /** + * @see cn.orionsec.ops.constant.history.HistoryValueType + */ + @ApiModelProperty(value = "值类型") + private Integer valueType; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/home/HomeStatisticsRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/home/HomeStatisticsRequest.java new file mode 100644 index 0000000..32730ba --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/home/HomeStatisticsRequest.java @@ -0,0 +1,15 @@ + +package cn.orionsec.ops.entity.request.home; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "主页统计请求") +public class HomeStatisticsRequest { + + @ApiModelProperty(value = "profileId") + private Long profileId; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineAlarmConfigRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineAlarmConfigRequest.java new file mode 100644 index 0000000..8e28c51 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineAlarmConfigRequest.java @@ -0,0 +1,36 @@ + +package cn.orionsec.ops.entity.request.machine; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "机器报警配置请求") +@SuppressWarnings("ALL") +public class MachineAlarmConfigRequest { + + @ApiModelProperty(value = "machineId") + private Long machineId; + + /** + * @see cn.orionsec.ops.constant.machine.MachineAlarmType + */ + @ApiModelProperty(value = "报警类型 10: cpu使用率 20: 内存使用率") + private Integer type; + + @ApiModelProperty(value = "报警阈值") + private Double alarmThreshold; + + @ApiModelProperty(value = "触发报警阈值 次") + private Integer triggerThreshold; + + @ApiModelProperty(value = "报警通知沉默时间 分") + private Integer notifySilence; + + @ApiModelProperty(value = "报警组id") + private List groupIdList; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineAlarmHistoryRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineAlarmHistoryRequest.java new file mode 100644 index 0000000..c1901be --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineAlarmHistoryRequest.java @@ -0,0 +1,42 @@ + +package cn.orionsec.ops.entity.request.machine; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "机器报警历史请求") +@SuppressWarnings("ALL") +public class MachineAlarmHistoryRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "machineId") + private Long machineId; + + /** + * @see cn.orionsec.ops.constant.machine.MachineAlarmType + */ + @ApiModelProperty(value = "报警类型 10: cpu使用率 20: 内存使用率") + private Integer type; + + @ApiModelProperty(value = "报警值区间开始") + private Double alarmValueStart; + + @ApiModelProperty(value = "报警值区间结束") + private Double alarmValueEnd; + + @ApiModelProperty(value = "报警时间区间开始") + private Date alarmTimeStart; + + @ApiModelProperty(value = "报警时间区间结束") + private Date alarmTimeEnd; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineAlarmRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineAlarmRequest.java new file mode 100644 index 0000000..f4facd5 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineAlarmRequest.java @@ -0,0 +1,30 @@ + +package cn.orionsec.ops.entity.request.machine; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "机器报警请求") +@SuppressWarnings("ALL") +public class MachineAlarmRequest { + + @ApiModelProperty(value = "机器id") + private Long machineId; + + /** + * @see cn.orionsec.ops.constant.machine.MachineAlarmType + */ + @ApiModelProperty(value = "报警类型 10: cpu使用率 20: 内存使用率") + private Integer type; + + @ApiModelProperty(value = "报警值") + private Double alarmValue; + + @ApiModelProperty(value = "报警时间") + private Date alarmTime; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineEnvRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineEnvRequest.java new file mode 100644 index 0000000..daf427f --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineEnvRequest.java @@ -0,0 +1,45 @@ + +package cn.orionsec.ops.entity.request.machine; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "机器环境变量请求") +@SuppressWarnings("ALL") +public class MachineEnvRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "id") + private List idList; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "key") + private String key; + + @ApiModelProperty(value = "value") + private String value; + + @ApiModelProperty(value = "描述") + private String description; + + /** + * @see cn.orionsec.ops.constant.env.EnvViewType + */ + @ApiModelProperty(value = "视图类型") + private Integer viewType; + + @ApiModelProperty(value = "目标机器id") + private List targetMachineIdList; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineGroupRelRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineGroupRelRequest.java new file mode 100644 index 0000000..0da72e9 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineGroupRelRequest.java @@ -0,0 +1,29 @@ + +package cn.orionsec.ops.entity.request.machine; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "机器分组请求") +public class MachineGroupRelRequest { + + @ApiModelProperty(value = "组id") + private Long groupId; + + @ApiModelProperty(value = "组id") + private List groupIdList; + + @ApiModelProperty(value = "目标组id") + private Long targetGroupId; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "机器id") + private List machineIdList; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineGroupRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineGroupRequest.java new file mode 100644 index 0000000..8f39295 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineGroupRequest.java @@ -0,0 +1,39 @@ + +package cn.orionsec.ops.entity.request.machine; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "机器分组请求") +@SuppressWarnings("ALL") +public class MachineGroupRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "id") + private List idList; + + @ApiModelProperty(value = "移动到的节点id") + private Long targetId; + + @ApiModelProperty(value = "父id") + private Long parentId; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "排序") + private Integer sort; + + /** + * @see cn.orionsec.ops.constant.common.TreeMoveType + */ + @ApiModelProperty(value = "移动类型") + private Integer moveType; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineInfoRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineInfoRequest.java new file mode 100644 index 0000000..b8f558c --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineInfoRequest.java @@ -0,0 +1,70 @@ + +package cn.orionsec.ops.entity.request.machine; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "机器信息请求") +@SuppressWarnings("ALL") +public class MachineInfoRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "idList") + private List idList; + + @ApiModelProperty(value = "代理id") + private Long proxyId; + + @ApiModelProperty(value = "密钥id") + private Long keyId; + + @ApiModelProperty(value = "主机ip") + private String host; + + @ApiModelProperty(value = "ssh端口") + private Integer sshPort; + + @ApiModelProperty(value = "机器名称") + private String name; + + @ApiModelProperty(value = "机器唯一标识") + private String tag; + + @ApiModelProperty(value = "机器描述") + private String description; + + @ApiModelProperty(value = "机器账号") + private String username; + + @ApiModelProperty(value = "机器密码") + private String password; + + /** + * @see cn.orionsec.ops.constant.machine.MachineAuthType + */ + @ApiModelProperty(value = "机器认证方式 1: 密码认证 2: 独立密钥") + private Integer authType; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "机器状态 1有效 2无效") + private Integer status; + + @ApiModelProperty(value = "是否查询分组") + private Boolean queryGroup; + + @ApiModelProperty(value = "分组id") + private List groupIdList; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineKeyRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineKeyRequest.java new file mode 100644 index 0000000..f4ee173 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineKeyRequest.java @@ -0,0 +1,39 @@ + +package cn.orionsec.ops.entity.request.machine; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "机器密钥请求") +@SuppressWarnings("ALL") +public class MachineKeyRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "id") + private List idList; + + @ApiModelProperty(value = "机器id集合") + private List machineIdList; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "密码") + private String password; + + @ApiModelProperty(value = "描述") + private String description; + + @ApiModelProperty(value = "文件base64") + private String file; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineMonitorEndpointRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineMonitorEndpointRequest.java new file mode 100644 index 0000000..eb68d5e --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineMonitorEndpointRequest.java @@ -0,0 +1,26 @@ + +package cn.orionsec.ops.entity.request.machine; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "机器监控请求") +public class MachineMonitorEndpointRequest { + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "数据粒度") + private Integer granularity; + + @ApiModelProperty(value = "开始区间 (秒)") + private Long startRange; + + @ApiModelProperty(value = "结束区间 (秒)") + private Long endRange; + + @ApiModelProperty(value = "磁盘序列") + private String seq; +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineMonitorRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineMonitorRequest.java new file mode 100644 index 0000000..4fa6bb8 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineMonitorRequest.java @@ -0,0 +1,40 @@ + +package cn.orionsec.ops.entity.request.machine; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "机器监控请求") +@SuppressWarnings("ALL") +public class MachineMonitorRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "机器名称") + private String machineName; + + /** + * @see cn.orionsec.ops.constant.monitor.MonitorStatus + */ + @ApiModelProperty(value = "安装状态") + private Integer status; + + @ApiModelProperty(value = "请求url") + private String url; + + @ApiModelProperty(value = "accessToken") + private String accessToken; + + @ApiModelProperty(value = "是否为升级") + private Boolean upgrade; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineMonitorSyncRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineMonitorSyncRequest.java new file mode 100644 index 0000000..37a86f1 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineMonitorSyncRequest.java @@ -0,0 +1,21 @@ + +package cn.orionsec.ops.entity.request.machine; + +import cn.orionsec.ops.entity.vo.machine.MachineAlarmConfigVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "机器监控同步请求") +public class MachineMonitorSyncRequest { + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "报警配置") + private List alarmConfig; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineProxyRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineProxyRequest.java new file mode 100644 index 0000000..8530b69 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineProxyRequest.java @@ -0,0 +1,45 @@ + +package cn.orionsec.ops.entity.request.machine; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "机器代理请求") +@SuppressWarnings("ALL") +public class MachineProxyRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "id") + private List idList; + + @ApiModelProperty(value = "主机") + private String host; + + @ApiModelProperty(value = "端口") + private Integer port; + + @ApiModelProperty(value = "用户名") + private String username; + + @ApiModelProperty(value = "密码") + private String password; + + /** + * @see cn.orionsec.ops.constant.machine.ProxyType + */ + @ApiModelProperty(value = "代理类型") + private Integer type; + + @ApiModelProperty(value = "描述") + private String description; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineTerminalLogRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineTerminalLogRequest.java new file mode 100644 index 0000000..058399b --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineTerminalLogRequest.java @@ -0,0 +1,57 @@ + +package cn.orionsec.ops.entity.request.machine; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "终端日志请求") +public class MachineTerminalLogRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "id") + private List idList; + + @ApiModelProperty(value = "用户id") + private Long userId; + + @ApiModelProperty(value = "用户名") + private String username; + + @ApiModelProperty(value = "机器id") + private Integer machineId; + + @ApiModelProperty(value = "机器名称") + private String machineName; + + @ApiModelProperty(value = "机器host") + private String machineHost; + + @ApiModelProperty(value = "token") + private String accessToken; + + @ApiModelProperty(value = "建立连接时间-区间开始") + private Date connectedTimeStart; + + @ApiModelProperty(value = "建立连接时间-区间结束") + private Date connectedTimeEnd; + + @ApiModelProperty(value = "断开连接时间-区间开始") + private Date disconnectedTimeStart; + + @ApiModelProperty(value = "断开连接时间-区间结束") + private Date disconnectedTimeEnd; + + @ApiModelProperty(value = "close code") + private Integer closeCode; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineTerminalManagerRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineTerminalManagerRequest.java new file mode 100644 index 0000000..e209736 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineTerminalManagerRequest.java @@ -0,0 +1,51 @@ + +package cn.orionsec.ops.entity.request.machine; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "终端管理请求") +@SuppressWarnings("ALL") +public class MachineTerminalManagerRequest extends PageRequest { + + @ApiModelProperty(value = "token") + private String token; + + @ApiModelProperty(value = "用户id") + private Long userId; + + @ApiModelProperty(value = "用户名") + private String username; + + @ApiModelProperty(value = "连接时间-区间开始") + private Date connectedTimeStart; + + @ApiModelProperty(value = "连接时间-区间结束") + private Date connectedTimeEnd; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "机器名称") + private String machineName; + + @ApiModelProperty(value = "机器主机") + private String machineHost; + + @ApiModelProperty(value = "机器唯一标识") + private String machineTag; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + */ + @ApiModelProperty(value = "是否只读") + private Integer readonly; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineTerminalRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineTerminalRequest.java new file mode 100644 index 0000000..b1af5b6 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/machine/MachineTerminalRequest.java @@ -0,0 +1,44 @@ + +package cn.orionsec.ops.entity.request.machine; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "终端配置请求") +@SuppressWarnings("ALL") +public class MachineTerminalRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + /** + * @see cn.orionsec.kit.net.host.ssh.TerminalType + */ + @ApiModelProperty(value = "终端类型") + private String terminalType; + + @ApiModelProperty(value = "背景色") + private String backgroundColor; + + @ApiModelProperty(value = "字体颜色") + private String fontColor; + + @ApiModelProperty(value = "字体大小") + private Integer fontSize; + + @ApiModelProperty(value = "字体名称") + private String fontFamily; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "是否开启url link 1开启 2关闭") + private Integer enableWebLink; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/message/WebSideMessageRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/message/WebSideMessageRequest.java new file mode 100644 index 0000000..e1c5c04 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/message/WebSideMessageRequest.java @@ -0,0 +1,55 @@ + +package cn.orionsec.ops.entity.request.message; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "站内信请求") +@SuppressWarnings("ALL") +public class WebSideMessageRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "id") + private List idList; + + /** + * @see cn.orionsec.ops.constant.message.MessageClassify + */ + @ApiModelProperty(value = "消息分类") + private Integer classify; + + /** + * @see cn.orionsec.ops.constant.message.MessageType + */ + @ApiModelProperty(value = "消息类型") + private Integer type; + + /** + * @see cn.orionsec.ops.constant.message.ReadStatus + */ + @ApiModelProperty(value = "是否已读 1未读 2已读") + private Integer status; + + @ApiModelProperty(value = "消息") + private String message; + + @ApiModelProperty(value = "最大id") + private Long maxId; + + @ApiModelProperty(value = "开始时间区间-开始") + private Date rangeStart; + + @ApiModelProperty(value = "开始时间区间-结束") + private Date rangeEnd; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/scheduler/SchedulerTaskRecordRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/scheduler/SchedulerTaskRecordRequest.java new file mode 100644 index 0000000..e43eae2 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/scheduler/SchedulerTaskRecordRequest.java @@ -0,0 +1,48 @@ + +package cn.orionsec.ops.entity.request.scheduler; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "任务执行记录请求") +@SuppressWarnings("ALL") +public class SchedulerTaskRecordRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "id") + private List idList; + + @ApiModelProperty(value = "明细id") + private Long recordId; + + @ApiModelProperty(value = "机器明细id") + private Long machineRecordId; + + @ApiModelProperty(value = "机器明细id") + private List machineRecordIdList; + + @ApiModelProperty(value = "任务id") + private Long taskId; + + @ApiModelProperty(value = "任务名称") + private String taskName; + + /** + * @see cn.orionsec.ops.constant.scheduler.SchedulerTaskStatus + */ + @ApiModelProperty(value = "状态") + private Integer status; + + @ApiModelProperty(value = "命令") + private String command; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/scheduler/SchedulerTaskRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/scheduler/SchedulerTaskRequest.java new file mode 100644 index 0000000..7121e3e --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/scheduler/SchedulerTaskRequest.java @@ -0,0 +1,64 @@ + +package cn.orionsec.ops.entity.request.scheduler; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "调度任务请求") +@SuppressWarnings("ALL") +public class SchedulerTaskRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "任务名称") + private String name; + + @ApiModelProperty(value = "任务描述") + private String description; + + @ApiModelProperty(value = "执行命令") + private String command; + + @ApiModelProperty(value = "cron表达式") + private String expression; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "启用状态 1启用 2停用") + private Integer enableStatus; + + /** + * @see cn.orionsec.ops.constant.scheduler.SchedulerTaskStatus + */ + @ApiModelProperty(value = "最近状态 10待调度 20调度中 30调度成功 40调度失败 50已停止") + private Integer latelyStatus; + + /** + * @see cn.orionsec.ops.constant.common.SerialType + */ + @ApiModelProperty(value = "调度序列 10串行 20并行") + private Integer serializeType; + + /** + * @see cn.orionsec.ops.constant.common.ExceptionHandlerType + */ + @ApiModelProperty(value = "异常处理 10跳过所有 20跳过错误") + private Integer exceptionHandler; + + @ApiModelProperty(value = "机器id") + private List machineIdList; + + @ApiModelProperty(value = "次数") + private Integer times; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/scheduler/SchedulerTaskStatisticsRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/scheduler/SchedulerTaskStatisticsRequest.java new file mode 100644 index 0000000..3803c16 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/scheduler/SchedulerTaskStatisticsRequest.java @@ -0,0 +1,15 @@ + +package cn.orionsec.ops.entity.request.scheduler; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "调度任务统计请求") +public class SchedulerTaskStatisticsRequest { + + @ApiModelProperty(value = "taskId") + private Long taskId; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileBaseRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileBaseRequest.java new file mode 100644 index 0000000..ce2ab10 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileBaseRequest.java @@ -0,0 +1,18 @@ + +package cn.orionsec.ops.entity.request.sftp; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "sftp请求") +public class FileBaseRequest { + + @ApiModelProperty(value = "sessionToken") + private String sessionToken; + + @ApiModelProperty(value = "是否查询隐藏文件") + private Boolean all; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileChangeGroupRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileChangeGroupRequest.java new file mode 100644 index 0000000..4d01e88 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileChangeGroupRequest.java @@ -0,0 +1,20 @@ + +package cn.orionsec.ops.entity.request.sftp; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "修改文件组请求") +public class FileChangeGroupRequest extends FileBaseRequest { + + @ApiModelProperty(value = "绝对路径") + private String path; + + @ApiModelProperty(value = "组id") + private Integer gid; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileChmodRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileChmodRequest.java new file mode 100644 index 0000000..bafb5db --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileChmodRequest.java @@ -0,0 +1,20 @@ + +package cn.orionsec.ops.entity.request.sftp; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "修改文件权限请求") +public class FileChmodRequest extends FileBaseRequest { + + @ApiModelProperty(value = "绝对路径") + private String path; + + @ApiModelProperty(value = "权限 10进制表现的8进制权限") + private Integer permission; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileChownRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileChownRequest.java new file mode 100644 index 0000000..bdc5aaf --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileChownRequest.java @@ -0,0 +1,20 @@ + +package cn.orionsec.ops.entity.request.sftp; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "修改所有者请求") +public class FileChownRequest extends FileBaseRequest { + + @ApiModelProperty(value = "绝对路径") + private String path; + + @ApiModelProperty(value = "用户id") + private Integer uid; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileDownloadRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileDownloadRequest.java new file mode 100644 index 0000000..8dd426a --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileDownloadRequest.java @@ -0,0 +1,19 @@ + +package cn.orionsec.ops.entity.request.sftp; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "下载请求") +public class FileDownloadRequest extends FileBaseRequest { + + @ApiModelProperty(value = "绝对路径") + private List paths; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileListRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileListRequest.java new file mode 100644 index 0000000..4053a38 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileListRequest.java @@ -0,0 +1,17 @@ + +package cn.orionsec.ops.entity.request.sftp; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "文件列表请求") +public class FileListRequest extends FileBaseRequest { + + @ApiModelProperty(value = "绝对路径") + private String path; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileMkdirRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileMkdirRequest.java new file mode 100644 index 0000000..a0b5f5d --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileMkdirRequest.java @@ -0,0 +1,17 @@ + +package cn.orionsec.ops.entity.request.sftp; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "创建文件夹请求") +public class FileMkdirRequest extends FileBaseRequest { + + @ApiModelProperty(value = "绝对路径") + private String path; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileMoveRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileMoveRequest.java new file mode 100644 index 0000000..04990f6 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileMoveRequest.java @@ -0,0 +1,20 @@ + +package cn.orionsec.ops.entity.request.sftp; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "移动文件请求") +public class FileMoveRequest extends FileBaseRequest { + + @ApiModelProperty(value = "绝对路径") + private String source; + + @ApiModelProperty(value = "路径 绝对路径/相对路径 可以包含../") + private String target; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileOpenRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileOpenRequest.java new file mode 100644 index 0000000..ddd5e3a --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileOpenRequest.java @@ -0,0 +1,15 @@ + +package cn.orionsec.ops.entity.request.sftp; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "sftp打开请求") +public class FileOpenRequest { + + @ApiModelProperty(value = "机器id") + private Long machineId; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FilePresentCheckRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FilePresentCheckRequest.java new file mode 100644 index 0000000..d5ed61b --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FilePresentCheckRequest.java @@ -0,0 +1,25 @@ + +package cn.orionsec.ops.entity.request.sftp; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "检查文件是否存在请求") +public class FilePresentCheckRequest extends FileBaseRequest { + + @ApiModelProperty(value = "当前路径") + private String path; + + @ApiModelProperty(value = "文件名称") + private List names; + + @ApiModelProperty(value = "文件大小") + private Long size; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileRemoveRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileRemoveRequest.java new file mode 100644 index 0000000..6ebb258 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileRemoveRequest.java @@ -0,0 +1,19 @@ + +package cn.orionsec.ops.entity.request.sftp; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "删除文件命令") +public class FileRemoveRequest extends FileBaseRequest { + + @ApiModelProperty(value = "绝对路径") + private List paths; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileTouchRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileTouchRequest.java new file mode 100644 index 0000000..52b3929 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileTouchRequest.java @@ -0,0 +1,17 @@ + +package cn.orionsec.ops.entity.request.sftp; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "创建文件请求") +public class FileTouchRequest extends FileBaseRequest { + + @ApiModelProperty(value = "绝对路径") + private String path; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileTruncateRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileTruncateRequest.java new file mode 100644 index 0000000..f5490d1 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileTruncateRequest.java @@ -0,0 +1,17 @@ + +package cn.orionsec.ops.entity.request.sftp; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "截断文件请求") +public class FileTruncateRequest extends FileBaseRequest { + + @ApiModelProperty(value = "绝对路径") + private String path; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileUploadRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileUploadRequest.java new file mode 100644 index 0000000..46efddf --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/sftp/FileUploadRequest.java @@ -0,0 +1,30 @@ + +package cn.orionsec.ops.entity.request.sftp; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "上传文件请求") +public class FileUploadRequest { + + @ApiModelProperty(value = "sessionToken") + private String sessionToken; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "文件token") + private String fileToken; + + @ApiModelProperty(value = "本地文件路径") + private String localPath; + + @ApiModelProperty(value = "远程文件路径") + private String remotePath; + + @ApiModelProperty(value = "文件大小") + private Long size; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/system/ConfigIpListRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/system/ConfigIpListRequest.java new file mode 100644 index 0000000..c6e3637 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/system/ConfigIpListRequest.java @@ -0,0 +1,24 @@ + +package cn.orionsec.ops.entity.request.system; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "配置ip过滤器请求") +public class ConfigIpListRequest { + + @ApiModelProperty(value = "ip白名单") + private String whiteIpList; + + @ApiModelProperty(value = "ip黑名单") + private String blackIpList; + + @ApiModelProperty(value = "是否启用ip过滤器") + private Boolean enableIpFilter; + + @ApiModelProperty(value = "是否启用ip白名单") + private Boolean enableWhiteIpList; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/system/SystemEnvRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/system/SystemEnvRequest.java new file mode 100644 index 0000000..047b3c4 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/system/SystemEnvRequest.java @@ -0,0 +1,39 @@ + +package cn.orionsec.ops.entity.request.system; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "系统环境变量请求") +@SuppressWarnings("ALL") +public class SystemEnvRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "id") + private List idList; + + @ApiModelProperty(value = "key") + private String key; + + @ApiModelProperty(value = "value") + private String value; + + @ApiModelProperty(value = "描述") + private String description; + + /** + * @see cn.orionsec.ops.constant.env.EnvViewType + */ + @ApiModelProperty(value = "视图类型") + private Integer viewType; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/system/SystemFileCleanRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/system/SystemFileCleanRequest.java new file mode 100644 index 0000000..1bf6042 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/system/SystemFileCleanRequest.java @@ -0,0 +1,19 @@ + +package cn.orionsec.ops.entity.request.system; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "文件清理请求") +@SuppressWarnings("ALL") +public class SystemFileCleanRequest { + + /** + * @see cn.orionsec.ops.constant.system.SystemCleanType + */ + @ApiModelProperty(value = "文件清理类型") + private Integer cleanType; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/system/SystemOptionRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/system/SystemOptionRequest.java new file mode 100644 index 0000000..7404cbc --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/system/SystemOptionRequest.java @@ -0,0 +1,22 @@ + +package cn.orionsec.ops.entity.request.system; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "系统配置请求") +@SuppressWarnings("ALL") +public class SystemOptionRequest { + + /** + * @see cn.orionsec.ops.constant.system.SystemConfigKey + */ + @ApiModelProperty(value = "配置项") + private Integer option; + + @ApiModelProperty(value = "配置值") + private String value; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/template/CommandTemplateRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/template/CommandTemplateRequest.java new file mode 100644 index 0000000..bd1f7d0 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/template/CommandTemplateRequest.java @@ -0,0 +1,35 @@ + +package cn.orionsec.ops.entity.request.template; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "命令模板请求") +public class CommandTemplateRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "id") + private List idList; + + @ApiModelProperty(value = "模板名称") + private String name; + + @ApiModelProperty(value = "模板值") + private String value; + + @ApiModelProperty(value = "模板描述") + private String description; + + @ApiModelProperty(value = "是否省略值") + private boolean omitValue; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/upload/BatchUploadRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/upload/BatchUploadRequest.java new file mode 100644 index 0000000..115b857 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/upload/BatchUploadRequest.java @@ -0,0 +1,26 @@ + +package cn.orionsec.ops.entity.request.upload; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "批量上传请求") +public class BatchUploadRequest { + + @ApiModelProperty(value = "文件大小") + private Long size; + + @ApiModelProperty(value = "远程路径") + private String remotePath; + + @ApiModelProperty(value = "机器id") + private List machineIds; + + @ApiModelProperty(value = "文件名称") + private List names; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/user/EventLogRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/user/EventLogRequest.java new file mode 100644 index 0000000..08c2611 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/user/EventLogRequest.java @@ -0,0 +1,67 @@ + +package cn.orionsec.ops.entity.request.user; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "操作日志请求") +@SuppressWarnings("ALL") +public class EventLogRequest extends PageRequest { + + @ApiModelProperty(value = "用户id") + private Long userId; + + @ApiModelProperty(value = "用户名") + private String username; + + /** + * @see cn.orionsec.ops.constant.event.EventClassify + */ + @ApiModelProperty(value = "事件分类") + private Integer classify; + + /** + * @see cn.orionsec.ops.constant.event.EventType + */ + @ApiModelProperty(value = "事件类型") + private Integer type; + + @ApiModelProperty(value = "日志信息") + private String log; + + @ApiModelProperty(value = "日志参数") + private String params; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "是否执行成功 1成功 2失败") + private Integer result; + + @ApiModelProperty(value = "开始时间区间-开始") + private Date rangeStart; + + @ApiModelProperty(value = "开始时间区间-结束") + private Date rangeEnd; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + */ + @ApiModelProperty(value = "只看自己") + private Integer onlyMyself; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + */ + @ApiModelProperty(value = "是否解析ip") + private Integer parserIp; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/user/UserInfoRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/user/UserInfoRequest.java new file mode 100644 index 0000000..d2150a9 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/user/UserInfoRequest.java @@ -0,0 +1,55 @@ + +package cn.orionsec.ops.entity.request.user; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "用户请求") +@SuppressWarnings("ALL") +public class UserInfoRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "id") + private List idList; + + @ApiModelProperty(value = "用户名") + private String username; + + @ApiModelProperty(value = "昵称") + private String nickname; + + @ApiModelProperty(value = "密码") + private String password; + + /** + * @see cn.orionsec.ops.constant.user.RoleType + */ + @ApiModelProperty(value = "角色类型 10管理员 20开发 30运维") + private Integer role; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "用户状态 1启用 2禁用") + private Integer status; + + @ApiModelProperty(value = "联系手机") + private String phone; + + @ApiModelProperty(value = "联系邮箱") + private String email; + + @ApiModelProperty(value = "头像base64") + private String avatar; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/user/UserLoginRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/user/UserLoginRequest.java new file mode 100644 index 0000000..87928a3 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/user/UserLoginRequest.java @@ -0,0 +1,21 @@ + +package cn.orionsec.ops.entity.request.user; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "用户登录请求") +public class UserLoginRequest { + + @ApiModelProperty(value = "用户名") + private String username; + + @ApiModelProperty(value = "密码") + private String password; + + @ApiModelProperty(value = "ip") + private String ip; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/user/UserResetRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/user/UserResetRequest.java new file mode 100644 index 0000000..77431c5 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/user/UserResetRequest.java @@ -0,0 +1,21 @@ + +package cn.orionsec.ops.entity.request.user; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "重置密码请求") +public class UserResetRequest { + + @ApiModelProperty(value = "用户id") + private Long userId; + + @ApiModelProperty(value = "原密码") + private String beforePassword; + + @ApiModelProperty(value = "密码") + private String password; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/webhook/WebhookConfigRequest.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/webhook/WebhookConfigRequest.java new file mode 100644 index 0000000..47139e9 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/request/webhook/WebhookConfigRequest.java @@ -0,0 +1,34 @@ + +package cn.orionsec.ops.entity.request.webhook; + +import cn.orionsec.kit.lang.define.wrapper.PageRequest; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "webhook 配置请求") +@SuppressWarnings("ALL") +public class WebhookConfigRequest extends PageRequest { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "url") + private String url; + + /** + * @see cn.orionsec.ops.constant.webhook.WebhookType + */ + @ApiModelProperty(value = "类型 10: 钉钉机器人") + private Integer type; + + @ApiModelProperty(value = "配置项 json") + private String config; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/alarm/AlarmGroupUserVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/alarm/AlarmGroupUserVO.java new file mode 100644 index 0000000..ef1e4c7 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/alarm/AlarmGroupUserVO.java @@ -0,0 +1,24 @@ + +package cn.orionsec.ops.entity.vo.alarm; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "报警组员响应") +public class AlarmGroupUserVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "用户id") + private Long userId; + + @ApiModelProperty(value = "用户名") + private String username; + + @ApiModelProperty(value = "用户花名") + private String nickname; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/alarm/AlarmGroupVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/alarm/AlarmGroupVO.java new file mode 100644 index 0000000..28e6745 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/alarm/AlarmGroupVO.java @@ -0,0 +1,32 @@ + +package cn.orionsec.ops.entity.vo.alarm; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "报警组响应") +public class AlarmGroupVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "组名称") + private String name; + + @ApiModelProperty(value = "组描述") + private String description; + + @ApiModelProperty(value = "组员名称") + private List groupUsers; + + @ApiModelProperty(value = "报警组员id") + private List userIdList; + + @ApiModelProperty(value = "报警通知方式") + private List notifyIdList; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationActionLogStatisticsVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationActionLogStatisticsVO.java new file mode 100644 index 0000000..e3cfda3 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationActionLogStatisticsVO.java @@ -0,0 +1,31 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "应用构建统计分析操作日志响应") +@SuppressWarnings("ALL") +public class ApplicationActionLogStatisticsVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "操作id") + private Long actionId; + + /** + * @see cn.orionsec.ops.constant.app.ActionStatus + */ + @ApiModelProperty(value = "状态 10未开始 20进行中 30已完成 40执行失败 50已跳过 60已取消") + private Integer status; + + @ApiModelProperty(value = "操作时长 (任何状态)") + private Long used; + + @ApiModelProperty(value = "操作时长 (任何状态)") + private String usedInterval; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationActionLogVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationActionLogVO.java new file mode 100644 index 0000000..285e061 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationActionLogVO.java @@ -0,0 +1,69 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "应用操作日志响应") +@SuppressWarnings("ALL") +public class ApplicationActionLogVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "引用id") + private Long relId; + + @ApiModelProperty(value = "操作id") + private Long actionId; + + @ApiModelProperty(value = "操作名称") + private String actionName; + + /** + * @see cn.orionsec.ops.constant.app.ActionType + */ + @ApiModelProperty(value = "操作类型") + private Integer actionType; + + @ApiModelProperty(value = "操作命令") + private String actionCommand; + + /** + * @see cn.orionsec.ops.constant.app.ActionStatus + */ + @ApiModelProperty(value = "状态 10未开始 20进行中 30已完成 40执行失败 50已跳过 60已取消") + private Integer status; + + @ApiModelProperty(value = "退出码") + private Integer exitCode; + + @ApiModelProperty(value = "开始时间") + private Date startTime; + + @ApiModelProperty(value = "开始时间") + private String startTimeAgo; + + @ApiModelProperty(value = "结束时间") + private Date endTime; + + @ApiModelProperty(value = "结束时间") + private String endTimeAgo; + + @ApiModelProperty(value = "使用毫秒") + private Long used; + + @ApiModelProperty(value = "使用时间") + private String keepTime; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationActionStatisticsVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationActionStatisticsVO.java new file mode 100644 index 0000000..b7bfecc --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationActionStatisticsVO.java @@ -0,0 +1,24 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "应用构建统计分析操作响应") +public class ApplicationActionStatisticsVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "操作名称") + private String name; + + @ApiModelProperty(value = "成功平均操作时长毫秒") + private Long avgUsed; + + @ApiModelProperty(value = "成功平均操作时长") + private String avgUsedInterval; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationActionStatusVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationActionStatusVO.java new file mode 100644 index 0000000..2174120 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationActionStatusVO.java @@ -0,0 +1,45 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "应用执行操作状态响应") +@SuppressWarnings("ALL") +public class ApplicationActionStatusVO { + + @ApiModelProperty(value = "id") + private Long id; + + /** + * @see cn.orionsec.ops.constant.app.ActionStatus + */ + @ApiModelProperty(value = "状态") + private Integer status; + + @ApiModelProperty(value = "执行开始时间") + private Date startTime; + + @ApiModelProperty(value = "执行开始时间") + private String startTimeAgo; + + @ApiModelProperty(value = "执行结束时间") + private Date endTime; + + @ApiModelProperty(value = "执行结束时间") + private String endTimeAgo; + + @ApiModelProperty(value = "使用时间毫秒") + private Long used; + + @ApiModelProperty(value = "使用时间") + private String keepTime; + + @ApiModelProperty(value = "exitCode") + private Integer exitCode; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationActionVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationActionVO.java new file mode 100644 index 0000000..7591e74 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationActionVO.java @@ -0,0 +1,29 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + + +@Data +@ApiModel(value = "应用操作响应") +@SuppressWarnings("ALL") +public class ApplicationActionVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "名称") + private String name; + + /** + * @see cn.orionsec.ops.constant.app.ActionType + */ + @ApiModelProperty(value = "类型") + private Integer type; + + @ApiModelProperty(value = "命令") + private String command; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildReleaseListVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildReleaseListVO.java new file mode 100644 index 0000000..34e25c8 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildReleaseListVO.java @@ -0,0 +1,26 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "发布构建列表响应") +public class ApplicationBuildReleaseListVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "构建序列") + private Integer seq; + + @ApiModelProperty(value = "描述") + private String description; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatisticsChartVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatisticsChartVO.java new file mode 100644 index 0000000..750ab6b --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatisticsChartVO.java @@ -0,0 +1,24 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "构建统计表格响应") +public class ApplicationBuildStatisticsChartVO { + + @ApiModelProperty(value = "日期") + private String date; + + @ApiModelProperty(value = "构建次数") + private Integer buildCount; + + @ApiModelProperty(value = "成功次数") + private Integer successCount; + + @ApiModelProperty(value = "失败次数") + private Integer failureCount; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatisticsMetricsVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatisticsMetricsVO.java new file mode 100644 index 0000000..44d25b7 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatisticsMetricsVO.java @@ -0,0 +1,27 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "构建统计响应") +public class ApplicationBuildStatisticsMetricsVO { + + @ApiModelProperty(value = "构建次数") + private Integer buildCount; + + @ApiModelProperty(value = "成功次数") + private Integer successCount; + + @ApiModelProperty(value = "失败次数") + private Integer failureCount; + + @ApiModelProperty(value = "成功平均构建时长毫秒") + private Long avgUsed; + + @ApiModelProperty(value = "成功平均构建时长") + private String avgUsedInterval; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatisticsMetricsWrapperVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatisticsMetricsWrapperVO.java new file mode 100644 index 0000000..f6bb970 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatisticsMetricsWrapperVO.java @@ -0,0 +1,18 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "构建统计响应") +public class ApplicationBuildStatisticsMetricsWrapperVO { + + @ApiModelProperty(value = "最近构建指标") + private ApplicationBuildStatisticsMetricsVO lately; + + @ApiModelProperty(value = "所有构建指标") + private ApplicationBuildStatisticsMetricsVO all; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatisticsRecordVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatisticsRecordVO.java new file mode 100644 index 0000000..72f886c --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatisticsRecordVO.java @@ -0,0 +1,40 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +@ApiModel(value = "应用构建统计分析操作日志响应") +@SuppressWarnings("ALL") +public class ApplicationBuildStatisticsRecordVO { + + @ApiModelProperty(value = "构建id") + private Long buildId; + + @ApiModelProperty(value = "构建序列") + private Integer seq; + + @ApiModelProperty(value = "构建时间") + private Date buildDate; + + /** + * @see cn.orionsec.ops.constant.app.BuildStatus + */ + @ApiModelProperty(value = "状态 10未开始 20执行中 30已完成 40执行失败 50已取消") + private Integer status; + + @ApiModelProperty(value = "成功构建操作时长毫秒") + private Long used; + + @ApiModelProperty(value = "成功构建操作时长") + private String usedInterval; + + @ApiModelProperty(value = "构建操作") + private List actionLogs; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatisticsViewVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatisticsViewVO.java new file mode 100644 index 0000000..1621261 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatisticsViewVO.java @@ -0,0 +1,26 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "应用构建统计分析操作响应") +public class ApplicationBuildStatisticsViewVO { + + @ApiModelProperty(value = "成功平均构建时长毫秒") + private Long avgUsed; + + @ApiModelProperty(value = "成功平均构建时长") + private String avgUsedInterval; + + @ApiModelProperty(value = "构建操作") + private List actions; + + @ApiModelProperty(value = "构建记录") + private List buildRecordList; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatusVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatusVO.java new file mode 100644 index 0000000..f56fabd --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildStatusVO.java @@ -0,0 +1,46 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +@ApiModel(value = "应用构建状态响应") +@SuppressWarnings("ALL") +public class ApplicationBuildStatusVO { + + @ApiModelProperty(value = "id") + private Long id; + + /** + * @see cn.orionsec.ops.constant.app.BuildStatus + */ + @ApiModelProperty(value = "状态") + private Integer status; + + @ApiModelProperty(value = "构建开始时间") + private Date startTime; + + @ApiModelProperty(value = "构建开始时间") + private String startTimeAgo; + + @ApiModelProperty(value = "构建结束时间") + private Date endTime; + + @ApiModelProperty(value = "构建结束时间") + private String endTimeAgo; + + @ApiModelProperty(value = "使用时间毫秒") + private Long used; + + @ApiModelProperty(value = "使用时间") + private String keepTime; + + @ApiModelProperty(value = "构建操作") + private List actions; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildVO.java new file mode 100644 index 0000000..ad98153 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationBuildVO.java @@ -0,0 +1,94 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +@ApiModel(value = "应用构建详情响应") +@SuppressWarnings("ALL") +public class ApplicationBuildVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "应用id") + private Long appId; + + @ApiModelProperty(value = "应用名称") + private String appName; + + @ApiModelProperty(value = "应用唯一标识") + private String appTag; + + @ApiModelProperty(value = "环境id") + private Long profileId; + + @ApiModelProperty(value = "环境名称") + private String profileName; + + @ApiModelProperty(value = "环境唯一标识") + private String profileTag; + + @ApiModelProperty(value = "构建序列") + private Integer seq; + + @ApiModelProperty(value = "版本仓库id") + private Long repoId; + + @ApiModelProperty(value = "版本仓库名称") + private String repoName; + + @ApiModelProperty(value = "构建分支") + private String branchName; + + @ApiModelProperty(value = "构建提交id") + private String commitId; + + /** + * @see cn.orionsec.ops.constant.app.BuildStatus + */ + @ApiModelProperty(value = "状态 10未开始 20执行中 30已完成 40执行失败 50已取消") + private Integer status; + + @ApiModelProperty(value = "描述") + private String description; + + @ApiModelProperty(value = "创建人id") + private Long createUserId; + + @ApiModelProperty(value = "创建人名称") + private String createUserName; + + @ApiModelProperty(value = "构建开始时间") + private Date startTime; + + @ApiModelProperty(value = "构建开始时间") + private String startTimeAgo; + + @ApiModelProperty(value = "构建结束时间") + private Date endTime; + + @ApiModelProperty(value = "构建结束时间") + private String endTimeAgo; + + @ApiModelProperty(value = "使用时间毫秒") + private Long used; + + @ApiModelProperty(value = "使用时间") + private String keepTime; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + private Date updateTime; + + @ApiModelProperty(value = "构建操作") + private List actions; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationConfigEnvVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationConfigEnvVO.java new file mode 100644 index 0000000..f3237e4 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationConfigEnvVO.java @@ -0,0 +1,52 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "应用配置环境响应") +@SuppressWarnings("ALL") +public class ApplicationConfigEnvVO { + + /** + * @see cn.orionsec.ops.constant.app.ApplicationEnvAttr#BUNDLE_PATH + */ + @ApiModelProperty(value = "构建产物路径") + private String bundlePath; + + /** + * @see cn.orionsec.ops.constant.app.ApplicationEnvAttr#TRANSFER_PATH + */ + @ApiModelProperty(value = "产物传输绝对路径") + private String transferPath; + + /** + * @see cn.orionsec.ops.constant.app.ApplicationEnvAttr#TRANSFER_MODE + */ + @ApiModelProperty(value = "产物传输方式 (sftp/scp)") + private String transferMode; + + /** + * @see cn.orionsec.ops.constant.app.ApplicationEnvAttr#TRANSFER_FILE_TYPE + */ + @ApiModelProperty(value = "产物传输文件类型 (normal/zip)") + private String transferFileType; + + /** + * @see cn.orionsec.ops.constant.app.ApplicationEnvAttr#RELEASE_SERIAL + * @see cn.orionsec.ops.constant.common.SerialType + */ + @ApiModelProperty(value = "发布序列 10串行 20并行") + private Integer releaseSerial; + + /** + * @see cn.orionsec.ops.constant.app.ApplicationEnvAttr#EXCEPTION_HANDLER + * @see cn.orionsec.ops.constant.common.ExceptionHandlerType + * @see cn.orionsec.ops.constant.common.SerialType#SERIAL + */ + @ApiModelProperty(value = "异常处理 10跳过所有 20跳过错误") + private Integer exceptionHandler; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationDetailVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationDetailVO.java new file mode 100644 index 0000000..378e25a --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationDetailVO.java @@ -0,0 +1,44 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "应用详情响应") +public class ApplicationDetailVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "应用唯一标识") + private String tag; + + @ApiModelProperty(value = "描述") + private String description; + + @ApiModelProperty(value = "应用版本仓库id") + private Long repoId; + + @ApiModelProperty(value = "应用版本仓库名称") + private String repoName; + + @ApiModelProperty(value = "配置环境变量") + private ApplicationConfigEnvVO env; + + @ApiModelProperty(value = "构建流程") + private List buildActions; + + @ApiModelProperty(value = "关联机器") + private List releaseMachines; + + @ApiModelProperty(value = "发布流程") + private List releaseActions; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationEnvVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationEnvVO.java new file mode 100644 index 0000000..0438b14 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationEnvVO.java @@ -0,0 +1,43 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "应用环境变量响应") +@SuppressWarnings("ALL") +public class ApplicationEnvVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "应用id") + private Long appId; + + @ApiModelProperty(value = "环境id") + private Long profileId; + + @ApiModelProperty(value = "key") + private String key; + + @ApiModelProperty(value = "value") + private String value; + + /** + * @see cn.orionsec.ops.constant.Const#FORBID_DELETE_CAN + * @see cn.orionsec.ops.constant.Const#FORBID_DELETE_NOT + */ + @ApiModelProperty(value = "是否禁止删除 1可以删除 2禁止删除") + private Integer forbidDelete; + + @ApiModelProperty(value = "描述") + private String description; + + @ApiModelProperty(value = "更新时间") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationInfoVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationInfoVO.java new file mode 100644 index 0000000..d8d9ae8 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationInfoVO.java @@ -0,0 +1,46 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "应用信息响应") +@SuppressWarnings("ALL") +public class ApplicationInfoVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "应用唯一标识") + private String tag; + + @ApiModelProperty(value = "排序") + private Integer sort; + + @ApiModelProperty(value = "应用版本仓库id") + private Long repoId; + + @ApiModelProperty(value = "应用版本仓库名称") + private String repoName; + + @ApiModelProperty(value = "描述") + private String description; + + /** + * @see cn.orionsec.ops.constant.Const#CONFIGURED + * @see cn.orionsec.ops.constant.Const#NOT_CONFIGURED + */ + @ApiModelProperty(value = "是否已经配置 1已配置 2未配置") + private Integer isConfig; + + @ApiModelProperty(value = "应用机器") + private List machines; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationMachineVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationMachineVO.java new file mode 100644 index 0000000..bb68e8e --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationMachineVO.java @@ -0,0 +1,37 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + + +@Data +@ApiModel(value = "应用关联机器信息响应") +public class ApplicationMachineVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "机器名称") + private String machineName; + + @ApiModelProperty(value = "机器主机") + private String machineHost; + + @ApiModelProperty(value = "唯一标识") + private String machineTag; + + @ApiModelProperty(value = "当前版本发布id") + private Long releaseId; + + @ApiModelProperty(value = "当前版本构建id") + private Long buildId; + + @ApiModelProperty(value = "当前版本构建序列") + private Integer buildSeq; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineDetailVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineDetailVO.java new file mode 100644 index 0000000..916f488 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineDetailVO.java @@ -0,0 +1,40 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "应用流水线详情响应") +@SuppressWarnings("ALL") +public class ApplicationPipelineDetailVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "流水线id") + private Long pipelineId; + + @ApiModelProperty(value = "环境id") + private Long profileId; + + @ApiModelProperty(value = "应用id") + private Long appId; + + @ApiModelProperty(value = "应用名称") + private String appName; + + @ApiModelProperty(value = "应用唯一标识") + private String appTag; + + @ApiModelProperty(value = "应用版本仓库id") + private Long repoId; + + /** + * @see cn.orionsec.ops.constant.app.StageType + */ + @ApiModelProperty(value = "阶段类型 10构建 20发布") + private Integer stageType; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineStageConfigVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineStageConfigVO.java new file mode 100644 index 0000000..ad8c3c2 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineStageConfigVO.java @@ -0,0 +1,34 @@ + +package cn.orionsec.ops.entity.vo.app; + +import cn.orionsec.ops.entity.vo.machine.MachineInfoVO; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + + +@Data +@ApiModel(value = "应用操作流水线配置响应") +public class ApplicationPipelineStageConfigVO { + + @ApiModelProperty(value = "分支名称") + private String branchName; + + @ApiModelProperty(value = "提交id") + private String commitId; + + @ApiModelProperty(value = "构建id") + private Long buildId; + + @ApiModelProperty(value = "构建序列") + private Integer buildSeq; + + @ApiModelProperty(value = "发布机器id") + private List machineIdList; + + @ApiModelProperty(value = "发布机器") + private List machineList; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineStatisticsDetailVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineStatisticsDetailVO.java new file mode 100644 index 0000000..f96f3fb --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineStatisticsDetailVO.java @@ -0,0 +1,35 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + + +@Data +@ApiModel(value = "应用流水线详情响应") +@SuppressWarnings("ALL") +public class ApplicationPipelineStatisticsDetailVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "应用id") + private Long appId; + + @ApiModelProperty(value = "应用名称") + private String appName; + + /** + * @see cn.orionsec.ops.constant.app.StageType + */ + @ApiModelProperty(value = "阶段类型 10构建 20发布") + private Integer stageType; + + @ApiModelProperty(value = "成功平均操作时长毫秒") + private Long avgUsed; + + @ApiModelProperty(value = "成平均操作时长") + private String avgUsedInterval; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskDetailStatusVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskDetailStatusVO.java new file mode 100644 index 0000000..b7f64f7 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskDetailStatusVO.java @@ -0,0 +1,48 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "应用流水线任务详情状态响应") +@SuppressWarnings("ALL") +public class ApplicationPipelineTaskDetailStatusVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "流水线任务id") + private Long taskId; + + @ApiModelProperty(value = "relId") + private Long relId; + + /** + * @see cn.orionsec.ops.constant.app.PipelineDetailStatus + */ + @ApiModelProperty(value = "状态 10未开始 20进行中 30已完成 40执行失败 50已跳过 60已终止") + private Integer status; + + @ApiModelProperty(value = "执行开始时间") + private Date startTime; + + @ApiModelProperty(value = "执行开始时间") + private String startTimeAgo; + + @ApiModelProperty(value = "执行结束时间") + private Date endTime; + + @ApiModelProperty(value = "执行结束时间") + private String endTimeAgo; + + @ApiModelProperty(value = "使用时间毫秒") + private Long used; + + @ApiModelProperty(value = "使用时间") + private String keepTime; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskDetailVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskDetailVO.java new file mode 100644 index 0000000..630e1e6 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskDetailVO.java @@ -0,0 +1,66 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "流水线明细详情响应") +@SuppressWarnings("ALL") +public class ApplicationPipelineTaskDetailVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "流水线任务id") + private Long taskId; + + @ApiModelProperty(value = "引用id") + private Long relId; + + @ApiModelProperty(value = "应用id") + private Long appId; + + @ApiModelProperty(value = "应用名称") + private String appName; + + @ApiModelProperty(value = "应用唯一标识") + private String appTag; + + /** + * @see cn.orionsec.ops.constant.app.StageType + */ + @ApiModelProperty(value = "阶段类型 10构建 20发布") + private Integer stageType; + + @ApiModelProperty(value = "阶段操作配置") + private ApplicationPipelineStageConfigVO config; + + /** + * @see cn.orionsec.ops.constant.app.PipelineDetailStatus + */ + @ApiModelProperty(value = "状态 10未开始 20进行中 30已完成 40执行失败 50已跳过 60已终止") + private Integer status; + + @ApiModelProperty(value = "执行开始时间") + private Date startTime; + + @ApiModelProperty(value = "执行开始时间") + private String startTimeAgo; + + @ApiModelProperty(value = "执行结束时间") + private Date endTime; + + @ApiModelProperty(value = "执行结束时间") + private String endTimeAgo; + + @ApiModelProperty(value = "使用时间毫秒") + private Long used; + + @ApiModelProperty(value = "使用时间") + private String keepTime; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskListVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskListVO.java new file mode 100644 index 0000000..b0b29fc --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskListVO.java @@ -0,0 +1,70 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + + +@Data +@ApiModel(value = "流水线明细详情响应") +@SuppressWarnings("ALL") +public class ApplicationPipelineTaskListVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "流水线id") + private Long pipelineId; + + @ApiModelProperty(value = "流水线名称") + private String pipelineName; + + @ApiModelProperty(value = "执行标题") + private String title; + + @ApiModelProperty(value = "执行描述") + private String description; + + /** + * @see cn.orionsec.ops.constant.app.PipelineStatus + */ + @ApiModelProperty(value = "执行状态 10待审核 20审核驳回 30待执行 35待调度 40执行中 50执行完成 60执行停止 70执行失败") + private Integer status; + + @ApiModelProperty(value = "是否是定时执行 10普通执行 20定时执行") + private Integer timedExec; + + @ApiModelProperty(value = "定时执行时间") + private Date timedExecTime; + + @ApiModelProperty(value = "创建人id") + private Long createUserId; + + @ApiModelProperty(value = "创建人名称") + private String createUserName; + + @ApiModelProperty(value = "执行人id") + private Long execUserId; + + @ApiModelProperty(value = "执行人名称") + private String execUserName; + + @ApiModelProperty(value = "执行开始时间") + private Date execStartTime; + + @ApiModelProperty(value = "执行结束时间") + private Date execEndTime; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + + @ApiModelProperty(value = "使用时间毫秒") + private Long used; + + @ApiModelProperty(value = "使用时间") + private String keepTime; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskLogVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskLogVO.java new file mode 100644 index 0000000..ae3a9d7 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskLogVO.java @@ -0,0 +1,45 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "流水线日志响应") +@SuppressWarnings("ALL") +public class ApplicationPipelineTaskLogVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "流水线任务id") + private Long taskId; + + @ApiModelProperty(value = "流水线任务详情id") + private Long taskDetailId; + + /** + * @see cn.orionsec.ops.constant.app.PipelineLogStatus + */ + @ApiModelProperty(value = "日志状态 10创建 20执行 30成功 40失败 50停止 60跳过") + private Integer status; + + /** + * @see cn.orionsec.ops.constant.app.StageType + */ + @ApiModelProperty(value = "阶段类型 10构建 20发布") + private Integer type; + + @ApiModelProperty(value = "日志详情") + private String log; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + + @ApiModelProperty(value = "创建时间") + private String createTimeAgo; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsChartVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsChartVO.java new file mode 100644 index 0000000..2a372b2 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsChartVO.java @@ -0,0 +1,24 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "流水线统计表格响应") +public class ApplicationPipelineTaskStatisticsChartVO { + + @ApiModelProperty(value = "日期") + private String date; + + @ApiModelProperty(value = "执行次数") + private Integer execCount; + + @ApiModelProperty(value = "成功次数") + private Integer successCount; + + @ApiModelProperty(value = "失败次数") + private Integer failureCount; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsDetailVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsDetailVO.java new file mode 100644 index 0000000..8da2ba2 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsDetailVO.java @@ -0,0 +1,40 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "应用流水线统计分析操作日志响应") +@SuppressWarnings("ALL") +public class ApplicationPipelineTaskStatisticsDetailVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "操作id") + private Long detailId; + + @ApiModelProperty(value = "引用id") + private Long relId; + + /** + * @see cn.orionsec.ops.constant.app.StageType + */ + @ApiModelProperty(value = "阶段类型 10构建 20发布") + private Integer stageType; + + /** + * @see cn.orionsec.ops.constant.app.PipelineDetailStatus + */ + @ApiModelProperty(value = "状态 10未开始 20进行中 30已完成 40执行失败 50已跳过 60已终止") + private Integer status; + + @ApiModelProperty(value = "操作时长 (任何状态)") + private Long used; + + @ApiModelProperty(value = "操作时长 (任何状态)") + private String usedInterval; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsMetricsVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsMetricsVO.java new file mode 100644 index 0000000..bcbceb1 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsMetricsVO.java @@ -0,0 +1,27 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "流水线执行统计指标响应") +public class ApplicationPipelineTaskStatisticsMetricsVO { + + @ApiModelProperty(value = "执行次数") + private Integer execCount; + + @ApiModelProperty(value = "成功次数") + private Integer successCount; + + @ApiModelProperty(value = "失败次数") + private Integer failureCount; + + @ApiModelProperty(value = "成功平均发布时长毫秒") + private Long avgUsed; + + @ApiModelProperty(value = "成功平均发布时长") + private String avgUsedInterval; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsMetricsWrapperVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsMetricsWrapperVO.java new file mode 100644 index 0000000..f08f396 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsMetricsWrapperVO.java @@ -0,0 +1,18 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "流水线执行统计指标响应") +public class ApplicationPipelineTaskStatisticsMetricsWrapperVO { + + @ApiModelProperty(value = "最近执行指标") + private ApplicationPipelineTaskStatisticsMetricsVO lately; + + @ApiModelProperty(value = "所有执行指标") + private ApplicationPipelineTaskStatisticsMetricsVO all; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsTaskVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsTaskVO.java new file mode 100644 index 0000000..6605950 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsTaskVO.java @@ -0,0 +1,40 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +@ApiModel(value = "应用流水线统计分析操作日志响应") +@SuppressWarnings("ALL") +public class ApplicationPipelineTaskStatisticsTaskVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "标题") + private String title; + + @ApiModelProperty(value = "执行时间") + private Date execDate; + + /** + * @see cn.orionsec.ops.constant.app.PipelineStatus + */ + @ApiModelProperty(value = "执行状态 10待审核 20审核驳回 30待执行 35待调度 40执行中 50执行完成 60执行停止 70执行失败") + private Integer status; + + @ApiModelProperty(value = "成功执行操作时长毫秒") + private Long used; + + @ApiModelProperty(value = "成功执行操作时长") + private String usedInterval; + + @ApiModelProperty(value = "执行操作") + private List details; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsViewVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsViewVO.java new file mode 100644 index 0000000..a57a500 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatisticsViewVO.java @@ -0,0 +1,26 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "应用流水线统计分析操作响应") +public class ApplicationPipelineTaskStatisticsViewVO { + + @ApiModelProperty(value = "成功平均执行时长毫秒") + private Long avgUsed; + + @ApiModelProperty(value = "成功平均执行时长") + private String avgUsedInterval; + + @ApiModelProperty(value = "流水线操作") + private List details; + + @ApiModelProperty(value = "流水线执行记录") + private List pipelineTaskList; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatusVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatusVO.java new file mode 100644 index 0000000..2ad8975 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskStatusVO.java @@ -0,0 +1,46 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +@ApiModel(value = "应用流水线任务状态响应") +@SuppressWarnings("ALL") +public class ApplicationPipelineTaskStatusVO { + + @ApiModelProperty(value = "id") + private Long id; + + /** + * @see cn.orionsec.ops.constant.app.PipelineStatus + */ + @ApiModelProperty(value = "状态") + private Integer status; + + @ApiModelProperty(value = "开始时间") + private Date startTime; + + @ApiModelProperty(value = "开始时间") + private String startTimeAgo; + + @ApiModelProperty(value = "结束时间") + private Date endTime; + + @ApiModelProperty(value = "结束时间") + private String endTimeAgo; + + @ApiModelProperty(value = "使用时间毫秒") + private Long used; + + @ApiModelProperty(value = "使用时间") + private String keepTime; + + @ApiModelProperty(value = "流水线详情") + private List details; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskVO.java new file mode 100644 index 0000000..f639f0c --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineTaskVO.java @@ -0,0 +1,106 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +@ApiModel(value = "流水线明细详情响应") +@SuppressWarnings("ALL") +public class ApplicationPipelineTaskVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "流水线id") + private Long pipelineId; + + @ApiModelProperty(value = "流水线名称") + private String pipelineName; + + @ApiModelProperty(value = "环境id") + private Long profileId; + + @ApiModelProperty(value = "环境名称") + private String profileName; + + @ApiModelProperty(value = "环境唯一标识") + private String profileTag; + + @ApiModelProperty(value = "执行标题") + private String title; + + @ApiModelProperty(value = "执行描述") + private String description; + + /** + * @see cn.orionsec.ops.constant.app.PipelineStatus + */ + @ApiModelProperty(value = "执行状态 10待审核 20审核驳回 30待执行 35待调度 40执行中 50执行完成 60执行停止 70执行失败") + private Integer status; + + @ApiModelProperty(value = "是否是定时执行 10普通执行 20定时执行") + private Integer timedExec; + + @ApiModelProperty(value = "定时执行时间") + private Date timedExecTime; + + @ApiModelProperty(value = "创建人id") + private Long createUserId; + + @ApiModelProperty(value = "创建人名称") + private String createUserName; + + @ApiModelProperty(value = "审核人id") + private Long auditUserId; + + @ApiModelProperty(value = "审核人名称") + private String auditUserName; + + @ApiModelProperty(value = "审核时间") + private Date auditTime; + + @ApiModelProperty(value = "审核时间") + private String auditTimeAgo; + + @ApiModelProperty(value = "审核备注") + private String auditReason; + + @ApiModelProperty(value = "执行人id") + private Long execUserId; + + @ApiModelProperty(value = "执行人名称") + private String execUserName; + + @ApiModelProperty(value = "执行开始时间") + private Date execStartTime; + + @ApiModelProperty(value = "执行开始时间") + private String execStartTimeAgo; + + @ApiModelProperty(value = "执行结束时间") + private Date execEndTime; + + @ApiModelProperty(value = "执行结束时间") + private String execEndTimeAgo; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + + @ApiModelProperty(value = "创建时间") + private String createTimeAgo; + + @ApiModelProperty(value = "使用时间毫秒") + private Long used; + + @ApiModelProperty(value = "使用时间") + private String keepTime; + + @ApiModelProperty(value = "详情") + private List details; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineVO.java new file mode 100644 index 0000000..d8527b8 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationPipelineVO.java @@ -0,0 +1,36 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +@ApiModel(value = "应用流水线响应") +public class ApplicationPipelineVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "描述") + private String description; + + @ApiModelProperty(value = "环境id") + private Long profileId; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + private Date updateTime; + + @ApiModelProperty(value = "详情") + private List details; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationProfileFastVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationProfileFastVO.java new file mode 100644 index 0000000..78746ec --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationProfileFastVO.java @@ -0,0 +1,21 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "应用环境响应") +public class ApplicationProfileFastVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "唯一标识") + private String tag; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationProfileVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationProfileVO.java new file mode 100644 index 0000000..c65f252 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationProfileVO.java @@ -0,0 +1,32 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "应用环境响应") +@SuppressWarnings("ALL") +public class ApplicationProfileVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "唯一标识") + private String tag; + + @ApiModelProperty(value = "描述") + private String description; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "发布是否需要审核 1需要 2无需") + private Integer releaseAudit; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseDetailVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseDetailVO.java new file mode 100644 index 0000000..c412211 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseDetailVO.java @@ -0,0 +1,139 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +@ApiModel(value = "应用发布响应") +@SuppressWarnings("ALL") +public class ApplicationReleaseDetailVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "发布标题") + private String title; + + @ApiModelProperty(value = "发布描述") + private String description; + + @ApiModelProperty(value = "构建id") + private Long buildId; + + @ApiModelProperty(value = "构建序列") + private Integer buildSeq; + + @ApiModelProperty(value = "应用id") + private Long appId; + + @ApiModelProperty(value = "应用名称") + private String appName; + + @ApiModelProperty(value = "应用唯一标识") + private String appTag; + + @ApiModelProperty(value = "环境id") + private Long profileId; + + @ApiModelProperty(value = "环境名称") + private String profileName; + + @ApiModelProperty(value = "环境唯一标识") + private String profileTag; + + /** + * @see cn.orionsec.ops.constant.app.ReleaseType + */ + @ApiModelProperty(value = "发布类型 10正常发布 20回滚发布") + private Integer type; + + /** + * @see cn.orionsec.ops.constant.app.ReleaseStatus + */ + @ApiModelProperty(value = "发布状态 10待审核 20审核驳回 30待发布 35待调度 40发布中 50发布完成 60发布停止 70发布失败") + private Integer status; + + /** + * @see cn.orionsec.ops.constant.common.SerialType + */ + @ApiModelProperty(value = "发布序列 10串行 20并行") + private Integer serializer; + + /** + * @see cn.orionsec.ops.constant.common.ExceptionHandlerType + */ + @ApiModelProperty(value = "异常处理 10跳过所有 20跳过错误") + private Integer exceptionHandler; + + /** + * @see cn.orionsec.ops.constant.app.TimedType + */ + @ApiModelProperty(value = "是否是定时发布 10普通发布 20定时发布") + private Integer timedRelease; + + @ApiModelProperty(value = "定时发布时间") + private Date timedReleaseTime; + + @ApiModelProperty(value = "定时发布时间") + private String timedReleaseTimeAgo; + + @ApiModelProperty(value = "创建人id") + private Long createUserId; + + @ApiModelProperty(value = "创建人名称") + private String createUserName; + + @ApiModelProperty(value = "审核人id") + private Long auditUserId; + + @ApiModelProperty(value = "审核人名称") + private String auditUserName; + + @ApiModelProperty(value = "审核时间") + private Date auditTime; + + @ApiModelProperty(value = "审核时间") + private String auditTimeAgo; + + @ApiModelProperty(value = "审核备注") + private String auditReason; + + @ApiModelProperty(value = "发布开始时间") + private Date startTime; + + @ApiModelProperty(value = "发布开始时间") + private String startTimeAgo; + + @ApiModelProperty(value = "发布结束时间") + private Date endTime; + + @ApiModelProperty(value = "发布结束时间") + private String endTimeAgo; + + @ApiModelProperty(value = "发布人名称") + private String releaseUserName; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + + @ApiModelProperty(value = "创建时间") + private String createTimeAgo; + + @ApiModelProperty(value = "使用时间毫秒") + private Long used; + + @ApiModelProperty(value = "使用时间") + private String keepTime; + + @ApiModelProperty(value = "操作") + private List actions; + + @ApiModelProperty(value = "发布机器") + private List machines; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseListVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseListVO.java new file mode 100644 index 0000000..9ffa524 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseListVO.java @@ -0,0 +1,103 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +@ApiModel(value = "应用发布响应") +@SuppressWarnings("ALL") +public class ApplicationReleaseListVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "发布标题") + private String title; + + @ApiModelProperty(value = "发布描述") + private String description; + + @ApiModelProperty(value = "构建id") + private Long buildId; + + @ApiModelProperty(value = "构建序列") + private Integer buildSeq; + + @ApiModelProperty(value = "应用id") + private Long appId; + + @ApiModelProperty(value = "应用名称") + private String appName; + + @ApiModelProperty(value = "应用唯一标识") + private String appTag; + + /** + * @see cn.orionsec.ops.constant.app.ReleaseType + */ + @ApiModelProperty(value = "发布类型 10正常发布 20回滚发布") + private Integer type; + + /** + * @see cn.orionsec.ops.constant.app.ReleaseStatus + */ + @ApiModelProperty(value = "发布状态 10待审核 20审核驳回 30待发布 35待调度 40发布中 50发布完成 60发布停止 70发布失败") + private Integer status; + + /** + * @see cn.orionsec.ops.constant.common.SerialType + */ + @ApiModelProperty(value = "发布序列 10串行 20并行") + private Integer serializer; + + /** + * @see cn.orionsec.ops.constant.common.ExceptionHandlerType + */ + @ApiModelProperty(value = "异常处理 10跳过所有 20跳过错误") + private Integer exceptionHandler; + + /** + * @see cn.orionsec.ops.constant.app.TimedType + */ + @ApiModelProperty(value = "是否是定时发布 10普通发布 20定时发布") + private Integer timedRelease; + + @ApiModelProperty(value = "定时发布时间") + private Date timedReleaseTime; + + @ApiModelProperty(value = "创建人名称") + private String createUserName; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + + @ApiModelProperty(value = "审核人名称") + private String auditUserName; + + @ApiModelProperty(value = "审核备注") + private String auditReason; + + @ApiModelProperty(value = "审核时间") + private Date auditTime; + + @ApiModelProperty(value = "发布人") + private String releaseUserName; + + @ApiModelProperty(value = "发布时间") + private Date releaseTime; + + @ApiModelProperty(value = "使用时间毫秒") + private Long used; + + @ApiModelProperty(value = "使用时间") + private String keepTime; + + @ApiModelProperty(value = "发布机器") + private List machines; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseMachineStatusVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseMachineStatusVO.java new file mode 100644 index 0000000..86ef248 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseMachineStatusVO.java @@ -0,0 +1,46 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +@ApiModel(value = "发布机器状态响应") +@SuppressWarnings("ALL") +public class ApplicationReleaseMachineStatusVO { + + @ApiModelProperty(value = "id") + private Long id; + + /** + * @see cn.orionsec.ops.constant.app.ActionStatus + */ + @ApiModelProperty(value = "状态") + private Integer status; + + @ApiModelProperty(value = "发布开始时间") + private Date startTime; + + @ApiModelProperty(value = "发布开始时间") + private String startTimeAgo; + + @ApiModelProperty(value = "发布结束时间") + private Date endTime; + + @ApiModelProperty(value = "发布结束时间") + private String endTimeAgo; + + @ApiModelProperty(value = "使用时间毫秒") + private Long used; + + @ApiModelProperty(value = "使用时间") + private String keepTime; + + @ApiModelProperty(value = "发布操作状态") + private List actions; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseMachineVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseMachineVO.java new file mode 100644 index 0000000..f8a095d --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseMachineVO.java @@ -0,0 +1,61 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +@ApiModel(value = "应用发布机器响应") +@SuppressWarnings("ALL") +public class ApplicationReleaseMachineVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "发布任务id") + private Long releaseId; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "机器名称") + private String machineName; + + @ApiModelProperty(value = "机器唯一标识") + private String machineTag; + + @ApiModelProperty(value = "机器主机") + private String machineHost; + + /** + * @see cn.orionsec.ops.constant.app.ActionType + */ + @ApiModelProperty(value = "状态 10未开始 20进行中 30已完成 40执行失败 50已跳过 60已取消") + private Integer status; + + @ApiModelProperty(value = "开始时间") + private Date startTime; + + @ApiModelProperty(value = "开始时间") + private String startTimeAgo; + + @ApiModelProperty(value = "结束时间") + private Date endTime; + + @ApiModelProperty(value = "结束时间") + private String endTimeAgo; + + @ApiModelProperty(value = "使用时间毫秒") + private Long used; + + @ApiModelProperty(value = "使用时间") + private String keepTime; + + @ApiModelProperty(value = "操作") + private List actions; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsChartVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsChartVO.java new file mode 100644 index 0000000..fc161e4 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsChartVO.java @@ -0,0 +1,24 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "发布统计表格响应") +public class ApplicationReleaseStatisticsChartVO { + + @ApiModelProperty(value = "日期") + private String date; + + @ApiModelProperty(value = "发布次数") + private Integer releaseCount; + + @ApiModelProperty(value = "成功次数") + private Integer successCount; + + @ApiModelProperty(value = "失败次数") + private Integer failureCount; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsMachineVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsMachineVO.java new file mode 100644 index 0000000..51aee7c --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsMachineVO.java @@ -0,0 +1,39 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "应用发布统计机器响应") +@SuppressWarnings("ALL") +public class ApplicationReleaseStatisticsMachineVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "机器名称") + private String machineName; + + /** + * @see cn.orionsec.ops.constant.app.ActionStatus + */ + @ApiModelProperty(value = "状态 10未开始 20进行中 30已完成 40执行失败 50已跳过 60已取消") + private Integer status; + + @ApiModelProperty(value = "机器操作时长毫秒") + private Long used; + + @ApiModelProperty(value = "机器操作时长") + private String usedInterval; + + @ApiModelProperty(value = "发布操作") + private List actionLogs; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsMetricsVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsMetricsVO.java new file mode 100644 index 0000000..5f8fbab --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsMetricsVO.java @@ -0,0 +1,27 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "发布统计响应") +public class ApplicationReleaseStatisticsMetricsVO { + + @ApiModelProperty(value = "发布次数") + private Integer releaseCount; + + @ApiModelProperty(value = "成功次数") + private Integer successCount; + + @ApiModelProperty(value = "失败次数") + private Integer failureCount; + + @ApiModelProperty(value = "成功平均发布时长毫秒") + private Long avgUsed; + + @ApiModelProperty(value = "成功平均发布时长") + private String avgUsedInterval; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsMetricsWrapperVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsMetricsWrapperVO.java new file mode 100644 index 0000000..d86ea15 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsMetricsWrapperVO.java @@ -0,0 +1,18 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "发布统计指标响应") +public class ApplicationReleaseStatisticsMetricsWrapperVO { + + @ApiModelProperty(value = "最近发布指标") + private ApplicationReleaseStatisticsMetricsVO lately; + + @ApiModelProperty(value = "所有发布指标") + private ApplicationReleaseStatisticsMetricsVO all; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsRecordVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsRecordVO.java new file mode 100644 index 0000000..5f14244 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsRecordVO.java @@ -0,0 +1,40 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +@ApiModel(value = "应用发布明细响应") +@SuppressWarnings("ALL") +public class ApplicationReleaseStatisticsRecordVO { + + @ApiModelProperty(value = "发布id") + private Long releaseId; + + @ApiModelProperty(value = "发布标题") + private String releaseTitle; + + @ApiModelProperty(value = "发布时间") + private Date releaseDate; + + /** + * @see cn.orionsec.ops.constant.app.ReleaseStatus + */ + @ApiModelProperty(value = "发布状态 10待审核 20审核驳回 30待发布 35待调度 40发布中 50发布完成 60发布停止 70发布失败") + private Integer status; + + @ApiModelProperty(value = "成功构建操作时长毫秒") + private Long used; + + @ApiModelProperty(value = "成功构建操作时长") + private String usedInterval; + + @ApiModelProperty(value = "发布机器") + private List machines; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsViewVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsViewVO.java new file mode 100644 index 0000000..dd8d18c --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatisticsViewVO.java @@ -0,0 +1,26 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "应用发布统计分析响应") +public class ApplicationReleaseStatisticsViewVO { + + @ApiModelProperty(value = "成功平均发布时长毫秒") + private Long avgUsed; + + @ApiModelProperty(value = "成功平均发布时长") + private String avgUsedInterval; + + @ApiModelProperty(value = "发布明细列表") + private List releaseRecordList; + + @ApiModelProperty(value = "发布操作") + private List actions; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatusVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatusVO.java new file mode 100644 index 0000000..8aa5f38 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationReleaseStatusVO.java @@ -0,0 +1,46 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +@ApiModel(value = "应用发布状态响应") +@SuppressWarnings("ALL") +public class ApplicationReleaseStatusVO { + + @ApiModelProperty(value = "id") + private Long id; + + /** + * @see cn.orionsec.ops.constant.app.ReleaseStatus + */ + @ApiModelProperty(value = "状态") + private Integer status; + + @ApiModelProperty(value = "发布开始时间") + private Date startTime; + + @ApiModelProperty(value = "发布开始时间") + private String startTimeAgo; + + @ApiModelProperty(value = "发布结束时间") + private Date endTime; + + @ApiModelProperty(value = "发布结束时间") + private String endTimeAgo; + + @ApiModelProperty(value = "使用时间毫秒") + private Long used; + + @ApiModelProperty(value = "使用时间") + private String keepTime; + + @ApiModelProperty(value = "机器状态") + private List machines; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationRepositoryBranchVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationRepositoryBranchVO.java new file mode 100644 index 0000000..dea5c8b --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationRepositoryBranchVO.java @@ -0,0 +1,22 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "应用分支信息响应") +@SuppressWarnings("ALL") +public class ApplicationRepositoryBranchVO { + + @ApiModelProperty(value = "名称") + private String name; + + /** + * @see cn.orionsec.ops.constant.Const#IS_DEFAULT + */ + @ApiModelProperty(value = "是否为默认") + private Integer isDefault; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationRepositoryCommitVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationRepositoryCommitVO.java new file mode 100644 index 0000000..542fca8 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationRepositoryCommitVO.java @@ -0,0 +1,29 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "分支提交信息响应") +public class ApplicationRepositoryCommitVO { + + @ApiModelProperty(value = "id") + private String id; + + @ApiModelProperty(value = "提交信息") + private String message; + + @ApiModelProperty(value = "提交人") + private String name; + + @ApiModelProperty(value = "提交时间") + private Date time; + + @ApiModelProperty(value = "提交时间") + private String timeAgo; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationRepositoryInfoVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationRepositoryInfoVO.java new file mode 100644 index 0000000..a7e4ed2 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationRepositoryInfoVO.java @@ -0,0 +1,20 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "应用仓库分支提交信息响应") +public class ApplicationRepositoryInfoVO { + + @ApiModelProperty(value = "分支") + private List branches; + + @ApiModelProperty(value = "提交记录") + private List commits; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationRepositoryVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationRepositoryVO.java new file mode 100644 index 0000000..d7f2339 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/app/ApplicationRepositoryVO.java @@ -0,0 +1,60 @@ + +package cn.orionsec.ops.entity.vo.app; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "应用版本仓库信息响应") +@SuppressWarnings("ALL") +public class ApplicationRepositoryVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "描述") + private String description; + + /** + * @see cn.orionsec.ops.constant.app.RepositoryType + */ + @ApiModelProperty(value = "类型 1git") + private Integer type; + + @ApiModelProperty(value = "url") + private String url; + + @ApiModelProperty(value = "用户名") + private String username; + + /** + * @see cn.orionsec.ops.constant.app.RepositoryStatus + */ + @ApiModelProperty(value = "状态 10未初始化 20初始化中 30正常 40失败") + private Integer status; + + /** + * @see cn.orionsec.ops.constant.app.RepositoryAuthType + */ + @ApiModelProperty(value = "认证类型 10密码 20令牌") + private Integer authType; + + /** + * @see cn.orionsec.ops.constant.app.RepositoryTokenType + */ + @ApiModelProperty(value = "令牌类型 10github 20gitee 30gitlab") + private Integer tokenType; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + + @ApiModelProperty(value = "更新时间") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/data/DataImportCheckRowVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/data/DataImportCheckRowVO.java new file mode 100644 index 0000000..02d5758 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/data/DataImportCheckRowVO.java @@ -0,0 +1,27 @@ + +package cn.orionsec.ops.entity.vo.data; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "数据导入检查响应") +public class DataImportCheckRowVO { + + @ApiModelProperty(value = "索引 0开始") + private Integer index; + + @ApiModelProperty(value = "行号 前端提示") + private Integer row; + + @ApiModelProperty(value = "唯一标识") + private String symbol; + + @ApiModelProperty(value = "非法信息") + private String illegalMessage; + + @ApiModelProperty(value = "数据id 更新操作") + private Long id; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/data/DataImportCheckVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/data/DataImportCheckVO.java new file mode 100644 index 0000000..d4bf5b9 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/data/DataImportCheckVO.java @@ -0,0 +1,26 @@ + +package cn.orionsec.ops.entity.vo.data; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "数据导入检查响应") +public class DataImportCheckVO { + + @ApiModelProperty(value = "无效行") + private List illegalRows; + + @ApiModelProperty(value = "插入行") + private List insertRows; + + @ApiModelProperty(value = "更新行") + private List updateRows; + + @ApiModelProperty(value = "导入token") + private String importToken; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/exec/CommandExecStatusVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/exec/CommandExecStatusVO.java new file mode 100644 index 0000000..519bc5e --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/exec/CommandExecStatusVO.java @@ -0,0 +1,27 @@ + +package cn.orionsec.ops.entity.vo.exec; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "命令执行状态响应") +public class CommandExecStatusVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "exitCode") + private Integer exitCode; + + @ApiModelProperty(value = "状态") + private Integer status; + + @ApiModelProperty(value = "使用时间毫秒") + private Long used; + + @ApiModelProperty(value = "使用时间") + private String keepTime; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/exec/CommandExecVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/exec/CommandExecVO.java new file mode 100644 index 0000000..b718b30 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/exec/CommandExecVO.java @@ -0,0 +1,81 @@ + +package cn.orionsec.ops.entity.vo.exec; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "命令执行响应") +@SuppressWarnings("ALL") +public class CommandExecVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "执行用户id") + private Long userId; + + @ApiModelProperty(value = "执行用户名称") + private String username; + + /** + * @see cn.orionsec.ops.constant.command.ExecType + */ + @ApiModelProperty(value = "类型") + private Integer type; + + /** + * @see cn.orionsec.ops.constant.command.ExecStatus + */ + @ApiModelProperty(value = "状态") + private Integer status; + + @ApiModelProperty(value = "描述") + private String description; + + @ApiModelProperty(value = "执行机器id") + private Long machineId; + + @ApiModelProperty(value = "执行机器名称") + private String machineName; + + @ApiModelProperty(value = "执行机器主机") + private String machineHost; + + @ApiModelProperty(value = "机器唯一标识") + private String machineTag; + + @ApiModelProperty(value = "执行退出码") + private Integer exitCode; + + @ApiModelProperty(value = "执行命令") + private String command; + + @ApiModelProperty(value = "执行开始时间") + private Date startDate; + + @ApiModelProperty(value = "执行开始时间") + private String startDateAgo; + + @ApiModelProperty(value = "执行结束时间") + private Date endDate; + + @ApiModelProperty(value = "执行结束时间") + private String endDateAgo; + + @ApiModelProperty(value = "使用时间毫秒") + private Long used; + + @ApiModelProperty(value = "使用时间") + private String keepTime; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + + @ApiModelProperty(value = "创建时间") + private String createTimeAgo; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/exec/CommandTaskSubmitVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/exec/CommandTaskSubmitVO.java new file mode 100644 index 0000000..9567f1f --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/exec/CommandTaskSubmitVO.java @@ -0,0 +1,24 @@ + +package cn.orionsec.ops.entity.vo.exec; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "命令提交响应") +public class CommandTaskSubmitVO { + + @ApiModelProperty(value = "执行id") + private Long execId; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "机器名称") + private String machineName; + + @ApiModelProperty(value = "机器主机") + private String machineHost; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/history/HistoryValueVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/history/HistoryValueVO.java new file mode 100644 index 0000000..2b97ff3 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/history/HistoryValueVO.java @@ -0,0 +1,42 @@ + +package cn.orionsec.ops.entity.vo.history; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "历史值快照响应") +@SuppressWarnings("ALL") +public class HistoryValueVO { + + @ApiModelProperty(value = "id") + private Long id; + + /** + * @see cn.orionsec.ops.constant.history.HistoryOperator + */ + @ApiModelProperty(value = "操作类型 1新增 2修改 3删除") + private Integer type; + + @ApiModelProperty(value = "原始值") + private String beforeValue; + + @ApiModelProperty(value = "新值") + private String afterValue; + + @ApiModelProperty(value = "修改人id") + private Long updateUserId; + + @ApiModelProperty(value = "修改人用户名") + private String updateUserName; + + @ApiModelProperty(value = "修改时间") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + private String createTimeAgo; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/home/HomeStatisticsCountVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/home/HomeStatisticsCountVO.java new file mode 100644 index 0000000..3fc4e0d --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/home/HomeStatisticsCountVO.java @@ -0,0 +1,24 @@ + +package cn.orionsec.ops.entity.vo.home; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "首页统计数量返回响应") +public class HomeStatisticsCountVO { + + @ApiModelProperty(value = "机器数量") + private Integer machineCount; + + @ApiModelProperty(value = "环境数量") + private Integer profileCount; + + @ApiModelProperty(value = "应用数量") + private Integer appCount; + + @ApiModelProperty(value = "流水线数量") + private Integer pipelineCount; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/home/HomeStatisticsVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/home/HomeStatisticsVO.java new file mode 100644 index 0000000..20ce77c --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/home/HomeStatisticsVO.java @@ -0,0 +1,15 @@ + +package cn.orionsec.ops.entity.vo.home; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "首页统计响应") +public class HomeStatisticsVO { + + @ApiModelProperty(value = "数量统计") + private HomeStatisticsCountVO count; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineAlarmConfigVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineAlarmConfigVO.java new file mode 100644 index 0000000..2a56cc1 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineAlarmConfigVO.java @@ -0,0 +1,28 @@ + +package cn.orionsec.ops.entity.vo.machine; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "机器报警配置响应") +@SuppressWarnings("ALL") +public class MachineAlarmConfigVO { + + /** + * @see cn.orionsec.ops.constant.machine.MachineAlarmType + */ + @ApiModelProperty(value = "报警类型 10: cpu使用率 20: 内存使用率") + private Integer type; + + @ApiModelProperty(value = "报警阈值") + private Double alarmThreshold; + + @ApiModelProperty(value = "触发报警阈值 次") + private Integer triggerThreshold; + + @ApiModelProperty(value = "报警通知沉默时间 分") + private Integer notifySilence; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineAlarmConfigWrapperVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineAlarmConfigWrapperVO.java new file mode 100644 index 0000000..1599519 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineAlarmConfigWrapperVO.java @@ -0,0 +1,20 @@ + +package cn.orionsec.ops.entity.vo.machine; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "机器报警配置包装响应") +public class MachineAlarmConfigWrapperVO { + + @ApiModelProperty(value = "配置") + private List config; + + @ApiModelProperty(value = "报警组id") + private List groupIdList; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineAlarmHistoryVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineAlarmHistoryVO.java new file mode 100644 index 0000000..356c595 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineAlarmHistoryVO.java @@ -0,0 +1,33 @@ + +package cn.orionsec.ops.entity.vo.machine; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "机器报警历史响应") +@SuppressWarnings("ALL") +public class MachineAlarmHistoryVO { + + @ApiModelProperty(value = "id") + private Long id; + + /** + * @see cn.orionsec.ops.constant.machine.MachineAlarmType + */ + @ApiModelProperty(value = "报警类型 10: cpu使用率 20: 内存使用率") + private Integer type; + + @ApiModelProperty(value = "报警值") + private Double alarmValue; + + @ApiModelProperty(value = "报警时间") + private Date alarmTime; + + @ApiModelProperty(value = "报警时间") + private String alarmTimeAgo; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineEnvVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineEnvVO.java new file mode 100644 index 0000000..c69d237 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineEnvVO.java @@ -0,0 +1,44 @@ + +package cn.orionsec.ops.entity.vo.machine; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + + +@Data +@ApiModel(value = "机器环境变量响应") +@SuppressWarnings("ALL") +public class MachineEnvVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "key") + private String key; + + @ApiModelProperty(value = "value") + private String value; + + @ApiModelProperty(value = "描述") + private String description; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + private Date updateTime; + + /** + * @see cn.orionsec.ops.constant.Const#FORBID_DELETE_CAN + * @see cn.orionsec.ops.constant.Const#FORBID_DELETE_NOT + */ + @ApiModelProperty(value = "是否禁止删除 1可以删除 2禁止删除") + private Integer forbidDelete; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineGroupTreeVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineGroupTreeVO.java new file mode 100644 index 0000000..0220706 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineGroupTreeVO.java @@ -0,0 +1,33 @@ + +package cn.orionsec.ops.entity.vo.machine; + +import com.alibaba.fastjson.annotation.JSONField; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "机器分组树响应") +public class MachineGroupTreeVO { + + @JSONField(name = "key") + @ApiModelProperty(value = "id") + private Long id; + + @JSONField(serialize = false) + @ApiModelProperty(value = "父id") + private Long parentId; + + @ApiModelProperty(value = "名称") + private String title; + + @JSONField(serialize = false) + @ApiModelProperty(value = "排序") + private Integer sort; + + @ApiModelProperty(value = "子节点") + private List children; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineInfoVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineInfoVO.java new file mode 100644 index 0000000..56b5bfb --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineInfoVO.java @@ -0,0 +1,77 @@ + +package cn.orionsec.ops.entity.vo.machine; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +@ApiModel(value = "机器信息响应") +@SuppressWarnings("ALL") +public class MachineInfoVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "代理id") + private Long proxyId; + + @ApiModelProperty(value = "代理主机") + private String proxyHost; + + @ApiModelProperty(value = "代理端口") + private Integer proxyPort; + + @ApiModelProperty(value = "代理类型") + private Integer proxyType; + + @ApiModelProperty(value = "密钥id") + private Long keyId; + + @ApiModelProperty(value = "密钥名称") + private String keyName; + + @ApiModelProperty(value = "主机ip") + private String host; + + @ApiModelProperty(value = "ssh端口") + private Integer sshPort; + + @ApiModelProperty(value = "机器名称") + private String name; + + @ApiModelProperty(value = "机器唯一标识") + private String tag; + + @ApiModelProperty(value = "机器描述") + private String description; + + @ApiModelProperty(value = "机器账号") + private String username; + + /** + * @see cn.orionsec.ops.constant.machine.MachineAuthType + */ + @ApiModelProperty(value = "机器认证方式 1: 密码认证 2: 独立密钥") + private Integer authType; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "机器状态 1有效 2无效") + private Integer status; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + private Date updateTime; + + @ApiModelProperty(value = "分组id") + private List groupIdList; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineMonitorVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineMonitorVO.java new file mode 100644 index 0000000..a288ba1 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineMonitorVO.java @@ -0,0 +1,43 @@ + +package cn.orionsec.ops.entity.vo.machine; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "机器代理响应") +@SuppressWarnings("ALL") +public class MachineMonitorVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "机器名称") + private String machineName; + + @ApiModelProperty(value = "机器主机") + private String machineHost; + + /** + * @see cn.orionsec.ops.constant.monitor.MonitorStatus + */ + @ApiModelProperty(value = "监控状态 1未安装 2安装中 3未运行 4运行中") + private Integer status; + + @ApiModelProperty("机器监控 url") + private String url; + + @ApiModelProperty("请求 accessToken") + private String accessToken; + + @ApiModelProperty("当前插件版本") + private String currentVersion; + + @ApiModelProperty("最新插件版本") + private String latestVersion; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineProxyVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineProxyVO.java new file mode 100644 index 0000000..0bb5d2f --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineProxyVO.java @@ -0,0 +1,35 @@ + +package cn.orionsec.ops.entity.vo.machine; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "机器代理响应") +public class MachineProxyVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "主机") + private String host; + + @ApiModelProperty(value = "端口") + private Integer port; + + @ApiModelProperty(value = "代理类型") + private Integer type; + + @ApiModelProperty(value = "用户名") + private String username; + + @ApiModelProperty(value = "描述") + private String description; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineSecretKeyVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineSecretKeyVO.java new file mode 100644 index 0000000..8083482 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineSecretKeyVO.java @@ -0,0 +1,30 @@ + +package cn.orionsec.ops.entity.vo.machine; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "机器密钥响应") +@SuppressWarnings("ALL") +public class MachineSecretKeyVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "密钥名称") + private String name; + + @ApiModelProperty(value = "路径") + private String path; + + @ApiModelProperty(value = "描述") + private String description; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineTerminalLogVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineTerminalLogVO.java new file mode 100644 index 0000000..bbf569e --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineTerminalLogVO.java @@ -0,0 +1,60 @@ + +package cn.orionsec.ops.entity.vo.machine; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "终端日志响应") +@SuppressWarnings("ALL") +public class MachineTerminalLogVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "用户id") + private Long userId; + + @ApiModelProperty(value = "用户名") + private String username; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "机器名称") + private String machineName; + + @ApiModelProperty(value = "机器唯一标识") + private String machineTag; + + @ApiModelProperty(value = "机器host") + private String machineHost; + + @ApiModelProperty(value = "访问token") + private String accessToken; + + @ApiModelProperty(value = "建立连接时间") + private Date connectedTime; + + @ApiModelProperty(value = "断开连接时间") + private Date disconnectedTime; + + @ApiModelProperty(value = "建立连接时间") + private String connectedTimeAgo; + + @ApiModelProperty(value = "断开连接时间") + private String disconnectedTimeAgo; + + /** + * @see cn.orionsec.ops.constant.ws.WsCloseCode + */ + @ApiModelProperty(value = "close code") + private Integer closeCode; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineTerminalManagerVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineTerminalManagerVO.java new file mode 100644 index 0000000..0420976 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineTerminalManagerVO.java @@ -0,0 +1,44 @@ + +package cn.orionsec.ops.entity.vo.machine; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "机器终端管理响应") +public class MachineTerminalManagerVO { + + @ApiModelProperty(value = "token") + private String token; + + @ApiModelProperty(value = "用户id") + private Long userId; + + @ApiModelProperty(value = "username") + private String userName; + + @ApiModelProperty(value = "连接时间") + private Date connectedTime; + + @ApiModelProperty(value = "连接时间") + private String connectedTimeAgo; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "机器名称") + private String machineName; + + @ApiModelProperty(value = "机器host") + private String machineHost; + + @ApiModelProperty(value = "机器唯一标识") + private String machineTag; + + @ApiModelProperty(value = "logId") + private Long logId; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineTerminalVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineTerminalVO.java new file mode 100644 index 0000000..d5177cd --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/MachineTerminalVO.java @@ -0,0 +1,64 @@ + +package cn.orionsec.ops.entity.vo.machine; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "终端终端配置响应") +@SuppressWarnings("ALL") +public class MachineTerminalVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + /** + * @see cn.orionsec.kit.net.host.ssh.TerminalType + */ + @ApiModelProperty(value = "终端类型") + private String terminalType; + + /** + * @see cn.orionsec.ops.constant.terminal.TerminalConst#BACKGROUND_COLOR + */ + @ApiModelProperty(value = "背景色") + private String backgroundColor; + + /** + * @see cn.orionsec.ops.constant.terminal.TerminalConst#FONT_COLOR + */ + @ApiModelProperty(value = "字体颜色") + private String fontColor; + + /** + * @see cn.orionsec.ops.constant.terminal.TerminalConst#FONT_SIZE + */ + @ApiModelProperty(value = "字体大小") + private Integer fontSize; + + /** + * @see cn.orionsec.ops.constant.terminal.TerminalConst#FONT_FAMILY + */ + @ApiModelProperty(value = "字体名称") + private String fontFamily; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "是否开启url link 1开启 2关闭") + private Integer enableWebLink; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + private Date updateTime; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/TerminalAccessVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/TerminalAccessVO.java new file mode 100644 index 0000000..f6edba4 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/TerminalAccessVO.java @@ -0,0 +1,59 @@ + +package cn.orionsec.ops.entity.vo.machine; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "访问终端响应") +@SuppressWarnings("ALL") +public class TerminalAccessVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "主机") + private String host; + + @ApiModelProperty(value = "端口") + private Integer port; + + @ApiModelProperty(value = "机器名称") + private String machineName; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "username") + private String username; + + /** + * @see cn.orionsec.kit.net.host.ssh.TerminalType + */ + @ApiModelProperty(value = "终端类型") + private String terminalType; + + @ApiModelProperty(value = "背景色") + private String backgroundColor; + + @ApiModelProperty(value = "字体颜色") + private String fontColor; + + @ApiModelProperty(value = "字体大小") + private Integer fontSize; + + @ApiModelProperty(value = "字体名称") + private String fontFamily; + + @ApiModelProperty(value = "访问token") + private String accessToken; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "是否开启url link 1开启 2关闭") + private Integer enableWebLink; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/TerminalWatcherVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/TerminalWatcherVO.java new file mode 100644 index 0000000..4988ff2 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/machine/TerminalWatcherVO.java @@ -0,0 +1,34 @@ + +package cn.orionsec.ops.entity.vo.machine; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@ApiModel(value = "访问监视响应") +@SuppressWarnings("ALL") +public class TerminalWatcherVO { + + @ApiModelProperty(value = "token") + private String token; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + */ + @ApiModelProperty(value = "是否只读") + private Integer readonly; + + @ApiModelProperty(value = "cols") + private Integer cols; + + @ApiModelProperty(value = "rows") + private Integer rows; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/message/WebSideMessagePollVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/message/WebSideMessagePollVO.java new file mode 100644 index 0000000..275a560 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/message/WebSideMessagePollVO.java @@ -0,0 +1,23 @@ + +package cn.orionsec.ops.entity.vo.message; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "站内信消息轮询响应") +public class WebSideMessagePollVO { + + @ApiModelProperty(value = "未读数量") + private Integer unreadCount; + + @ApiModelProperty(value = "最大id") + private Long maxId; + + @ApiModelProperty(value = "最新消息") + private List newMessages; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/message/WebSideMessageVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/message/WebSideMessageVO.java new file mode 100644 index 0000000..62d06f4 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/message/WebSideMessageVO.java @@ -0,0 +1,54 @@ + +package cn.orionsec.ops.entity.vo.message; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "站内信响应") +@SuppressWarnings("ALL") +public class WebSideMessageVO { + + @ApiModelProperty(value = "id") + private Long id; + + /** + * @see cn.orionsec.ops.constant.message.MessageClassify + */ + @ApiModelProperty(value = "消息分类") + private Integer classify; + + /** + * @see cn.orionsec.ops.constant.message.MessageType + */ + @ApiModelProperty(value = "消息类型") + private Integer type; + + /** + * @see cn.orionsec.ops.constant.message.ReadStatus + */ + @ApiModelProperty(value = "是否已读 1未读 2已读") + private Integer status; + + @ApiModelProperty(value = "收信人id") + private Long toUserId; + + @ApiModelProperty(value = "收信人名称") + private String toUserName; + + @ApiModelProperty(value = "消息") + private String message; + + @ApiModelProperty(value = "消息关联id") + private Long relId; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + + @ApiModelProperty(value = "创建时间") + private String createTimeAgo; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/CronNextVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/CronNextVO.java new file mode 100644 index 0000000..cfdacf3 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/CronNextVO.java @@ -0,0 +1,21 @@ + +package cn.orionsec.ops.entity.vo.scheduler; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +@ApiModel(value = "cron下次执行时间响应") +public class CronNextVO { + + @ApiModelProperty(value = "是否有效") + private Boolean valid; + + @ApiModelProperty(value = "下次执行时间") + private List next; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskMachineRecordStatisticsVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskMachineRecordStatisticsVO.java new file mode 100644 index 0000000..ccd3405 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskMachineRecordStatisticsVO.java @@ -0,0 +1,33 @@ + +package cn.orionsec.ops.entity.vo.scheduler; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "调度任务机器执行统计响应") +public class SchedulerTaskMachineRecordStatisticsVO { + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "机器名称") + private String machineName; + + @ApiModelProperty(value = "调度次数") + private Integer scheduledCount; + + @ApiModelProperty(value = "成功次数") + private Integer successCount; + + @ApiModelProperty(value = "失败次数") + private Integer failureCount; + + @ApiModelProperty(value = "成功机器平均执行时长毫秒") + private Long avgUsed; + + @ApiModelProperty(value = "成功机器平均执行时长") + private String avgUsedInterval; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskMachineRecordStatusVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskMachineRecordStatusVO.java new file mode 100644 index 0000000..82ec9eb --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskMachineRecordStatusVO.java @@ -0,0 +1,48 @@ + +package cn.orionsec.ops.entity.vo.scheduler; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "调度机器执行状态响应") +@SuppressWarnings("ALL") +public class SchedulerTaskMachineRecordStatusVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "执行记录id") + private Long recordId; + + /** + * @see cn.orionsec.ops.constant.scheduler.SchedulerTaskMachineStatus + */ + @ApiModelProperty(value = "执行状态") + private Integer status; + + @ApiModelProperty(value = "执行开始时间") + private Date startTime; + + @ApiModelProperty(value = "执行开始时间") + private String startTimeAgo; + + @ApiModelProperty(value = "执行结束时间") + private Date endTime; + + @ApiModelProperty(value = "执行结束时间") + private String endTimeAgo; + + @ApiModelProperty(value = "使用时间毫秒") + private Long used; + + @ApiModelProperty(value = "使用时间") + private String keepTime; + + @ApiModelProperty(value = "exitCode") + private Integer exitCode; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskMachineRecordVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskMachineRecordVO.java new file mode 100644 index 0000000..4d4d107 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskMachineRecordVO.java @@ -0,0 +1,63 @@ + +package cn.orionsec.ops.entity.vo.scheduler; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "调度机器执行明细响应") +@SuppressWarnings("ALL") +public class SchedulerTaskMachineRecordVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "明细id") + private Long recordId; + + @ApiModelProperty(value = "执行机器id") + private Long machineId; + + @ApiModelProperty(value = "机器名称") + private String machineName; + + @ApiModelProperty(value = "机器主机") + private String machineHost; + + @ApiModelProperty(value = "机器唯一标识") + private String machineTag; + + @ApiModelProperty(value = "执行命令") + private String command; + + /** + * @see cn.orionsec.ops.constant.scheduler.SchedulerTaskMachineStatus + */ + @ApiModelProperty(value = "执行状态 10待调度 20调度中 30调度成功 40调度失败 50已跳过 60已停止") + private Integer status; + + @ApiModelProperty(value = "退出码") + private Integer exitCode; + + @ApiModelProperty(value = "开始时间") + private Date startTime; + + @ApiModelProperty(value = "开始时间") + private String startTimeAgo; + + @ApiModelProperty(value = "结束时间") + private Date endTime; + + @ApiModelProperty(value = "结束时间") + private String endTimeAgo; + + @ApiModelProperty(value = "使用时间毫秒") + private Long used; + + @ApiModelProperty(value = "使用时间") + private String keepTime; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskRecordStatisticsChartVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskRecordStatisticsChartVO.java new file mode 100644 index 0000000..d211745 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskRecordStatisticsChartVO.java @@ -0,0 +1,24 @@ + +package cn.orionsec.ops.entity.vo.scheduler; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "调度任务统计图表响应") +public class SchedulerTaskRecordStatisticsChartVO { + + @ApiModelProperty(value = "日期") + private String date; + + @ApiModelProperty(value = "调度次数") + private Integer scheduledCount; + + @ApiModelProperty(value = "成功次数") + private Integer successCount; + + @ApiModelProperty(value = "失败次数") + private Integer failureCount; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskRecordStatisticsVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskRecordStatisticsVO.java new file mode 100644 index 0000000..4f7e9ec --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskRecordStatisticsVO.java @@ -0,0 +1,35 @@ + +package cn.orionsec.ops.entity.vo.scheduler; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "调度任务统计响应") +public class SchedulerTaskRecordStatisticsVO { + + @ApiModelProperty(value = "调度次数") + private Integer scheduledCount; + + @ApiModelProperty(value = "成功次数") + private Integer successCount; + + @ApiModelProperty(value = "失败次数") + private Integer failureCount; + + @ApiModelProperty(value = "成功机器平均执行时长毫秒") + private Long avgUsed; + + @ApiModelProperty(value = "成功机器平均执行时长") + private String avgUsedInterval; + + @ApiModelProperty(value = "机器执行统计") + private List machineList; + + @ApiModelProperty(value = "调度图表") + private List charts; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskRecordStatusVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskRecordStatusVO.java new file mode 100644 index 0000000..8e12717 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskRecordStatusVO.java @@ -0,0 +1,46 @@ + +package cn.orionsec.ops.entity.vo.scheduler; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +@ApiModel(value = "调度机器执行状态响应") +@SuppressWarnings("ALL") +public class SchedulerTaskRecordStatusVO { + + @ApiModelProperty(value = "id") + private Long id; + + /** + * @see cn.orionsec.ops.constant.scheduler.SchedulerTaskStatus + */ + @ApiModelProperty(value = "调度状态") + private Integer status; + + @ApiModelProperty(value = "执行开始时间") + private Date startTime; + + @ApiModelProperty(value = "执行开始时间") + private String startTimeAgo; + + @ApiModelProperty(value = "执行结束时间") + private Date endTime; + + @ApiModelProperty(value = "执行结束时间") + private String endTimeAgo; + + @ApiModelProperty(value = "使用时间毫秒") + private Long used; + + @ApiModelProperty(value = "使用时间") + private String keepTime; + + @ApiModelProperty(value = "机器状态") + private List machines; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskRecordVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskRecordVO.java new file mode 100644 index 0000000..f04108b --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskRecordVO.java @@ -0,0 +1,52 @@ + +package cn.orionsec.ops.entity.vo.scheduler; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +@ApiModel(value = "调度任务明细响应") +@SuppressWarnings("ALL") +public class SchedulerTaskRecordVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "任务id") + private Long taskId; + + @ApiModelProperty(value = "任务名称") + private String taskName; + + /** + * @see cn.orionsec.ops.constant.scheduler.SchedulerTaskStatus + */ + @ApiModelProperty(value = "任务状态 10待调度 20调度中 30调度成功 40调度失败 50已停止") + private Integer status; + + @ApiModelProperty(value = "开始时间") + private Date startTime; + + @ApiModelProperty(value = "开始时间") + private String startTimeAgo; + + @ApiModelProperty(value = "结束时间") + private Date endTime; + + @ApiModelProperty(value = "结束时间") + private String endTimeAgo; + + @ApiModelProperty(value = "使用时间毫秒") + private Long used; + + @ApiModelProperty(value = "使用时间") + private String keepTime; + + @ApiModelProperty(value = "调度机器") + private List machines; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskVO.java new file mode 100644 index 0000000..c09ea1b --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/scheduler/SchedulerTaskVO.java @@ -0,0 +1,68 @@ + +package cn.orionsec.ops.entity.vo.scheduler; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + +@Data +@ApiModel(value = "调度任务响应") +@SuppressWarnings("ALL") +public class SchedulerTaskVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "任务名称") + private String name; + + @ApiModelProperty(value = "任务描述") + private String description; + + @ApiModelProperty(value = "执行命令") + private String command; + + @ApiModelProperty(value = "cron表达式") + private String expression; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "启用状态 1启用 2停用") + private Integer enableStatus; + + /** + * @see cn.orionsec.ops.constant.scheduler.SchedulerTaskStatus + */ + @ApiModelProperty(value = "最近状态 10待调度 20调度中 30调度成功 40调度失败 50已停止") + private Integer latelyStatus; + + /** + * @see cn.orionsec.ops.constant.common.SerialType + */ + @ApiModelProperty(value = "调度序列 10串行 20并行") + private Integer serializeType; + + /** + * @see cn.orionsec.ops.constant.common.ExceptionHandlerType + */ + @ApiModelProperty(value = "异常处理 10跳过所有 20跳过错误") + private Integer exceptionHandler; + + @ApiModelProperty(value = "上次调度时间") + private Date latelyScheduleTime; + + @ApiModelProperty(value = "执行机器id") + private List machineIdList; + + @ApiModelProperty(value = "更新时间") + private Date updateTime; + + @ApiModelProperty(value = "下次执行时间") + private List nextTime; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/sftp/FileDetailVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/sftp/FileDetailVO.java new file mode 100644 index 0000000..3b10bad --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/sftp/FileDetailVO.java @@ -0,0 +1,47 @@ + +package cn.orionsec.ops.entity.vo.sftp; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "文件详情响应") +public class FileDetailVO { + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "绝对路径") + private String path; + + @ApiModelProperty(value = "文件大小") + private String size; + + @ApiModelProperty(value = "文件大小(byte)") + private Long sizeByte; + + @ApiModelProperty(value = "属性") + private String attr; + + @ApiModelProperty(value = "10进制表现的8进制权限") + private Integer permission; + + @ApiModelProperty(value = "用户id") + private Integer uid; + + @ApiModelProperty(value = "组id") + private Integer gid; + + @ApiModelProperty(value = "更新时间") + private Date modifyTime; + + @ApiModelProperty(value = "是否为目录") + private Boolean isDir; + + @ApiModelProperty(value = "是否安全") + private Boolean isSafe; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/sftp/FileListVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/sftp/FileListVO.java new file mode 100644 index 0000000..c7580d9 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/sftp/FileListVO.java @@ -0,0 +1,20 @@ + +package cn.orionsec.ops.entity.vo.sftp; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "文件列表响应") +public class FileListVO { + + @ApiModelProperty(value = "当前路径") + private String path; + + @ApiModelProperty(value = "文件") + private List files; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/sftp/FileOpenVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/sftp/FileOpenVO.java new file mode 100644 index 0000000..6ce069e --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/sftp/FileOpenVO.java @@ -0,0 +1,23 @@ + +package cn.orionsec.ops.entity.vo.sftp; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +@ApiModel(value = "打开连接响应") +public class FileOpenVO extends FileListVO { + + @ApiModelProperty(value = "根目录") + private String home; + + @ApiModelProperty(value = "sessionToken") + private String sessionToken; + + @ApiModelProperty(value = "编码格式") + private String charset; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/sftp/FileTransferLogVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/sftp/FileTransferLogVO.java new file mode 100644 index 0000000..5b8c326 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/sftp/FileTransferLogVO.java @@ -0,0 +1,46 @@ + +package cn.orionsec.ops.entity.vo.sftp; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "传输列表响应") +@SuppressWarnings("ALL") +public class FileTransferLogVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "fileToken") + private String fileToken; + + /** + * @see cn.orionsec.ops.constant.sftp.SftpTransferType + */ + @ApiModelProperty(value = "传输类型 10上传 20下载 30传输") + private Integer type; + + @ApiModelProperty(value = "远程文件") + private String remoteFile; + + @ApiModelProperty(value = "当前大小") + private String current; + + @ApiModelProperty(value = "文件大小") + private String size; + + @ApiModelProperty(value = "当前进度") + private Double progress; + + /** + * @see cn.orionsec.ops.constant.sftp.SftpTransferStatus + */ + @ApiModelProperty(value = "传输状态 10未开始 20进行中 30已暂停 40已完成 50已取消 60传输异常") + private Integer status; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/IpListConfigVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/IpListConfigVO.java new file mode 100644 index 0000000..e39f78d --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/IpListConfigVO.java @@ -0,0 +1,30 @@ + +package cn.orionsec.ops.entity.vo.system; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "ip过滤器配置响应") +public class IpListConfigVO { + + @ApiModelProperty(value = "当前ip") + private String currentIp; + + @ApiModelProperty(value = "当前ip位置") + private String ipLocation; + + @ApiModelProperty(value = "ip白名单") + private String whiteIpList; + + @ApiModelProperty(value = "ip黑名单") + private String blackIpList; + + @ApiModelProperty(value = "是否启用ip过滤器") + private Boolean enableIpFilter; + + @ApiModelProperty(value = "是否启用ip白名单") + private Boolean enableWhiteIpList; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/SystemAboutVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/SystemAboutVO.java new file mode 100644 index 0000000..f42fa40 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/SystemAboutVO.java @@ -0,0 +1,37 @@ + +package cn.orionsec.ops.entity.vo.system; + +import io.swagger.annotations.ApiModel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@ApiModel(value = "系统信息响应") +public class SystemAboutVO { + + /** + * orion-kit 版本 + */ + private String orionKitVersion; + + /** + * orion-ops 版本 + */ + private String orionOpsVersion; + + /** + * 作者 + */ + private String author; + + /** + * 作者 + */ + private String authorCn; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/SystemAnalysisVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/SystemAnalysisVO.java new file mode 100644 index 0000000..0759946 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/SystemAnalysisVO.java @@ -0,0 +1,54 @@ + +package cn.orionsec.ops.entity.vo.system; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "系统分析响应") +public class SystemAnalysisVO { + + @ApiModelProperty(value = "临时文件数量") + private Integer tempFileCount; + + @ApiModelProperty(value = "临时文件大小") + private String tempFileSize; + + @ApiModelProperty(value = "日志文件数量") + private Integer logFileCount; + + @ApiModelProperty(value = "日志文件大小") + private String logFileSize; + + @ApiModelProperty(value = "交换文件数量") + private Integer swapFileCount; + + @ApiModelProperty(value = "交换文件大小") + private String swapFileSize; + + @ApiModelProperty(value = "构建产物版本数") + private Integer distVersionCount; + + @ApiModelProperty(value = "构建产物大小") + private String distFileSize; + + @ApiModelProperty(value = "应用仓库版本数") + private Integer repoVersionCount; + + @ApiModelProperty(value = "应用仓库大小") + private String repoVersionFileSize; + + @ApiModelProperty(value = "录屏文件数") + private Integer screenFileCount; + + @ApiModelProperty(value = "录屏文件大小") + private String screenFileSize; + + @ApiModelProperty(value = "文件清理阈值") + private Integer fileCleanThreshold; + + @ApiModelProperty(value = "自动清理文件") + private Boolean autoCleanFile; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/SystemEnvVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/SystemEnvVO.java new file mode 100644 index 0000000..4480222 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/SystemEnvVO.java @@ -0,0 +1,40 @@ + +package cn.orionsec.ops.entity.vo.system; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "系统环境变量响应") +@SuppressWarnings("ALL") +public class SystemEnvVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "key") + private String key; + + @ApiModelProperty(value = "value") + private String value; + + @ApiModelProperty(value = "描述") + private String description; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + + @ApiModelProperty(value = "修改时间") + private Date updateTime; + + /** + * @see cn.orionsec.ops.constant.Const#FORBID_DELETE_CAN + * @see cn.orionsec.ops.constant.Const#FORBID_DELETE_NOT + */ + @ApiModelProperty(value = "是否禁止删除 1可以删除 2禁止删除") + private Integer forbidDelete; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/SystemOptionVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/SystemOptionVO.java new file mode 100644 index 0000000..0a7f317 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/SystemOptionVO.java @@ -0,0 +1,91 @@ + +package cn.orionsec.ops.entity.vo.system; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "系统配置响应") +@SuppressWarnings("ALL") +public class SystemOptionVO { + + /** + * @see cn.orionsec.ops.constant.system.SystemEnvAttr#ENABLE_AUTO_CLEAN_FILE + */ + @ApiModelProperty(value = "是否启用自动清理") + private Boolean autoCleanFile; + + /** + * @see cn.orionsec.ops.constant.system.SystemEnvAttr#FILE_CLEAN_THRESHOLD + */ + @ApiModelProperty(value = "文件清理阈值") + private Integer fileCleanThreshold; + + /** + * @see cn.orionsec.ops.constant.system.SystemEnvAttr#ALLOW_MULTIPLE_LOGIN + */ + @ApiModelProperty(value = "允许多端登录") + private Boolean allowMultipleLogin; + + /** + * @see cn.orionsec.ops.constant.system.SystemEnvAttr#LOGIN_FAILURE_LOCK + */ + @ApiModelProperty(value = "是否启用登录失败锁定") + private Boolean loginFailureLock; + + /** + * @see cn.orionsec.ops.constant.system.SystemEnvAttr#LOGIN_IP_BIND + */ + @ApiModelProperty(value = "是否启用登录IP绑定") + private Boolean loginIpBind; + + /** + * @see cn.orionsec.ops.constant.system.SystemEnvAttr#LOGIN_TOKEN_AUTO_RENEW + */ + @ApiModelProperty(value = "是否启用登录IP绑定") + private Boolean loginTokenAutoRenew; + + /** + * @see cn.orionsec.ops.constant.system.SystemEnvAttr#LOGIN_TOKEN_EXPIRE + */ + @ApiModelProperty(value = "登录凭证有效期") + private Integer loginTokenExpire; + + /** + * @see cn.orionsec.ops.constant.system.SystemEnvAttr#LOGIN_FAILURE_LOCK_THRESHOLD + */ + @ApiModelProperty(value = "登录失败锁定阈值") + private Integer loginFailureLockThreshold; + + /** + * @see cn.orionsec.ops.constant.system.SystemEnvAttr#LOGIN_TOKEN_AUTO_RENEW_THRESHOLD + */ + @ApiModelProperty(value = "登录自动续签阈值") + private Integer loginTokenAutoRenewThreshold; + + /** + * @see cn.orionsec.ops.constant.system.SystemEnvAttr#RESUME_ENABLE_SCHEDULER_TASK + */ + @ApiModelProperty(value = "自动恢复启用的调度任务") + private Boolean resumeEnableSchedulerTask; + + /** + * @see cn.orionsec.ops.constant.system.SystemEnvAttr#TERMINAL_ACTIVE_PUSH_HEARTBEAT + */ + @ApiModelProperty(value = "终端后台主动推送心跳") + private Boolean terminalActivePushHeartbeat; + + /** + * @see cn.orionsec.ops.constant.system.SystemEnvAttr#SFTP_UPLOAD_THRESHOLD + */ + @ApiModelProperty(value = "SFTP 上传文件最大阈值 (MB)") + private Integer sftpUploadThreshold; + + /** + * @see cn.orionsec.ops.constant.system.SystemEnvAttr#STATISTICS_CACHE_EXPIRE + */ + @ApiModelProperty(value = "统计缓存有效时间 (分)") + private Integer statisticsCacheExpire; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/ThreadPoolMetricsVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/ThreadPoolMetricsVO.java new file mode 100644 index 0000000..e51772b --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/system/ThreadPoolMetricsVO.java @@ -0,0 +1,31 @@ + +package cn.orionsec.ops.entity.vo.system; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "线程池指标响应") +@SuppressWarnings("ALL") +public class ThreadPoolMetricsVO { + + /** + * @see cn.orionsec.ops.constant.system.ThreadPoolMetricsType + */ + @ApiModelProperty(value = "type") + private Integer type; + + @ApiModelProperty(value = "活跃线程数") + private Integer activeThreadCount; + + @ApiModelProperty(value = "总任务数") + private Long totalTaskCount; + + @ApiModelProperty(value = "已完成任务数") + private Long completedTaskCount; + + @ApiModelProperty(value = "队列任务数") + private Integer queueSize; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/tail/FileTailConfigVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/tail/FileTailConfigVO.java new file mode 100644 index 0000000..24e82ab --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/tail/FileTailConfigVO.java @@ -0,0 +1,31 @@ + +package cn.orionsec.ops.entity.vo.tail; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "tail配置响应") +@SuppressWarnings("ALL") +public class FileTailConfigVO { + + /** + * @see cn.orionsec.ops.constant.Const#TAIL_OFFSET_LINE + */ + @ApiModelProperty(value = "偏移量") + private Integer offset; + + /** + * @see cn.orionsec.ops.constant.Const#UTF_8 + */ + @ApiModelProperty(value = "文件编码") + private String charset; + + /** + * @see cn.orionsec.ops.constant.machine.MachineEnvAttr#TAIL_DEFAULT_COMMAND + */ + @ApiModelProperty(value = "命令") + private String command; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/tail/FileTailVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/tail/FileTailVO.java new file mode 100644 index 0000000..a1c9cec --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/tail/FileTailVO.java @@ -0,0 +1,73 @@ + +package cn.orionsec.ops.entity.vo.tail; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "文件tail响应") +@SuppressWarnings("ALL") +public class FileTailVO { + + @ApiModelProperty(value = "token") + private String token; + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "机器id") + private Long machineId; + + @ApiModelProperty(value = "机器名称") + private String machineName; + + @ApiModelProperty(value = "机器host") + private String machineHost; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "机器状态 1有效 2无效") + private Integer machineStatus; + + @ApiModelProperty(value = "文件路径") + private String path; + + @ApiModelProperty(value = "文件名称") + private String fileName; + + /** + * @see cn.orionsec.ops.constant.Const#TAIL_OFFSET_LINE + */ + @ApiModelProperty(value = "偏移量") + private Integer offset; + + /** + * @see cn.orionsec.ops.constant.Const#UTF_8 + */ + @ApiModelProperty(value = "编码集") + private String charset; + + @ApiModelProperty(value = "命令") + private String command; + + /** + * @see cn.orionsec.ops.constant.tail.FileTailMode + */ + @ApiModelProperty(value = "宿主机文件追踪类型 tracker/tail") + private String tailMode; + + @ApiModelProperty(value = "更新时间") + private Date updateTime; + + @ApiModelProperty(value = "更新时间") + private String updateTimeAgo; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/template/CommandTemplateVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/template/CommandTemplateVO.java new file mode 100644 index 0000000..cfe6fa9 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/template/CommandTemplateVO.java @@ -0,0 +1,50 @@ + +package cn.orionsec.ops.entity.vo.template; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "命令模板响应") +public class CommandTemplateVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "模板名称") + private String name; + + @ApiModelProperty(value = "模板值") + private String value; + + @ApiModelProperty(value = "命令描述") + private String description; + + @ApiModelProperty(value = "创建用户id") + private Long createUserId; + + @ApiModelProperty(value = "创建用户名") + private String createUserName; + + @ApiModelProperty(value = "修改用户id") + private Long updateUserId; + + @ApiModelProperty(value = "修改用户名") + private String updateUserName; + + @ApiModelProperty(value = "创建时间") + private Date createTime; + + @ApiModelProperty(value = "创建时间") + private String createTimeAgo; + + @ApiModelProperty(value = "修改时间") + private Date updateTime; + + @ApiModelProperty(value = "修改时间") + private String updateTimeAgo; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/upload/BatchUploadCheckFileVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/upload/BatchUploadCheckFileVO.java new file mode 100644 index 0000000..5444fa7 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/upload/BatchUploadCheckFileVO.java @@ -0,0 +1,26 @@ + +package cn.orionsec.ops.entity.vo.upload; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "批量上传文件检查文件响应") +public class BatchUploadCheckFileVO { + + @ApiModelProperty(value = "机器id") + private Long id; + + @ApiModelProperty(value = "机器名称") + private String name; + + @ApiModelProperty(value = "机器主机") + private String host; + + @ApiModelProperty(value = "已存在的文件") + private List presentFiles; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/upload/BatchUploadCheckMachineVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/upload/BatchUploadCheckMachineVO.java new file mode 100644 index 0000000..478318f --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/upload/BatchUploadCheckMachineVO.java @@ -0,0 +1,21 @@ + +package cn.orionsec.ops.entity.vo.upload; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "批量上传文件检查机器响应") +public class BatchUploadCheckMachineVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "主机") + private String host; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/upload/BatchUploadCheckVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/upload/BatchUploadCheckVO.java new file mode 100644 index 0000000..7b12300 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/upload/BatchUploadCheckVO.java @@ -0,0 +1,27 @@ + +package cn.orionsec.ops.entity.vo.upload; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; +import java.util.Set; + +@Data +@ApiModel(value = "批量上传文件检查响应") +public class BatchUploadCheckVO { + + @ApiModelProperty(value = "可连接的机器id") + private Set connectedMachineIdList; + + @ApiModelProperty(value = "可以连接的机器") + private List connectedMachines; + + @ApiModelProperty(value = "无法连接的机器") + private List notConnectedMachines; + + @ApiModelProperty(value = "机器已存在的文件") + private List machinePresentFiles; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/upload/BatchUploadTokenVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/upload/BatchUploadTokenVO.java new file mode 100644 index 0000000..d07dbd2 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/upload/BatchUploadTokenVO.java @@ -0,0 +1,18 @@ + +package cn.orionsec.ops.entity.vo.upload; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "批量上传获取token响应") +public class BatchUploadTokenVO { + + @ApiModelProperty(value = "accessToken") + private String accessToken; + + @ApiModelProperty(value = "notifyToken") + private String notifyToken; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/user/UserEventLogVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/user/UserEventLogVO.java new file mode 100644 index 0000000..b7c9c4d --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/user/UserEventLogVO.java @@ -0,0 +1,55 @@ + +package cn.orionsec.ops.entity.vo.user; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "操作日志响应") +@SuppressWarnings("ALL") +public class UserEventLogVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "用户id") + private Long userId; + + @ApiModelProperty(value = "用户名") + private String username; + + @ApiModelProperty(value = "事件分类") + private Integer classify; + + @ApiModelProperty(value = "事件类型") + private Integer type; + + @ApiModelProperty(value = "日志信息") + private String log; + + @ApiModelProperty(value = "日志参数") + private String params; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "是否执行成功 1成功 2失败") + private Integer result; + + @ApiModelProperty(value = "操作时间") + private Date createTime; + + @ApiModelProperty(value = "操作时间") + private String createTimeAgo; + + @ApiModelProperty(value = "操作ip") + private String ip; + + @ApiModelProperty(value = "操作ip位置") + private String ipLocation; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/user/UserInfoVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/user/UserInfoVO.java new file mode 100644 index 0000000..8a941fc --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/user/UserInfoVO.java @@ -0,0 +1,59 @@ + +package cn.orionsec.ops.entity.vo.user; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "用户信息响应") +@SuppressWarnings("ALL") +public class UserInfoVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "用户名") + private String username; + + @ApiModelProperty(value = "名称") + private String nickname; + + /** + * @see cn.orionsec.ops.constant.user.RoleType + */ + @ApiModelProperty(value = "角色类型 10管理员 20开发 30运维") + private Integer role; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "用户状态 1启用 2停用") + private Integer status; + + /** + * @see cn.orionsec.ops.constant.Const#ENABLE + * @see cn.orionsec.ops.constant.Const#DISABLE + */ + @ApiModelProperty(value = "锁定状态 1正常 2锁定") + private Integer locked; + + @ApiModelProperty(value = "手机号") + private String phone; + + @ApiModelProperty(value = "邮箱") + private String email; + + @ApiModelProperty(value = "最后一次登录时间") + private Date lastLoginTime; + + @ApiModelProperty(value = "最后登录时间") + private String lastLoginAgo; + + @ApiModelProperty(value = "头像base64") + private String avatar; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/user/UserLoginVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/user/UserLoginVO.java new file mode 100644 index 0000000..f839d04 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/user/UserLoginVO.java @@ -0,0 +1,31 @@ + +package cn.orionsec.ops.entity.vo.user; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "用户登录响应") +@SuppressWarnings("ALL") +public class UserLoginVO { + + @ApiModelProperty(value = "token") + private String token; + + @ApiModelProperty(value = "用户id") + private Long userId; + + @ApiModelProperty(value = "用户名") + private String username; + + @ApiModelProperty(value = "昵称") + private String nickname; + + /** + * @see cn.orionsec.ops.constant.user.RoleType + */ + @ApiModelProperty(value = "角色类型") + private Integer roleType; + +} diff --git a/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/webhook/WebhookConfigVO.java b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/webhook/WebhookConfigVO.java new file mode 100644 index 0000000..e33c078 --- /dev/null +++ b/orion-ops-api/orion-ops-model/src/main/java/cn/orionsec/ops/entity/vo/webhook/WebhookConfigVO.java @@ -0,0 +1,31 @@ + +package cn.orionsec.ops.entity.vo.webhook; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "webhook配置响应") +@SuppressWarnings("ALL") +public class WebhookConfigVO { + + @ApiModelProperty(value = "id") + private Long id; + + @ApiModelProperty(value = "名称") + private String name; + + @ApiModelProperty(value = "url") + private String url; + + /** + * @see cn.orionsec.ops.constant.webhook.WebhookType + */ + @ApiModelProperty(value = "类型 10: 钉钉机器人") + private Integer type; + + @ApiModelProperty(value = "配置项 json") + private String config; + +} diff --git a/orion-ops-api/orion-ops-runner/pom.xml b/orion-ops-api/orion-ops-runner/pom.xml new file mode 100644 index 0000000..9297448 --- /dev/null +++ b/orion-ops-api/orion-ops-runner/pom.xml @@ -0,0 +1,23 @@ + + + + orion-ops-api + cn.orionsec.ops + 1.3.1 + + + orion-ops-runner + orion-ops-runner + 4.0.0 + + + + + cn.orionsec.ops + orion-ops-service + ${project.version} + + + + diff --git a/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/AppBuildStatusCleanRunner.java b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/AppBuildStatusCleanRunner.java new file mode 100644 index 0000000..b1684be --- /dev/null +++ b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/AppBuildStatusCleanRunner.java @@ -0,0 +1,54 @@ + +package cn.orionsec.ops.runner; + +import cn.orionsec.ops.constant.app.ActionStatus; +import cn.orionsec.ops.constant.app.BuildStatus; +import cn.orionsec.ops.constant.app.StageType; +import cn.orionsec.ops.dao.ApplicationActionLogDAO; +import cn.orionsec.ops.dao.ApplicationBuildDAO; +import cn.orionsec.ops.entity.domain.ApplicationActionLogDO; +import cn.orionsec.ops.entity.domain.ApplicationBuildDO; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Date; + +@Component +@Order(2300) +@Slf4j +public class AppBuildStatusCleanRunner implements CommandLineRunner { + + @Resource + private ApplicationBuildDAO applicationBuildDAO; + + @Resource + private ApplicationActionLogDAO applicationActionLogDAO; + + @Override + public void run(String... args) { + log.info("重置应用构建状态-开始"); + // 更新构建状态 + Wrapper buildWrapper = new LambdaQueryWrapper() + .in(ApplicationBuildDO::getBuildStatus, BuildStatus.WAIT.getStatus(), BuildStatus.RUNNABLE.getStatus()); + ApplicationBuildDO updateBuild = new ApplicationBuildDO(); + updateBuild.setBuildStatus(BuildStatus.TERMINATED.getStatus()); + updateBuild.setUpdateTime(new Date()); + applicationBuildDAO.update(updateBuild, buildWrapper); + + // 更新操作状态 + LambdaQueryWrapper actionWrapper = new LambdaQueryWrapper() + .eq(ApplicationActionLogDO::getStageType, StageType.BUILD.getType()) + .in(ApplicationActionLogDO::getRunStatus, ActionStatus.WAIT.getStatus(), ActionStatus.RUNNABLE.getStatus()); + ApplicationActionLogDO updateAction = new ApplicationActionLogDO(); + updateAction.setRunStatus(ActionStatus.TERMINATED.getStatus()); + updateAction.setUpdateTime(new Date()); + applicationActionLogDAO.update(updateAction, actionWrapper); + log.info("重置应用构建状态-结束"); + } + +} diff --git a/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/AppPipelineTaskRunner.java b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/AppPipelineTaskRunner.java new file mode 100644 index 0000000..53909d5 --- /dev/null +++ b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/AppPipelineTaskRunner.java @@ -0,0 +1,105 @@ + +package cn.orionsec.ops.runner; + +import cn.orionsec.ops.constant.app.PipelineDetailStatus; +import cn.orionsec.ops.constant.app.PipelineStatus; +import cn.orionsec.ops.dao.ApplicationPipelineTaskDAO; +import cn.orionsec.ops.dao.ApplicationPipelineTaskDetailDAO; +import cn.orionsec.ops.entity.domain.ApplicationPipelineTaskDO; +import cn.orionsec.ops.entity.domain.ApplicationPipelineTaskDetailDO; +import cn.orionsec.ops.service.api.ApplicationPipelineTaskDetailService; +import cn.orionsec.ops.task.TaskRegister; +import cn.orionsec.ops.task.TaskType; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +@Component +@Order(3400) +@Slf4j +public class AppPipelineTaskRunner implements CommandLineRunner { + + @Resource + private ApplicationPipelineTaskDAO applicationPipelineTaskDAO; + + @Resource + private ApplicationPipelineTaskDetailDAO applicationPipelineTaskDetailDAO; + + @Resource + private ApplicationPipelineTaskDetailService applicationPipelineTaskDetailService; + + @Resource + private TaskRegister taskRegister; + + @Override + public void run(String... args) { + log.info("流水线任务初始化-开始"); + // 更新执行任务状态 + this.updateTaskStatus(); + // 自动恢复任务 + this.autoResumeTask(); + log.info("流水线任务初始化-结束"); + } + + /** + * 更新任务执行状态 + */ + private void updateTaskStatus() { + // 查询任务 + Wrapper taskWrapper = new LambdaQueryWrapper() + .in(ApplicationPipelineTaskDO::getExecStatus, PipelineStatus.RUNNABLE.getStatus()); + List taskList = applicationPipelineTaskDAO.selectList(taskWrapper); + if (taskList.isEmpty()) { + return; + } + List taskIdList = taskList.stream() + .map(ApplicationPipelineTaskDO::getId) + .collect(Collectors.toList()); + // 更新任务状态 + ApplicationPipelineTaskDO updateTask = new ApplicationPipelineTaskDO(); + updateTask.setExecStatus(PipelineStatus.TERMINATED.getStatus()); + updateTask.setUpdateTime(new Date()); + applicationPipelineTaskDAO.update(updateTask, taskWrapper); + log.info("流水线任务初始化-重置任务状态 {}", taskIdList); + // 查询任务明细 + List details = applicationPipelineTaskDetailService.selectTaskDetails(taskIdList); + // 更新明细状态 + for (ApplicationPipelineTaskDetailDO detail : details) { + ApplicationPipelineTaskDetailDO updateDetail = new ApplicationPipelineTaskDetailDO(); + updateDetail.setId(detail.getId()); + updateDetail.setUpdateTime(new Date()); + switch (PipelineDetailStatus.of(detail.getExecStatus())) { + case WAIT: + case RUNNABLE: + updateDetail.setExecStatus(PipelineDetailStatus.TERMINATED.getStatus()); + break; + default: + break; + } + applicationPipelineTaskDetailDAO.updateById(updateDetail); + } + } + + /** + * 自动恢复任务 + */ + private void autoResumeTask() { + Wrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationPipelineTaskDO::getExecStatus, PipelineStatus.WAIT_SCHEDULE.getStatus()); + List taskList = applicationPipelineTaskDAO.selectList(wrapper); + for (ApplicationPipelineTaskDO task : taskList) { + Long id = task.getId(); + log.info("重新加载流水线任务-提交 id: {}", id); + taskRegister.submit(TaskType.PIPELINE, task.getTimedExecTime(), id); + } + } + +} diff --git a/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/AppReleaseTimedTaskRestoreRunner.java b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/AppReleaseTimedTaskRestoreRunner.java new file mode 100644 index 0000000..cb3e02c --- /dev/null +++ b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/AppReleaseTimedTaskRestoreRunner.java @@ -0,0 +1,44 @@ + +package cn.orionsec.ops.runner; + +import cn.orionsec.ops.constant.app.ReleaseStatus; +import cn.orionsec.ops.dao.ApplicationReleaseDAO; +import cn.orionsec.ops.entity.domain.ApplicationReleaseDO; +import cn.orionsec.ops.task.TaskRegister; +import cn.orionsec.ops.task.TaskType; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.List; + +@Component +@Order(3100) +@Slf4j +public class AppReleaseTimedTaskRestoreRunner implements CommandLineRunner { + + @Resource + private ApplicationReleaseDAO applicationReleaseDAO; + + @Resource + private TaskRegister register; + + @Override + public void run(String... args) { + log.info("重新加载发布任务-开始"); + Wrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationReleaseDO::getReleaseStatus, ReleaseStatus.WAIT_SCHEDULE.getStatus()); + List releaseList = applicationReleaseDAO.selectList(wrapper); + for (ApplicationReleaseDO release : releaseList) { + Long id = release.getId(); + log.info("重新加载发布任务-提交 releaseId: {}", id); + register.submit(TaskType.RELEASE, release.getTimedReleaseTime(), id); + } + log.info("重新加载发布任务-结束"); + } + +} diff --git a/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/AppRepositoryStatusCleanRunner.java b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/AppRepositoryStatusCleanRunner.java new file mode 100644 index 0000000..da6a79d --- /dev/null +++ b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/AppRepositoryStatusCleanRunner.java @@ -0,0 +1,81 @@ + +package cn.orionsec.ops.runner; + +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.ops.constant.app.RepositoryStatus; +import cn.orionsec.ops.dao.ApplicationRepositoryDAO; +import cn.orionsec.ops.entity.domain.ApplicationRepositoryDO; +import cn.orionsec.ops.utils.Utils; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.io.File; +import java.util.List; + +@Component +@Order(2500) +@Slf4j +public class AppRepositoryStatusCleanRunner implements CommandLineRunner { + + @Resource + private ApplicationRepositoryDAO applicationRepositoryDAO; + + @Override + public void run(String... args) { + log.info("重置版本仓库状态-开始"); + // 清空初始化中的数据 + this.cleanInitializing(); + // 检查已初始化的数据 + this.checkFinished(); + log.info("重置版本仓库状态-结束"); + } + + /** + * 清空初始化中的数据 + */ + private void cleanInitializing() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationRepositoryDO::getRepoStatus, RepositoryStatus.INITIALIZING.getStatus()); + List repoList = applicationRepositoryDAO.selectList(wrapper); + for (ApplicationRepositoryDO repo : repoList) { + Long id = repo.getId(); + // 更新状态 + ApplicationRepositoryDO update = new ApplicationRepositoryDO(); + update.setId(id); + update.setRepoStatus(RepositoryStatus.UNINITIALIZED.getStatus()); + applicationRepositoryDAO.updateById(update); + // 删除文件夹 + File clonePath = new File(Utils.getRepositoryEventDir(id)); + Files1.delete(clonePath); + log.info("重置版本仓库状态-重置 id: {}, clonePath: {}", id, clonePath); + } + } + + /** + * 检查已初始化的数据 + */ + private void checkFinished() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationRepositoryDO::getRepoStatus, RepositoryStatus.OK.getStatus()); + List repoList = applicationRepositoryDAO.selectList(wrapper); + for (ApplicationRepositoryDO repo : repoList) { + // 检查是否存在 + Long id = repo.getId(); + File clonePath = new File(Utils.getRepositoryEventDir(id)); + if (Files1.isDirectory(clonePath)) { + continue; + } + // 更新状态 + ApplicationRepositoryDO update = new ApplicationRepositoryDO(); + update.setId(id); + update.setRepoStatus(RepositoryStatus.UNINITIALIZED.getStatus()); + applicationRepositoryDAO.updateById(update); + log.info("重置版本仓库状态-重置 id: {}, clonePath: {}", id, clonePath); + } + } + +} diff --git a/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/ApplicationStartArgsRunner.java b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/ApplicationStartArgsRunner.java new file mode 100644 index 0000000..e84927e --- /dev/null +++ b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/ApplicationStartArgsRunner.java @@ -0,0 +1,89 @@ + +package cn.orionsec.ops.runner; + +import cn.orionsec.ops.constant.common.EnableType; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.service.api.SystemService; +import cn.orionsec.ops.service.api.UserService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Arrays; + +@Component +@Order(4000) +@Slf4j +public class ApplicationStartArgsRunner implements CommandLineRunner { + + /** + * 关闭ip过滤器 + */ + private static final String DISABLE_IP_FILTER = "--disable-ip-filter"; + + /** + * 自动生成默认管理员账号 + */ + private static final String GENERATOR_ADMIN = "--generator-admin"; + + /** + * 重置默认管理员密码 + */ + private static final String RESET_ADMIN = "--reset-admin"; + + @Resource + private SystemService systemService; + + @Resource + private UserService userService; + + @Override + public void run(String... args) { + log.info("应用启动参数: {}", Arrays.toString(args)); + for (String arg : args) { + switch (arg) { + case DISABLE_IP_FILTER: + // 关闭ip过滤器 + this.disableIpFilter(); + break; + case GENERATOR_ADMIN: + // 生成默认管理员账号 + this.generatorDefaultAdminUser(); + break; + case RESET_ADMIN: + // 重置默认管理员密码 + this.resetDefaultAdminUser(); + break; + default: + break; + } + } + } + + /** + * 关闭ip过滤器 + */ + private void disableIpFilter() { + systemService.updateSystemOption(SystemEnvAttr.ENABLE_IP_FILTER, EnableType.DISABLED.getLabel()); + log.info("启动参数-IP过滤器已关闭"); + } + + /** + * 生成默认管理员账号 + */ + private void generatorDefaultAdminUser() { + userService.generatorDefaultAdminUser(); + log.info("启动参数-默认管理员用户已初始化"); + } + + /** + * 重置默认管理员密码 + */ + private void resetDefaultAdminUser() { + userService.resetDefaultAdminUserPassword(); + log.info("启动参数-默认管理员密码已重置"); + } + +} diff --git a/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/CacheKeyCleanRunner.java b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/CacheKeyCleanRunner.java new file mode 100644 index 0000000..d04bf14 --- /dev/null +++ b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/CacheKeyCleanRunner.java @@ -0,0 +1,64 @@ + +package cn.orionsec.ops.runner; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.KeyConst; +import cn.orionsec.ops.utils.RedisUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.List; + +@Component +@Order(1200) +@Slf4j +public class CacheKeyCleanRunner implements CommandLineRunner { + + @Resource + private RedisTemplate redisTemplate; + + @Override + public void run(String... args) { + log.info("重启清除缓存-开始"); + List scanKeys = Lists.of( + // terminal访问token + Strings.format(KeyConst.TERMINAL_ACCESS_TOKEN, "*"), + // terminal监视token + Strings.format(KeyConst.TERMINAL_WATCHER_TOKEN, "*"), + // 文件tail访问token + Strings.format(KeyConst.FILE_TAIL_ACCESS_TOKEN, "*"), + // 文件下载token + Strings.format(KeyConst.FILE_DOWNLOAD_TOKEN, "*"), + // sftp会话token + Strings.format(KeyConst.SFTP_SESSION_TOKEN, "*"), + // sftp上传请求token + Strings.format(KeyConst.SFTP_UPLOAD_ACCESS_TOKEN, "*"), + // 首页统计key + Strings.format(KeyConst.HOME_STATISTICS_COUNT_KEY, "*"), + // 调度统计key + Strings.format(KeyConst.SCHEDULER_TASK_STATISTICS_KEY, "*"), + // 环境缓存key + KeyConst.DATA_PROFILE_KEY, + // 数据导入缓存key + Strings.format(KeyConst.DATA_IMPORT_TOKEN, "*", "*"), + // 机器分组数据key + KeyConst.MACHINE_GROUP_DATA_KEY, + // 机器分组关联key + KeyConst.MACHINE_GROUP_REL_KEY + ); + // 查询删除缓存key + scanKeys.stream() + .map(key -> RedisUtils.scanKeys(redisTemplate, key, Const.N_10000)) + .filter(Lists::isNotEmpty) + .peek(keys -> keys.forEach(key -> log.info("重启清除缓存-处理 key: {}", key))) + .forEach(keys -> redisTemplate.delete(keys)); + log.info("重启清除缓存-结束"); + } + +} diff --git a/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/CommandExecStatusCleanRunner.java b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/CommandExecStatusCleanRunner.java new file mode 100644 index 0000000..86d66b7 --- /dev/null +++ b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/CommandExecStatusCleanRunner.java @@ -0,0 +1,37 @@ + +package cn.orionsec.ops.runner; + +import cn.orionsec.ops.constant.command.ExecStatus; +import cn.orionsec.ops.dao.CommandExecDAO; +import cn.orionsec.ops.entity.domain.CommandExecDO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Date; + +@Component +@Order(2200) +@Slf4j +public class CommandExecStatusCleanRunner implements CommandLineRunner { + + @Resource + private CommandExecDAO commandExecDAO; + + @Override + public void run(String... args) { + log.info("重置命令执行状态-开始"); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .in(CommandExecDO::getExecStatus, ExecStatus.WAITING.getStatus(), ExecStatus.RUNNABLE.getStatus()); + // 更新执行状态 + CommandExecDO update = new CommandExecDO(); + update.setExecStatus(ExecStatus.TERMINATED.getStatus()); + update.setUpdateTime(new Date()); + commandExecDAO.update(update, wrapper); + log.info("重置命令执行状态-结束"); + } + +} diff --git a/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/HostMachineInitializeRunner.java b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/HostMachineInitializeRunner.java new file mode 100644 index 0000000..a12d4b1 --- /dev/null +++ b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/HostMachineInitializeRunner.java @@ -0,0 +1,67 @@ + +package cn.orionsec.ops.runner; + +import cn.orionsec.kit.lang.utils.Systems; +import cn.orionsec.kit.lang.utils.net.IPs; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.machine.MachineAuthType; +import cn.orionsec.ops.dao.MachineInfoDAO; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.service.api.MachineEnvService; +import cn.orionsec.ops.utils.ValueMix; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +@Component +@Order(1600) +@Slf4j +public class HostMachineInitializeRunner implements CommandLineRunner { + + private static final String DEFAULT_PASSWORD = "orionadmin"; + + @Resource + private MachineInfoDAO machineInfoDAO; + + @Resource + private MachineEnvService machineEnvService; + + @Override + public void run(String... args) { + log.info("初始化宿主机配置-开始"); + this.initMachine(); + log.info("初始化宿主机配置-结束"); + } + + /** + * 初始化宿主机 + */ + private void initMachine() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(MachineInfoDO::getId, Const.HOST_MACHINE_ID); + MachineInfoDO machineInfo = machineInfoDAO.selectOne(wrapper); + if (machineInfo == null) { + // 插入机器 + MachineInfoDO insert = new MachineInfoDO(); + insert.setMachineName(Systems.HOST_NAME); + insert.setMachineTag(Const.HOST_MACHINE_TAG); + insert.setMachineHost(IPs.IP); + insert.setSshPort(22); + insert.setDescription("宿主机"); + insert.setUsername(Systems.USER_NAME); + insert.setPassword(ValueMix.encrypt(DEFAULT_PASSWORD)); + insert.setAuthType(MachineAuthType.PASSWORD.getType()); + insert.setMachineStatus(Const.ENABLE); + machineInfoDAO.insert(insert); + machineInfoDAO.setId(insert.getId(), Const.HOST_MACHINE_ID); + log.info("宿主机已初始化"); + // 初始化环境变量 + machineEnvService.initEnv(Const.HOST_MACHINE_ID); + } + } + +} diff --git a/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/LoadIpFilterRunner.java b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/LoadIpFilterRunner.java new file mode 100644 index 0000000..366bdfe --- /dev/null +++ b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/LoadIpFilterRunner.java @@ -0,0 +1,60 @@ + +package cn.orionsec.ops.runner; + +import cn.orionsec.ops.constant.common.EnableType; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.interceptor.IpFilterInterceptor; +import cn.orionsec.ops.service.api.SystemEnvService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +@Component +@Order(1300) +@Slf4j +public class LoadIpFilterRunner implements CommandLineRunner { + + @Resource + private SystemEnvService systemEnvService; + + @Resource + private IpFilterInterceptor ipFilterInterceptor; + + @Override + public void run(String... args) { + log.info("加载IP黑白名单-开始"); + this.load(); + log.info("加载IP黑白名单-结束"); + } + + /** + * 加载 + */ + private void load() { + // 启用状态 + String enableIpFilter = systemEnvService.getEnvValue(SystemEnvAttr.ENABLE_IP_FILTER.getKey()); + if (!EnableType.of(enableIpFilter).getValue()) { + log.info("加载IP黑白名单-未启用"); + return; + } + // 规则类型 + String enableWhiteIpList = systemEnvService.getEnvValue(SystemEnvAttr.ENABLE_WHITE_IP_LIST.getKey()); + boolean enableWhite = EnableType.of(enableWhiteIpList).getValue(); + String loadFilterKey; + if (enableWhite) { + loadFilterKey = SystemEnvAttr.WHITE_IP_LIST.getKey(); + log.info("加载IP黑白名单-加载白名单"); + } else { + loadFilterKey = SystemEnvAttr.BLACK_IP_LIST.getKey(); + log.info("加载IP黑白名单-加载黑名单"); + } + String filter = systemEnvService.getEnvValue(loadFilterKey); + log.info("加载IP黑白名单-过滤规则:\n{}", filter); + // 加载 + ipFilterInterceptor.set(true, enableWhite, filter); + } + +} diff --git a/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/MachineMonitorStatusRunner.java b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/MachineMonitorStatusRunner.java new file mode 100644 index 0000000..a132f84 --- /dev/null +++ b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/MachineMonitorStatusRunner.java @@ -0,0 +1,88 @@ + +package cn.orionsec.ops.runner; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.Threads; +import cn.orionsec.ops.constant.monitor.MonitorConst; +import cn.orionsec.ops.constant.monitor.MonitorStatus; +import cn.orionsec.ops.dao.MachineMonitorDAO; +import cn.orionsec.ops.entity.domain.MachineMonitorDO; +import cn.orionsec.ops.entity.dto.MachineMonitorDTO; +import cn.orionsec.ops.entity.query.MachineMonitorQuery; +import cn.orionsec.ops.service.api.MachineMonitorService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.List; + +@Component +@Order(4000) +@Slf4j +public class MachineMonitorStatusRunner implements CommandLineRunner { + + @Resource + private MachineMonitorDAO machineMonitorDAO; + + @Resource + private MachineMonitorService machineMonitorService; + + @Override + public void run(String... args) throws Exception { + log.info("重置机器监控插件状态-开始"); + // 清除启动中的状态 + this.clearStartingStatus(); + // 异步检查插件状态及版本 + Threads.start(this::checkMonitorStatus); + log.info("重置机器监控插件状态-结束"); + } + + /** + * 清除启动中的状态 + */ + private void clearStartingStatus() { + MachineMonitorDO update = new MachineMonitorDO(); + update.setMonitorStatus(MonitorStatus.NOT_START.getStatus()); + update.setUpdateTime(new Date()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(MachineMonitorDO::getMonitorStatus, MonitorStatus.STARTING.getStatus()); + machineMonitorDAO.update(update, wrapper); + } + + /** + * 检查插件状态及版本 + */ + private void checkMonitorStatus() { + List monitors = machineMonitorDAO.selectMonitorList(new MachineMonitorQuery(), null); + for (MachineMonitorDTO monitor : monitors) { + log.info("检测机器监控插件状态-开始 {} ({})", monitor.getMachineName(), monitor.getMachineHost()); + MachineMonitorDO update = new MachineMonitorDO(); + update.setId(monitor.getId()); + String monitorUrl = monitor.getMonitorUrl(); + String accessToken = monitor.getAccessToken(); + // 不存在则设置默认值 + if (Strings.isBlank(monitorUrl)) { + monitorUrl = Strings.format(MonitorConst.DEFAULT_URL_FORMAT, monitor.getMachineHost()); + accessToken = MonitorConst.DEFAULT_ACCESS_TOKEN; + update.setMonitorUrl(monitorUrl); + update.setAccessToken(accessToken); + } + // 同步并且获取插件版本 + String monitorVersion = machineMonitorService.syncMonitorAgent(monitor.getMachineId(), monitorUrl, accessToken); + if (monitorVersion == null) { + // 未启动 + update.setMonitorStatus(MonitorStatus.NOT_START.getStatus()); + } else { + update.setAgentVersion(monitorVersion); + update.setMonitorStatus(MonitorStatus.RUNNING.getStatus()); + } + log.info("检测机器监控插件状态-完成 {} ({}), {}", monitor.getMachineName(), monitor.getMachineHost(), update.getMonitorStatus()); + machineMonitorDAO.updateById(update); + } + } + +} diff --git a/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/ReleaseStatusCleanRunner.java b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/ReleaseStatusCleanRunner.java new file mode 100644 index 0000000..010e26a --- /dev/null +++ b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/ReleaseStatusCleanRunner.java @@ -0,0 +1,88 @@ + +package cn.orionsec.ops.runner; + +import cn.orionsec.ops.constant.app.ActionStatus; +import cn.orionsec.ops.constant.app.ReleaseStatus; +import cn.orionsec.ops.constant.app.StageType; +import cn.orionsec.ops.dao.ApplicationReleaseDAO; +import cn.orionsec.ops.dao.ApplicationReleaseMachineDAO; +import cn.orionsec.ops.entity.domain.ApplicationReleaseDO; +import cn.orionsec.ops.entity.domain.ApplicationReleaseMachineDO; +import cn.orionsec.ops.service.api.ApplicationActionLogService; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.List; + +@Component +@Order(2400) +@Slf4j +public class ReleaseStatusCleanRunner implements CommandLineRunner { + + @Resource + private ApplicationReleaseDAO applicationReleaseDAO; + + @Resource + private ApplicationReleaseMachineDAO applicationReleaseMachineDAO; + + @Resource + private ApplicationActionLogService applicationActionLogService; + + @Override + public void run(String... args) { + log.info("重置应用发布状态-开始"); + Wrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationReleaseDO::getReleaseStatus, ReleaseStatus.RUNNABLE.getStatus()); + List releaseList = applicationReleaseDAO.selectList(wrapper); + for (ApplicationReleaseDO release : releaseList) { + Long releaseId = release.getId(); + ApplicationReleaseDO update = new ApplicationReleaseDO(); + update.setId(releaseId); + update.setReleaseStatus(ReleaseStatus.TERMINATED.getStatus()); + update.setUpdateTime(new Date()); + applicationReleaseDAO.updateById(update); + // 设置机器状态 + this.resetMachineStatus(releaseId); + log.info("重置应用发布状态-执行 {}", releaseId); + } + + log.info("重置应用发布状态-结束"); + } + + /** + * 设置机器状态 + * + * @param releaseId releaseId + */ + private void resetMachineStatus(Long releaseId) { + // 查询机器 + Wrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationReleaseMachineDO::getReleaseId, releaseId); + List machines = applicationReleaseMachineDAO.selectList(wrapper); + // 修改状态 + for (ApplicationReleaseMachineDO machine : machines) { + ApplicationReleaseMachineDO update = new ApplicationReleaseMachineDO(); + Long releaseMachineId = machine.getId(); + update.setId(releaseMachineId); + update.setUpdateTime(new Date()); + switch (ActionStatus.of(machine.getRunStatus())) { + case WAIT: + case RUNNABLE: + update.setRunStatus(ActionStatus.TERMINATED.getStatus()); + break; + default: + break; + } + applicationReleaseMachineDAO.updateById(update); + // 设置操作状态 + applicationActionLogService.resetActionStatus(releaseMachineId, StageType.RELEASE); + } + } + +} diff --git a/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/SchedulerTaskRunner.java b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/SchedulerTaskRunner.java new file mode 100644 index 0000000..85d4ade --- /dev/null +++ b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/SchedulerTaskRunner.java @@ -0,0 +1,127 @@ + +package cn.orionsec.ops.runner; + +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.common.EnableType; +import cn.orionsec.ops.constant.scheduler.SchedulerTaskMachineStatus; +import cn.orionsec.ops.constant.scheduler.SchedulerTaskStatus; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.dao.SchedulerTaskDAO; +import cn.orionsec.ops.dao.SchedulerTaskMachineRecordDAO; +import cn.orionsec.ops.dao.SchedulerTaskRecordDAO; +import cn.orionsec.ops.entity.domain.SchedulerTaskDO; +import cn.orionsec.ops.entity.domain.SchedulerTaskMachineRecordDO; +import cn.orionsec.ops.entity.domain.SchedulerTaskRecordDO; +import cn.orionsec.ops.task.TaskRegister; +import cn.orionsec.ops.task.TaskType; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.List; + +@Component +@Order(3300) +@Slf4j +public class SchedulerTaskRunner implements CommandLineRunner { + + @Resource + private SchedulerTaskDAO schedulerTaskDAO; + + @Resource + private SchedulerTaskRecordDAO schedulerTaskRecordDAO; + + @Resource + private SchedulerTaskMachineRecordDAO schedulerTaskMachineRecordDAO; + + @Resource + private TaskRegister taskRegister; + + @Override + public void run(String... args) { + log.info("调度任务初始化-开始"); + // 更新开始状态 + this.updateTaskStatus(); + // 更新执行记录状态 + this.updateTaskRecordStatus(); + // 自动恢复 + this.autoResumeTask(); + log.info("调度任务初始化-结束"); + } + + /** + * 更新任务状态 + */ + private void updateTaskStatus() { + Boolean autoResume = EnableType.of(SystemEnvAttr.RESUME_ENABLE_SCHEDULER_TASK.getValue()).getValue(); + List tasks = schedulerTaskDAO.selectList(null); + for (SchedulerTaskDO task : tasks) { + SchedulerTaskDO update = new SchedulerTaskDO(); + update.setId(task.getId()); + if (!autoResume || !Const.ENABLE.equals(task.getEnableStatus())) { + update.setEnableStatus(Const.DISABLE); + } + update.setUpdateTime(new Date()); + // 最近状态 + SchedulerTaskStatus status = SchedulerTaskStatus.of(task.getLatelyStatus()); + switch (status) { + case WAIT: + case RUNNABLE: + update.setLatelyStatus(SchedulerTaskStatus.TERMINATED.getStatus()); + break; + default: + break; + } + // 更新 + schedulerTaskDAO.updateById(update); + } + } + + /** + * 更新任务执行状态 + */ + private void updateTaskRecordStatus() { + // 重置任务明细 + Wrapper recordWrapper = new LambdaQueryWrapper() + .in(SchedulerTaskRecordDO::getTaskStatus, SchedulerTaskStatus.WAIT.getStatus(), SchedulerTaskStatus.RUNNABLE.getStatus()); + SchedulerTaskRecordDO updateRecord = new SchedulerTaskRecordDO(); + updateRecord.setTaskStatus(SchedulerTaskStatus.TERMINATED.getStatus()); + updateRecord.setUpdateTime(new Date()); + schedulerTaskRecordDAO.update(updateRecord, recordWrapper); + + // 重置机器明细 + Wrapper machineWrapper = new LambdaQueryWrapper() + .in(SchedulerTaskMachineRecordDO::getExecStatus, SchedulerTaskMachineStatus.WAIT.getStatus(), SchedulerTaskMachineStatus.RUNNABLE.getStatus()); + SchedulerTaskMachineRecordDO updateMachine = new SchedulerTaskMachineRecordDO(); + updateMachine.setExecStatus(SchedulerTaskMachineStatus.TERMINATED.getStatus()); + updateMachine.setUpdateTime(new Date()); + schedulerTaskMachineRecordDAO.update(updateMachine, machineWrapper); + + } + + /** + * 自动恢复任务 + */ + private void autoResumeTask() { + Boolean autoResume = EnableType.of(SystemEnvAttr.RESUME_ENABLE_SCHEDULER_TASK.getValue()).getValue(); + if (!autoResume) { + return; + } + // 查询启用的定时任务 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(SchedulerTaskDO::getEnableStatus, Const.ENABLE); + List taskList = schedulerTaskDAO.selectList(wrapper); + // 启用 + for (SchedulerTaskDO task : taskList) { + Long id = task.getId(); + log.info("调度任务自动恢复 id: {}, name: {}", id, task.getTaskName()); + taskRegister.submit(TaskType.SCHEDULER_TASK, task.getExpression(), id); + } + } + +} diff --git a/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/SftpTransferStatusCleanRunner.java b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/SftpTransferStatusCleanRunner.java new file mode 100644 index 0000000..dc64ccc --- /dev/null +++ b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/SftpTransferStatusCleanRunner.java @@ -0,0 +1,48 @@ + +package cn.orionsec.ops.runner; + +import cn.orionsec.ops.constant.sftp.SftpTransferStatus; +import cn.orionsec.ops.constant.sftp.SftpTransferType; +import cn.orionsec.ops.dao.FileTransferLogDAO; +import cn.orionsec.ops.entity.domain.FileTransferLogDO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Date; + +@Component +@Order(2100) +@Slf4j +public class SftpTransferStatusCleanRunner implements CommandLineRunner { + + @Resource + private FileTransferLogDAO fileTransferLogDAO; + + @Override + public void run(String... args) { + log.info("重置传输状态-开始"); + // 更新可打包传输状态 + LambdaQueryWrapper packageWrapper = new LambdaQueryWrapper() + .eq(FileTransferLogDO::getTransferType, SftpTransferType.PACKAGE.getType()) + .in(FileTransferLogDO::getTransferStatus, SftpTransferStatus.WAIT.getStatus(), SftpTransferStatus.RUNNABLE.getStatus()); + FileTransferLogDO updatePackage = new FileTransferLogDO(); + updatePackage.setTransferStatus(SftpTransferStatus.CANCEL.getStatus()); + updatePackage.setUpdateTime(new Date()); + fileTransferLogDAO.update(updatePackage, packageWrapper); + + // 更新可恢复传输状态 + LambdaQueryWrapper resumeWrapper = new LambdaQueryWrapper() + .ne(FileTransferLogDO::getTransferType, SftpTransferType.PACKAGE.getType()) + .in(FileTransferLogDO::getTransferStatus, SftpTransferStatus.WAIT.getStatus(), SftpTransferStatus.RUNNABLE.getStatus()); + FileTransferLogDO updateResume = new FileTransferLogDO(); + updateResume.setTransferStatus(SftpTransferStatus.PAUSE.getStatus()); + updateResume.setUpdateTime(new Date()); + fileTransferLogDAO.update(updateResume, resumeWrapper); + log.info("重置传输状态-结束"); + } + +} diff --git a/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/SystemEnvInitializeRunner.java b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/SystemEnvInitializeRunner.java new file mode 100644 index 0000000..88bff2f --- /dev/null +++ b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/SystemEnvInitializeRunner.java @@ -0,0 +1,152 @@ + +package cn.orionsec.ops.runner; + +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.common.EnableType; +import cn.orionsec.ops.constant.history.HistoryOperator; +import cn.orionsec.ops.constant.history.HistoryValueType; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.constant.tail.FileTailMode; +import cn.orionsec.ops.dao.SystemEnvDAO; +import cn.orionsec.ops.entity.domain.SystemEnvDO; +import cn.orionsec.ops.service.api.HistoryValueService; +import cn.orionsec.ops.utils.PathBuilders; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.List; + +@Component +@Order(1500) +@Slf4j +public class SystemEnvInitializeRunner implements CommandLineRunner { + + @Resource + private SystemEnvDAO systemEnvDAO; + + @Resource + private HistoryValueService historyValueService; + + @Override + public void run(String... args) { + log.info("初始化系统环境初始化-开始"); + this.initEnv(); + log.info("初始化系统环境初始化-结束"); + } + + /** + * 初始化环境 + */ + private void initEnv() { + // 查询所有的key + List keys = SystemEnvAttr.getKeys(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .in(SystemEnvDO::getAttrKey, keys); + List envList = systemEnvDAO.selectList(wrapper); + // 初始化数据 + for (String key : keys) { + SystemEnvAttr attr = SystemEnvAttr.of(key); + SystemEnvDO env = envList.stream() + .filter(s -> s.getAttrKey().equals(key)) + .findFirst() + .orElse(null); + if (env == null) { + // 插入数据 + String value = this.getAttrValue(attr); + SystemEnvDO insert = new SystemEnvDO(); + insert.setAttrKey(key); + insert.setAttrValue(value); + insert.setDescription(attr.getDescription()); + insert.setSystemEnv(attr.isSystemEnv() ? Const.IS_SYSTEM : Const.NOT_SYSTEM); + systemEnvDAO.insert(insert); + log.info("初始化系统变量 {} - {}", key, value); + // 插入历史值 + Long id = insert.getId(); + historyValueService.addHistory(id, HistoryValueType.SYSTEM_ENV, HistoryOperator.ADD, null, insert.getAttrValue()); + // 设置本地值 + attr.setValue(value); + } else { + // 设置本地值 + attr.setValue(env.getAttrValue()); + } + } + } + + /** + * 获取属性值 + * + * @param attr attr + * @return value + */ + private String getAttrValue(SystemEnvAttr attr) { + switch (attr) { + case KEY_PATH: + return createOrionOpsPath(Const.KEYS_PATH); + case PIC_PATH: + return createOrionOpsPath(Const.PIC_PATH); + case SWAP_PATH: + return createOrionOpsPath(Const.SWAP_PATH); + case SCREEN_PATH: + return createOrionOpsPath(Const.SCREEN_PATH); + case LOG_PATH: + return createOrionOpsPath(Const.LOG_PATH); + case TEMP_PATH: + return createOrionOpsPath(Const.TEMP_PATH); + case REPO_PATH: + return createOrionOpsPath(Const.REPO_PATH); + case DIST_PATH: + return createOrionOpsPath(Const.DIST_PATH); + case MACHINE_MONITOR_AGENT_PATH: + return createOrionOpsPath(Const.MACHINE_MONITOR_AGENT_PATH); + case TAIL_FILE_UPLOAD_PATH: + return createOrionOpsPath(Const.TAIL_FILE_PATH); + case TAIL_MODE: + return FileTailMode.TRACKER.getMode(); + case TRACKER_DELAY_TIME: + return Const.TRACKER_DELAY_MS + Const.EMPTY; + case ENABLE_IP_FILTER: + case ENABLE_WHITE_IP_LIST: + case ENABLE_AUTO_CLEAN_FILE: + case ALLOW_MULTIPLE_LOGIN: + case RESUME_ENABLE_SCHEDULER_TASK: + case TERMINAL_ACTIVE_PUSH_HEARTBEAT: + case LOGIN_IP_BIND: + return EnableType.DISABLED.getLabel(); + case LOGIN_FAILURE_LOCK: + case LOGIN_TOKEN_AUTO_RENEW: + return EnableType.ENABLED.getLabel(); + case LOGIN_TOKEN_EXPIRE: + return Const.DEFAULT_LOGIN_TOKEN_EXPIRE_HOUR + Const.EMPTY; + case LOGIN_FAILURE_LOCK_THRESHOLD: + case STATISTICS_CACHE_EXPIRE: + return Const.N_5 + Const.EMPTY; + case LOGIN_TOKEN_AUTO_RENEW_THRESHOLD: + return Const.N_2 + Const.EMPTY; + case FILE_CLEAN_THRESHOLD: + return Const.DEFAULT_FILE_CLEAN_THRESHOLD + Const.EMPTY; + case SFTP_UPLOAD_THRESHOLD: + return Const.SFTP_UPLOAD_THRESHOLD + Const.EMPTY; + default: + return null; + } + } + + /** + * 创建项目目录 + * + * @param path path + * @return path + */ + public static String createOrionOpsPath(String path) { + String dir = PathBuilders.getHostEnvPath(path); + dir = Files1.getPath(dir); + Files1.mkdirs(dir); + return dir; + } + +} diff --git a/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/SystemSpaceAnalysisRunner.java b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/SystemSpaceAnalysisRunner.java new file mode 100644 index 0000000..f08c6b1 --- /dev/null +++ b/orion-ops-api/orion-ops-runner/src/main/java/cn/orionsec/ops/runner/SystemSpaceAnalysisRunner.java @@ -0,0 +1,34 @@ + +package cn.orionsec.ops.runner; + +import cn.orionsec.kit.lang.utils.Threads; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.ops.service.api.SystemService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +@Slf4j +@Component +@Order(3200) +public class SystemSpaceAnalysisRunner implements CommandLineRunner { + + @Resource + private SystemService systemService; + + @Override + public void run(String... args) { + try { + log.info("runner-执行占用磁盘空间统计-开始 {}", Dates.current()); + // 不考虑多线程计算 + Threads.start(systemService::analysisSystemSpace); + log.info("runner-执行占用磁盘空间统计-结束 {}", Dates.current()); + } catch (Exception e) { + log.error("runner-执行占用磁盘空间统计-失败 {}", Dates.current(), e); + } + } + +} diff --git a/orion-ops-api/orion-ops-service/pom.xml b/orion-ops-api/orion-ops-service/pom.xml new file mode 100644 index 0000000..4c0c28b --- /dev/null +++ b/orion-ops-api/orion-ops-service/pom.xml @@ -0,0 +1,87 @@ + + + + cn.orionsec.ops + orion-ops-api + 1.3.1 + ../pom.xml + + + orion-ops-service + orion-ops-service + 4.0.0 + + + + + cn.orionsec.ops + orion-ops-common + ${project.version} + + + + + cn.orionsec.ops + orion-ops-model + ${project.version} + + + + + cn.orionsec.ops + orion-ops-dao + ${project.version} + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-websocket + + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + io.lettuce + lettuce-core + + + + + redis.clients + jedis + + + + + com.aliyun + alibaba-dingtalk-service-sdk + + + + diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/config/DataSourceConfig.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/config/DataSourceConfig.java new file mode 100644 index 0000000..15faaca --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/config/DataSourceConfig.java @@ -0,0 +1,20 @@ + +package cn.orionsec.ops.config; + +import com.alibaba.druid.pool.DruidDataSource; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.sql.DataSource; + +@Configuration +public class DataSourceConfig { + + @Bean + @ConfigurationProperties(prefix = "spring.datasource") + public DataSource druidDataSource() { + return new DruidDataSource(); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/config/JsonSerializerConfig.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/config/JsonSerializerConfig.java new file mode 100644 index 0000000..7023286 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/config/JsonSerializerConfig.java @@ -0,0 +1,35 @@ + +package cn.orionsec.ops.config; + +import cn.orionsec.kit.lang.utils.collect.Lists; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.alibaba.fastjson.support.config.FastJsonConfig; +import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.MediaType; + +import java.util.List; + +@Configuration +public class JsonSerializerConfig { + + @Bean + public HttpMessageConverters fastJsonHttpMessageConverters() { + FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter(); + FastJsonConfig fastJsonConfig = new FastJsonConfig(); + List mediaTypes = Lists.newList(); + mediaTypes.add(MediaType.APPLICATION_JSON); + fastJsonHttpMessageConverter.setSupportedMediaTypes(mediaTypes); + fastJsonConfig.setSerializerFeatures( + SerializerFeature.DisableCircularReferenceDetect, + SerializerFeature.WriteMapNullValue, + SerializerFeature.WriteNullListAsEmpty, + SerializerFeature.IgnoreNonFieldGetter + ); + fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig); + return new HttpMessageConverters(fastJsonHttpMessageConverter); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/config/RedisSerializeConfig.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/config/RedisSerializeConfig.java new file mode 100644 index 0000000..036551b --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/config/RedisSerializeConfig.java @@ -0,0 +1,25 @@ + +package cn.orionsec.ops.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializer; + +@Configuration +public class RedisSerializeConfig { + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory); + redisTemplate.setKeySerializer(RedisSerializer.string()); + redisTemplate.setValueSerializer(RedisSerializer.json()); + redisTemplate.setHashKeySerializer(RedisSerializer.string()); + redisTemplate.setHashValueSerializer(RedisSerializer.json()); + redisTemplate.afterPropertiesSet(); + return redisTemplate; + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/config/SchedulerConfig.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/config/SchedulerConfig.java new file mode 100644 index 0000000..5dddc25 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/config/SchedulerConfig.java @@ -0,0 +1,23 @@ + +package cn.orionsec.ops.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +@EnableScheduling +@Configuration +public class SchedulerConfig { + + @Bean + public TaskScheduler taskScheduler() { + ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); + scheduler.setPoolSize(4); + scheduler.setRemoveOnCancelPolicy(true); + scheduler.setThreadNamePrefix("scheduling-task-"); + return scheduler; + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/alarm/MachineAlarmContext.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/alarm/MachineAlarmContext.java new file mode 100644 index 0000000..4253a34 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/alarm/MachineAlarmContext.java @@ -0,0 +1,51 @@ + +package cn.orionsec.ops.handler.alarm; + +import cn.orionsec.ops.constant.machine.MachineAlarmType; +import cn.orionsec.ops.entity.domain.UserInfoDO; +import lombok.Data; + +import java.util.Date; +import java.util.Map; + +@Data +public class MachineAlarmContext { + + /** + * 报警机器id + */ + private Long machineId; + + /** + * 报警机器名称 + */ + private String machineName; + + /** + * 报警主机 + */ + private String machineHost; + + /** + * 报警类型 + * + * @see MachineAlarmType + */ + private Integer alarmType; + + /** + * 报警值 + */ + private Double alarmValue; + + /** + * 报警时间 + */ + private Date alarmTime; + + /** + * 用户映射 + */ + private Map userMapping; + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/alarm/MachineAlarmExecutor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/alarm/MachineAlarmExecutor.java new file mode 100644 index 0000000..7a35fc6 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/alarm/MachineAlarmExecutor.java @@ -0,0 +1,103 @@ + +package cn.orionsec.ops.handler.alarm; + +import cn.orionsec.kit.lang.able.Executable; +import cn.orionsec.kit.lang.utils.Threads; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.SchedulerPools; +import cn.orionsec.ops.constant.alarm.AlarmGroupNotifyType; +import cn.orionsec.ops.dao.UserInfoDAO; +import cn.orionsec.ops.entity.domain.AlarmGroupNotifyDO; +import cn.orionsec.ops.entity.domain.AlarmGroupUserDO; +import cn.orionsec.ops.entity.domain.MachineAlarmGroupDO; +import cn.orionsec.ops.entity.domain.UserInfoDO; +import cn.orionsec.ops.handler.alarm.push.AlarmWebSideMessagePusher; +import cn.orionsec.ops.handler.alarm.push.AlarmWebhookPusher; +import cn.orionsec.ops.service.api.AlarmGroupNotifyService; +import cn.orionsec.ops.service.api.AlarmGroupUserService; +import cn.orionsec.ops.service.api.MachineAlarmGroupService; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Slf4j +public class MachineAlarmExecutor implements Runnable, Executable { + + private static final MachineAlarmGroupService machineAlarmGroupService = SpringHolder.getBean(MachineAlarmGroupService.class); + + private static final AlarmGroupUserService alarmGroupUserService = SpringHolder.getBean(AlarmGroupUserService.class); + + private static final AlarmGroupNotifyService alarmGroupNotifyService = SpringHolder.getBean(AlarmGroupNotifyService.class); + + private static final UserInfoDAO userInfoDAO = SpringHolder.getBean(UserInfoDAO.class); + + private final MachineAlarmContext context; + + public MachineAlarmExecutor(MachineAlarmContext context) { + this.context = context; + } + + @Override + public void exec() { + Threads.start(this, SchedulerPools.MACHINE_ALARM_SCHEDULER); + } + + @Override + public void run() { + log.info("机器触发报警推送 context: {}", JSON.toJSONString(context)); + // 查询报警组 + List alarmGroupIdList = machineAlarmGroupService.selectByMachineId(context.getMachineId()) + .stream() + .map(MachineAlarmGroupDO::getGroupId) + .collect(Collectors.toList()); + log.info("机器触发报警推送 groupId: {}", alarmGroupIdList); + if (alarmGroupIdList.isEmpty()) { + return; + } + // 查询报警组员 + List alarmUserIdList = alarmGroupUserService.selectByGroupIdList(alarmGroupIdList) + .stream() + .map(AlarmGroupUserDO::getUserId) + .distinct() + .collect(Collectors.toList()); + log.info("机器触发报警推送 userId: {}", alarmUserIdList); + if (alarmGroupIdList.isEmpty()) { + return; + } + // 查询用户信息 + Map userMapping = userInfoDAO.selectBatchIds(alarmUserIdList) + .stream() + .collect(Collectors.toMap(UserInfoDO::getId, Function.identity())); + context.setUserMapping(userMapping); + // 通知站内信 + this.doAlarmPush(alarmGroupIdList); + } + + /** + * 执行报警推送 + * + * @param alarmGroupIdList 报警组id + */ + private void doAlarmPush(List alarmGroupIdList) { + // 通知站内信 + new AlarmWebSideMessagePusher(context).push(); + // 通知报警组 + List alarmNotifyList = alarmGroupNotifyService.selectByGroupIdList(alarmGroupIdList); + for (AlarmGroupNotifyDO alarmNotify : alarmNotifyList) { + Integer notifyType = alarmNotify.getNotifyType(); + if (AlarmGroupNotifyType.WEBHOOK.getType().equals(notifyType)) { + // 通知 webhook + try { + new AlarmWebhookPusher(alarmNotify.getNotifyId(), context).push(); + } catch (Exception e) { + log.error("机器报警 webhook 推送失败 id: {}", alarmNotify.getNotifyId(), e); + } + } + } + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/alarm/push/AlarmWebSideMessagePusher.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/alarm/push/AlarmWebSideMessagePusher.java new file mode 100644 index 0000000..7dbcd48 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/alarm/push/AlarmWebSideMessagePusher.java @@ -0,0 +1,40 @@ + +package cn.orionsec.ops.handler.alarm.push; + +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.math.Numbers; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.machine.MachineAlarmType; +import cn.orionsec.ops.constant.message.MessageType; +import cn.orionsec.ops.handler.alarm.MachineAlarmContext; +import cn.orionsec.ops.service.api.WebSideMessageService; + +import java.util.Map; + +public class AlarmWebSideMessagePusher implements IAlarmPusher { + + private static final WebSideMessageService webSideMessageService = SpringHolder.getBean(WebSideMessageService.class); + + private final MachineAlarmContext context; + + public AlarmWebSideMessagePusher(MachineAlarmContext context) { + this.context = context; + } + + @Override + public void push() { + // 发送站内信 + Map params = Maps.newMap(); + params.put(EventKeys.NAME, context.getMachineName()); + params.put(EventKeys.HOST, context.getMachineHost()); + params.put(EventKeys.TYPE, MachineAlarmType.of(context.getAlarmType()).getLabel()); + params.put(EventKeys.VALUE, Numbers.setScale(context.getAlarmValue(), 2)); + params.put(EventKeys.TIME, Dates.format(context.getAlarmTime())); + context.getUserMapping().forEach((k, v) -> { + webSideMessageService.addMessage(MessageType.MACHINE_ALARM, context.getMachineId(), k, v.getUsername(), params); + }); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/alarm/push/AlarmWebhookPusher.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/alarm/push/AlarmWebhookPusher.java new file mode 100644 index 0000000..0e2dbe9 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/alarm/push/AlarmWebhookPusher.java @@ -0,0 +1,81 @@ + +package cn.orionsec.ops.handler.alarm.push; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.math.Numbers; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.machine.MachineAlarmType; +import cn.orionsec.ops.constant.webhook.WebhookType; +import cn.orionsec.ops.dao.WebhookConfigDAO; +import cn.orionsec.ops.entity.domain.UserInfoDO; +import cn.orionsec.ops.entity.domain.WebhookConfigDO; +import cn.orionsec.ops.handler.alarm.MachineAlarmContext; +import cn.orionsec.ops.handler.webhook.DingRobotPusher; +import cn.orionsec.ops.utils.ResourceLoader; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class AlarmWebhookPusher implements IAlarmPusher { + + private static final String DING_TEMPLATE = "/templates/push/machine-alarm-ding.template"; + + private static WebhookConfigDAO webhookConfigDAO = SpringHolder.getBean(WebhookConfigDAO.class); + + private final Long webhookId; + + private final MachineAlarmContext context; + + public AlarmWebhookPusher(Long webhookId, MachineAlarmContext context) { + this.webhookId = webhookId; + this.context = context; + } + + @Override + public void push() { + // 查询 webhook + WebhookConfigDO webhook = webhookConfigDAO.selectById(webhookId); + if (webhook == null) { + return; + } + // 触发 webhook + if (WebhookType.DING_ROBOT.getType().equals(webhook.getWebhookType())) { + // 钉钉机器人 + this.doDingRobotPush(webhook); + } + } + + /** + * 执行钉钉机器人推送 + * + * @param webhook webhook + */ + private void doDingRobotPush(WebhookConfigDO webhook) { + Map params = Maps.newMap(); + params.put(EventKeys.NAME, context.getMachineName()); + params.put(EventKeys.HOST, context.getMachineHost()); + params.put(EventKeys.VALUE, Numbers.setScale(context.getAlarmValue(), 2)); + params.put(EventKeys.TYPE, MachineAlarmType.of(context.getAlarmType()).getLabel()); + params.put(EventKeys.TIME, Dates.format(context.getAlarmTime())); + String text = Strings.format(ResourceLoader.get(DING_TEMPLATE, AlarmWebhookPusher.class), params); + // @ 的用户 + List atMobiles = context.getUserMapping() + .values() + .stream() + .map(UserInfoDO::getContactPhone) + .collect(Collectors.toList()); + // 推送 + DingRobotPusher.builder() + .url(webhook.getWebhookUrl()) + .title("机器发生报警") + .text(text) + .atMobiles(atMobiles) + .build() + .push(); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/alarm/push/IAlarmPusher.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/alarm/push/IAlarmPusher.java new file mode 100644 index 0000000..2681750 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/alarm/push/IAlarmPusher.java @@ -0,0 +1,10 @@ + +package cn.orionsec.ops.handler.alarm.push; + +public interface IAlarmPusher { + + /** + * 执行推送 + */ + void push(); +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/AbstractActionHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/AbstractActionHandler.java new file mode 100644 index 0000000..38b83b5 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/AbstractActionHandler.java @@ -0,0 +1,309 @@ + +package cn.orionsec.ops.handler.app.action; + +import cn.orionsec.kit.lang.constant.Letters; +import cn.orionsec.kit.lang.define.io.OutputAppender; +import cn.orionsec.kit.lang.exception.ExecuteException; +import cn.orionsec.kit.lang.exception.LogException; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.lang.utils.io.Streams; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.kit.net.host.ssh.ExitCode; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.app.ActionStatus; +import cn.orionsec.ops.constant.app.ActionType; +import cn.orionsec.ops.constant.common.StainCode; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.dao.ApplicationActionLogDAO; +import cn.orionsec.ops.entity.domain.ApplicationActionLogDO; +import cn.orionsec.ops.utils.Utils; +import lombok.Getter; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.OutputStream; +import java.util.Date; + +@Slf4j +public abstract class AbstractActionHandler implements IActionHandler { + + protected static ApplicationActionLogDAO applicationActionLogDAO = SpringHolder.getBean(ApplicationActionLogDAO.class); + + protected Long id; + + protected Long relId; + + protected MachineActionStore store; + + protected ApplicationActionLogDO action; + + protected OutputStream outputSteam; + + protected OutputAppender appender; + + protected volatile boolean terminated; + + protected Date startTime, endTime; + + @Getter + protected volatile ActionStatus status; + + public AbstractActionHandler(Long id, MachineActionStore store) { + this.id = id; + this.relId = store.getRelId(); + this.store = store; + this.action = store.getActions().get(id); + this.status = ActionStatus.of(action.getRunStatus()); + } + + @Override + public void exec() { + log.info("应用操作执行-开始: relId: {}, id: {}", relId, id); + // 状态检查 + if (!ActionStatus.WAIT.equals(status)) { + return; + } + Exception ex = null; + // 执行 + try { + // 更新状态 + this.updateStatus(ActionStatus.RUNNABLE); + // 打开日志 + this.openLogger(); + // 执行 + this.handler(); + } catch (Exception e) { + ex = e; + } + // 回调 + try { + if (terminated) { + // 停止回调 + this.terminatedCallback(); + } else if (ex == null) { + // 成功回调 + this.successCallback(); + } else { + // 异常回调 + this.exceptionCallback(ex); + throw Exceptions.runtime(ex.getMessage(), ex); + } + } finally { + // 释放资源 + this.close(); + } + } + + /** + * 处理流程 + * + * @throws Exception Exception + */ + protected abstract void handler() throws Exception; + + @Override + public void skip() { + log.info("应用操作执行-跳过: relId: {}, id: {}", relId, id); + if (ActionStatus.WAIT.equals(status)) { + // 只能跳过等待中的任务 + this.updateStatus(ActionStatus.SKIPPED); + } + } + + @Override + public void terminate() { + log.info("应用操作执行-终止: relId: {}, id: {}", relId, id); + this.terminated = true; + } + + /** + * 打开日志 + */ + protected void openLogger() { + String logPath = Files1.getPath(SystemEnvAttr.LOG_PATH.getValue(), action.getLogPath()); + log.info("应用操作执行-打开日志 relId: {}, id: {}, path: {}", relId, id, logPath); + File logFile = new File(logPath); + Files1.touch(logFile); + this.outputSteam = Files1.openOutputStreamFastSafe(logFile); + this.appender = OutputAppender.create(outputSteam).then(store.getSuperLogStream()); + // 拼接开始日志 + this.appendStartedLog(); + } + + /** + * 拼接开始日志 + */ + @SneakyThrows + private void appendStartedLog() { + StringBuilder log = new StringBuilder() + .append(StainCode.prefix(StainCode.GLOSS_GREEN)) + .append("# ").append(action.getActionName()).append(" 执行开始") + .append(StainCode.SUFFIX) + .append(Letters.TAB) + .append(Utils.getStainKeyWords(Dates.format(startTime), StainCode.GLOSS_BLUE)) + .append(Const.LF); + ActionType actionType = ActionType.of(action.getActionType()); + if (ActionType.BUILD_COMMAND.equals(actionType) || ActionType.RELEASE_COMMAND.equals(actionType)) { + log.append(Letters.LF) + .append(Utils.getStainKeyWords("# 执行命令", StainCode.GLOSS_BLUE)) + .append(Letters.LF) + .append(StainCode.prefix(StainCode.GLOSS_CYAN)) + .append(Utils.getEndLfWithEof(action.getActionCommand())) + .append(StainCode.SUFFIX); + } + store.getSuperLogStream().write(Strings.bytes(Const.LF_3)); + this.appendLog(log.toString()); + } + + /** + * 停止回调 + */ + private void terminatedCallback() { + log.info("应用操作执行-终止回调: relId: {}, id: {}", relId, id); + // 修改状态 + this.updateStatus(ActionStatus.TERMINATED); + // 拼接日志 + StringBuilder log = new StringBuilder(Const.LF) + .append(StainCode.prefix(StainCode.GLOSS_YELLOW)) + .append("# ").append(action.getActionName()).append(" 手动停止") + .append(StainCode.SUFFIX) + .append(Letters.TAB) + .append(Utils.getStainKeyWords(Dates.format(endTime), StainCode.GLOSS_BLUE)) + .append(Const.LF); + this.appendLog(log.toString()); + } + + /** + * 成功回调 + */ + private void successCallback() { + log.info("应用操作执行-成功回调: relId: {}, id: {}", relId, id); + // 修改状态 + this.updateStatus(ActionStatus.FINISH); + // 拼接完成日志 + this.appendFinishedLog(null); + } + + /** + * 异常回调 + * + * @param ex ex + */ + private void exceptionCallback(Exception ex) { + log.error("应用操作执行-异常回调: relId: {}, id: {}", relId, id, ex); + // 修改状态 + this.updateStatus(ActionStatus.FAILURE); + // 拼接完成日志 + this.appendFinishedLog(ex); + } + + /** + * 拼接完成日志 + * + * @param ex ex + */ + private void appendFinishedLog(Exception ex) { + StringBuilder log = new StringBuilder(); + Integer actionType = action.getActionType(); + if (ActionType.BUILD_COMMAND.getType().equals(actionType) || ActionType.RELEASE_COMMAND.getType().equals(actionType)) { + log.append(Const.LF); + } + if (ex != null) { + // 有异常 + log.append(StainCode.prefix(StainCode.GLOSS_RED)) + .append("# ").append(action.getActionName()).append(" 执行失败") + .append(StainCode.SUFFIX) + .append(Letters.TAB) + .append(Utils.getStainKeyWords(Dates.format(endTime), StainCode.GLOSS_BLUE)); + Integer exitCode = this.getExitCode(); + if (exitCode != null) { + log.append(" exitcode: ") + .append(ExitCode.isSuccess(exitCode) + ? Utils.getStainKeyWords(exitCode, StainCode.GLOSS_BLUE) + : Utils.getStainKeyWords(exitCode, StainCode.GLOSS_RED)); + } + log.append(Letters.LF); + } else { + // 无异常 + long used = endTime.getTime() - startTime.getTime(); + log.append(StainCode.prefix(StainCode.GLOSS_GREEN)) + .append("# ").append(action.getActionName()).append(" 执行完成") + .append(StainCode.SUFFIX) + .append(Letters.TAB) + .append(Utils.getStainKeyWords(Dates.format(endTime), StainCode.GLOSS_BLUE)) + .append(" used ") + .append(Utils.getStainKeyWords(Utils.interval(used), StainCode.GLOSS_BLUE)) + .append(" (") + .append(StainCode.prefix(StainCode.GLOSS_BLUE)) + .append(used) + .append("ms") + .append(StainCode.SUFFIX) + .append(")\n"); + } + // 拼接异常 + if (ex != null && !(ex instanceof ExecuteException)) { + log.append(Const.LF); + if (ex instanceof LogException) { + log.append(Utils.getStainKeyWords(ex.getMessage(), StainCode.GLOSS_RED)); + } else { + log.append(Exceptions.getStackTraceAsString(ex)); + } + log.append(Const.LF); + } + // 拼接日志 + this.appendLog(log.toString()); + } + + /** + * 拼接日志 + * + * @param log log + */ + @SneakyThrows + protected void appendLog(String log) { + appender.write(Strings.bytes(log)); + appender.flush(); + } + + /** + * 更新状态 + * + * @param status status + */ + protected void updateStatus(ActionStatus status) { + Date now = new Date(); + this.status = status; + ApplicationActionLogDO update = new ApplicationActionLogDO(); + update.setId(id); + update.setRunStatus(status.getStatus()); + update.setUpdateTime(now); + switch (status) { + case RUNNABLE: + this.startTime = now; + update.setStartTime(now); + break; + case FINISH: + case FAILURE: + case TERMINATED: + this.endTime = now; + update.setEndTime(now); + update.setExitCode(this.getExitCode()); + break; + default: + break; + } + // 更新状态 + applicationActionLogDAO.updateById(update); + } + + @Override + public void close() { + // 关闭日志 + Streams.close(outputSteam); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/CheckoutActionHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/CheckoutActionHandler.java new file mode 100644 index 0000000..b563e43 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/CheckoutActionHandler.java @@ -0,0 +1,106 @@ + +package cn.orionsec.ops.handler.app.action; + +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.lang.utils.io.Streams; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.app.ActionType; +import cn.orionsec.ops.constant.common.StainCode; +import cn.orionsec.ops.entity.domain.ApplicationRepositoryDO; +import cn.orionsec.ops.service.api.ApplicationRepositoryService; +import cn.orionsec.ops.utils.Utils; +import org.eclipse.jgit.api.CloneCommand; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.ResetCommand; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; + +import java.io.File; + +public class CheckoutActionHandler extends AbstractActionHandler { + + private static final ApplicationRepositoryService applicationRepositoryService = SpringHolder.getBean(ApplicationRepositoryService.class); + + private Git git; + + public CheckoutActionHandler(Long actionId, MachineActionStore store) { + super(actionId, store); + } + + @Override + protected void handler() { + ApplicationRepositoryDO repo = applicationRepositoryService.selectById(store.getRepoId()); + // 查询分支 + String fullBranchName = store.getBranchName(); + String remote = fullBranchName.substring(0, fullBranchName.indexOf("/")); + String branchName = fullBranchName.substring(fullBranchName.indexOf("/") + 1); + String commitId = store.getCommitId(); + String repoClonePath = store.getRepoClonePath(); + Files1.delete(repoClonePath); + // 拼接日志 + StringBuilder log = new StringBuilder(Const.LF_2) + .append(Utils.getStainKeyWords(" *** 检出url ", StainCode.GLOSS_BLUE)) + .append(Utils.getStainKeyWords(repo.getRepoUrl(), StainCode.GLOSS_CYAN)) + .append(Const.LF); + log.append(Utils.getStainKeyWords(" *** 检出分支 ", StainCode.GLOSS_BLUE)) + .append(Utils.getStainKeyWords(fullBranchName, StainCode.GLOSS_CYAN)) + .append(Const.LF); + log.append(Utils.getStainKeyWords(" *** commitId ", StainCode.GLOSS_BLUE)) + .append(Utils.getStainKeyWords(commitId, StainCode.GLOSS_CYAN)) + .append(Const.LF); + log.append(Utils.getStainKeyWords(" *** 检出目录 ", StainCode.GLOSS_BLUE)) + .append(Utils.getStainKeyWords(repoClonePath, StainCode.GLOSS_CYAN)) + .append(Const.LF) + .append(Utils.getStainKeyWords(" *** 开始检出", StainCode.GLOSS_BLUE)) + .append(Const.LF); + this.appendLog(log.toString()); + // clone + try { + CloneCommand clone = Git.cloneRepository() + .setURI(repo.getRepoUrl()) + .setDirectory(new File(repoClonePath)) + .setRemote(remote) + .setBranch(branchName); + // 设置密码 + String[] pair = applicationRepositoryService.getRepositoryUsernamePassword(repo); + String username = pair[0]; + String password = pair[1]; + if (username != null) { + clone.setCredentialsProvider(new UsernamePasswordCredentialsProvider(username, password)); + } + this.git = clone.call(); + this.appendLog(Utils.getStainKeyWords(" *** 检出完成", StainCode.GLOSS_GREEN) + Const.LF_3); + } catch (Exception e) { + throw Exceptions.vcs(MessageConst.CHECKOUT_ERROR, e); + } + // 已停止则关闭 + if (terminated) { + return; + } + // reset + try { + git.reset().setMode(ResetCommand.ResetType.HARD) + .setRef(commitId) + .call(); + } catch (Exception e) { + throw Exceptions.vcs(MessageConst.RESET_ERROR, e); + } + } + + @Override + public void terminate() { + super.terminate(); + // 关闭git + Streams.close(git); + } + + @Override + public void close() { + super.close(); + // 关闭git + Streams.close(git); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/CommandActionHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/CommandActionHandler.java new file mode 100644 index 0000000..7c9a5c5 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/CommandActionHandler.java @@ -0,0 +1,58 @@ + +package cn.orionsec.ops.handler.app.action; + +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.io.Streams; +import cn.orionsec.kit.net.host.ssh.ExitCode; +import cn.orionsec.kit.net.host.ssh.command.CommandExecutor; +import cn.orionsec.kit.net.host.ssh.command.CommandExecutors; +import cn.orionsec.ops.constant.app.ActionType; +import cn.orionsec.ops.constant.common.StainCode; +import cn.orionsec.ops.utils.Utils; +import lombok.Getter; + +public class CommandActionHandler extends AbstractActionHandler { + + private CommandExecutor executor; + + @Getter + private Integer exitCode; + + public CommandActionHandler(Long actionId, MachineActionStore store) { + super(actionId, store); + } + + @Override + protected void handler() throws Exception { + this.appendLog(Utils.getStainKeyWords("# 开始执行\n", StainCode.GLOSS_BLUE)); + // 打开executor + this.executor = store.getSessionStore().getCommandExecutor(Strings.replaceCRLF(action.getActionCommand())); + // 执行命令 + CommandExecutors.execCommand(executor, appender); + this.exitCode = executor.getExitCode(); + if (!ExitCode.isSuccess(exitCode)) { + throw Exceptions.execute("*** 命令执行失败 exitCode: " + exitCode); + } + } + + @Override + public void terminate() { + super.terminate(); + // 关闭executor + Streams.close(executor); + } + + @Override + public void write(String command) { + executor.write(command); + } + + @Override + public void close() { + super.close(); + // 关闭executor + Streams.close(executor); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/IActionHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/IActionHandler.java new file mode 100644 index 0000000..a351bcc --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/IActionHandler.java @@ -0,0 +1,79 @@ + +package cn.orionsec.ops.handler.app.action; + +import cn.orionsec.kit.lang.able.Executable; +import cn.orionsec.kit.lang.able.SafeCloseable; +import cn.orionsec.kit.lang.function.select.Branches; +import cn.orionsec.kit.lang.function.select.Selector; +import cn.orionsec.ops.constant.app.ActionStatus; +import cn.orionsec.ops.constant.app.ActionType; +import cn.orionsec.ops.constant.app.TransferMode; +import cn.orionsec.ops.entity.domain.ApplicationActionLogDO; + +import java.util.List; +import java.util.stream.Collectors; + +public interface IActionHandler extends Executable, SafeCloseable { + + /** + * 获取状态 + * + * @return status + * @see ActionStatus + */ + ActionStatus getStatus(); + + /** + * 跳过 + */ + void skip(); + + /** + * 终止 + */ + void terminate(); + + /** + * 输入命令 + * + * @param command command + */ + default void write(String command) { + } + + /** + * 获取退出码 + * + * @return exitCode + */ + default Integer getExitCode() { + return null; + } + + /** + * 创建处理器 + * + * @param actions actions + * @param store store + * @return handler + */ + static List createHandler(List actions, MachineActionStore store) { + return actions.stream() + .map(action -> Selector.of(ActionType.of(action.getActionType())) + .test(Branches.eq(ActionType.BUILD_CHECKOUT) + .then(() -> new CheckoutActionHandler(action.getId(), store))) + .test(Branches.in(ActionType.BUILD_COMMAND, ActionType.RELEASE_COMMAND) + .then(() -> new CommandActionHandler(action.getId(), store))) + .test(Branches.eq(ActionType.RELEASE_TRANSFER) + .then(() -> { + if (TransferMode.SCP.getValue().equals(store.getTransferMode())) { + return new ScpTransferActionHandler(action.getId(), store); + } else { + return new SftpTransferActionHandler(action.getId(), store); + } + })) + .get()) + .collect(Collectors.toList()); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/MachineActionStore.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/MachineActionStore.java new file mode 100644 index 0000000..eefb074 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/MachineActionStore.java @@ -0,0 +1,108 @@ + +package cn.orionsec.ops.handler.app.action; + +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.net.host.SessionStore; +import cn.orionsec.ops.constant.app.TransferMode; +import cn.orionsec.ops.entity.domain.ApplicationActionLogDO; +import lombok.Data; + +import java.io.OutputStream; +import java.util.Map; + +@Data +public class MachineActionStore { + + /** + * 引用id + */ + private Long relId; + + /** + * action + */ + private Map actions; + + /** + * 日志输出流 + */ + private OutputStream superLogStream; + + /** + * 机器id + */ + private Long machineId; + + /** + * 机器用户 + */ + private String machineUsername; + + /** + * 机器主机 + */ + private String machineHost; + + /** + * 机器会话 + */ + private SessionStore sessionStore; + + /** + * 版本id + * + * @see CheckoutActionHandler + */ + private Long repoId; + + /** + * 分支 + * + * @see CheckoutActionHandler + */ + private String branchName; + + /** + * 提交版本 + * + * @see CheckoutActionHandler + */ + private String commitId; + + /** + * 仓库 clone 路径 + * + * @see CheckoutActionHandler + */ + private String repoClonePath; + + /** + * 构建产物文件 + * + * @see SftpTransferActionHandler + * @see ScpTransferActionHandler + */ + private String bundlePath; + + /** + * 产物传输路径 + * + * @see SftpTransferActionHandler + * @see ScpTransferActionHandler + */ + private String transferPath; + + /** + * 产物传输方式 + * + * @see SftpTransferActionHandler + * @see ScpTransferActionHandler + * @see TransferMode + */ + private String transferMode; + + public MachineActionStore() { + this.actions = Maps.newLinkedMap(); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/ScpTransferActionHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/ScpTransferActionHandler.java new file mode 100644 index 0000000..05acb3b --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/ScpTransferActionHandler.java @@ -0,0 +1,104 @@ + +package cn.orionsec.ops.handler.app.action; + +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.lang.utils.io.Streams; +import cn.orionsec.kit.net.host.SessionStore; +import cn.orionsec.kit.net.host.ssh.ExitCode; +import cn.orionsec.kit.net.host.ssh.command.CommandExecutor; +import cn.orionsec.kit.net.host.ssh.command.CommandExecutors; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.app.ActionType; +import cn.orionsec.ops.constant.app.TransferMode; +import cn.orionsec.ops.constant.command.CommandConst; +import cn.orionsec.ops.constant.common.StainCode; +import cn.orionsec.ops.constant.env.EnvConst; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.service.api.MachineInfoService; +import cn.orionsec.ops.utils.Utils; +import lombok.Getter; + +import java.io.File; +import java.util.Map; + +public class ScpTransferActionHandler extends AbstractActionHandler { + + protected static MachineInfoService machineInfoService = SpringHolder.getBean(MachineInfoService.class); + + private SessionStore session; + + private CommandExecutor executor; + + @Getter + private Integer exitCode; + + public ScpTransferActionHandler(Long actionId, MachineActionStore store) { + super(actionId, store); + } + + @Override + protected void handler() throws Exception { + // 检查文件 + String bundlePath = Files1.getPath(SystemEnvAttr.DIST_PATH.getValue(), store.getBundlePath()); + File bundleFile = new File(bundlePath); + if (!bundleFile.exists()) { + throw Exceptions.log("*** 产物文件不存在 " + bundlePath); + } + // 替换命令 + String scpCommand = Strings.def(action.getActionCommand(), CommandConst.SCP_TRANSFER_DEFAULT); + Map params = Maps.newMap(); + params.put(EnvConst.BUNDLE_PATH, bundlePath); + // 目标文件有空格需要转义空格为 \\ + params.put(EnvConst.TRANSFER_PATH, store.getTransferPath().replaceAll(Strings.SPACE, "\\\\\\\\ ")); + params.put(EnvConst.TARGET_USERNAME, store.getMachineUsername()); + params.put(EnvConst.TARGET_HOST, store.getMachineHost()); + scpCommand = Strings.format(scpCommand, EnvConst.SYMBOL, params); + // 拼接日志 + StringBuilder log = new StringBuilder(Const.LF) + .append(Utils.getStainKeyWords("# 执行 scp 传输命令", StainCode.GLOSS_BLUE)) + .append(Const.LF) + .append(StainCode.prefix(StainCode.GLOSS_CYAN)) + .append(Utils.getEndLfWithEof(scpCommand)) + .append(StainCode.SUFFIX); + this.appendLog(log.toString()); + // 打开session + this.session = machineInfoService.openSessionStore(Const.HOST_MACHINE_ID); + // 打开executor + this.executor = session.getCommandExecutor(Strings.replaceCRLF(scpCommand)); + // 执行命令 + CommandExecutors.execCommand(executor, appender); + this.exitCode = executor.getExitCode(); + this.appendLog(Const.LF); + if (!ExitCode.isSuccess(exitCode)) { + throw Exceptions.execute("*** 命令执行失败 exitCode: " + exitCode); + } + } + + @Override + public void write(String command) { + executor.write(command); + } + + @Override + public void terminate() { + super.terminate(); + // 关闭executor + Streams.close(executor); + // 关闭宿主机session + Streams.close(session); + } + + @Override + public void close() { + super.close(); + // 关闭executor + Streams.close(executor); + // 关闭宿主机session + Streams.close(session); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/SftpTransferActionHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/SftpTransferActionHandler.java new file mode 100644 index 0000000..aaf657f --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/action/SftpTransferActionHandler.java @@ -0,0 +1,140 @@ + +package cn.orionsec.ops.handler.app.action; + +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.lang.utils.io.Streams; +import cn.orionsec.kit.net.host.sftp.SftpExecutor; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.app.ActionType; +import cn.orionsec.ops.constant.app.TransferMode; +import cn.orionsec.ops.constant.common.StainCode; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.service.api.MachineEnvService; +import cn.orionsec.ops.utils.Utils; + +import java.io.File; +import java.util.List; +import java.util.Map; + +public class SftpTransferActionHandler extends AbstractActionHandler { + + private static final String SPACE = " "; + + protected static MachineEnvService machineEnvService = SpringHolder.getBean(MachineEnvService.class); + + private SftpExecutor executor; + + public SftpTransferActionHandler(Long actionId, MachineActionStore store) { + super(actionId, store); + } + + @Override + protected void handler() throws Exception { + // 检查文件 + String bundlePath = Files1.getPath(SystemEnvAttr.DIST_PATH.getValue(), store.getBundlePath()); + File bundleFile = new File(bundlePath); + if (!bundleFile.exists()) { + throw Exceptions.log("*** 产物文件不存在 " + bundlePath); + } + // 打开executor + String charset = machineEnvService.getSftpCharset(store.getMachineId()); + this.executor = store.getSessionStore().getSftpExecutor(charset); + executor.connect(); + // 拼接删除日志 + String transferPath = store.getTransferPath(); + String bundleAbsolutePath = bundleFile.getAbsolutePath(); + // 拼接头文件 + StringBuilder headerLog = new StringBuilder(Const.LF) + .append(SPACE) + .append(Utils.getStainKeyWords("开始传输文件", StainCode.GLOSS_BLUE)) + .append(Const.LF) + .append(SPACE) + .append(Utils.getStainKeyWords("source: ", StainCode.GLOSS_GREEN)) + .append(Utils.getStainKeyWords(bundleAbsolutePath, StainCode.GLOSS_BLUE)) + .append(Const.LF) + .append(SPACE) + .append(Utils.getStainKeyWords("target: ", StainCode.GLOSS_GREEN)) + .append(Utils.getStainKeyWords(transferPath, StainCode.GLOSS_BLUE)) + .append(Const.LF_2); + headerLog.append(StainCode.prefix(StainCode.GLOSS_GREEN)) + .append(SPACE) + .append("类型") + .append(SPACE) + .append(" target") + .append(StainCode.SUFFIX) + .append(Const.LF); + this.appendLog(headerLog.toString()); + // 转化文件 + Map transferFiles = this.convertFile(bundleFile, transferPath); + for (Map.Entry entity : transferFiles.entrySet()) { + File localFile = entity.getKey(); + String remoteFile = entity.getValue(); + // 文件夹则创建 + if (localFile.isDirectory()) { + StringBuilder createDirLog = new StringBuilder(SPACE) + .append(Utils.getStainKeyWords("mkdir", StainCode.GLOSS_GREEN)) + .append(SPACE) + .append(Utils.getStainKeyWords(remoteFile, StainCode.GLOSS_BLUE)) + .append(Const.LF); + this.appendLog(createDirLog.toString()); + executor.makeDirectories(remoteFile); + continue; + } + // 文件则传输 + StringBuilder transferLog = new StringBuilder(SPACE) + .append(Utils.getStainKeyWords("touch", StainCode.GLOSS_GREEN)) + .append(SPACE) + .append(Utils.getStainKeyWords(remoteFile, StainCode.GLOSS_BLUE)) + .append(StainCode.prefix(StainCode.GLOSS_BLUE)) + .append(" (") + .append(Files1.getSize(localFile.length())) + .append(")") + .append(StainCode.SUFFIX) + .append(Const.LF); + this.appendLog(transferLog.toString()); + executor.uploadFile(remoteFile, Files1.openInputStreamFast(localFile), true); + } + this.appendLog(Const.LF); + } + + /** + * 转化文件 + * + * @param bundleFile 打包文件 + * @param transferPath 传输目录 + * @return transferFiles + */ + private Map convertFile(File bundleFile, String transferPath) { + Map map = Maps.newLinkedMap(); + if (bundleFile.isFile()) { + map.put(bundleFile, transferPath); + return map; + } + // 如果是文件夹则需要截取 + String bundleFileAbsolutePath = bundleFile.getAbsolutePath(); + List transferFiles = Files1.listFiles(bundleFile, true, true); + for (File transferFile : transferFiles) { + String remoteFile = Files1.getPath(transferPath, transferFile.getAbsolutePath().substring(bundleFileAbsolutePath.length() + 1)); + map.put(transferFile, remoteFile); + } + return map; + } + + @Override + public void terminate() { + super.terminate(); + // 关闭executor + Streams.close(executor); + } + + @Override + public void close() { + super.close(); + // 关闭executor + Streams.close(executor); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/build/BuildSessionHolder.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/build/BuildSessionHolder.java new file mode 100644 index 0000000..c5b3e46 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/build/BuildSessionHolder.java @@ -0,0 +1,46 @@ + +package cn.orionsec.ops.handler.app.build; + +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.ops.handler.app.machine.IMachineProcessor; +import org.springframework.stereotype.Component; + +import java.util.concurrent.ConcurrentHashMap; + +@Component +public class BuildSessionHolder { + + /** + * session + */ + private final ConcurrentHashMap session = Maps.newCurrentHashMap(); + + /** + * 添加 session + * + * @param processor processor + */ + public void addSession(Long id, IMachineProcessor processor) { + session.put(id, processor); + } + + /** + * 获取 session + * + * @param id id + * @return session + */ + public IMachineProcessor getSession(Long id) { + return session.get(id); + } + + /** + * 移除 session + * + * @param id id + */ + public void removeSession(Long id) { + session.remove(id); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/machine/AbstractMachineProcessor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/machine/AbstractMachineProcessor.java new file mode 100644 index 0000000..a9a4572 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/machine/AbstractMachineProcessor.java @@ -0,0 +1,292 @@ + +package cn.orionsec.ops.handler.app.machine; + +import cn.orionsec.kit.lang.constant.Letters; +import cn.orionsec.kit.lang.exception.DisabledException; +import cn.orionsec.kit.lang.exception.LogException; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.lang.utils.io.Streams; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.app.ActionStatus; +import cn.orionsec.ops.constant.common.StainCode; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.handler.app.action.IActionHandler; +import cn.orionsec.ops.handler.tail.TailSessionHolder; +import cn.orionsec.ops.utils.Utils; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.OutputStream; +import java.util.Date; +import java.util.List; + +@Slf4j +public abstract class AbstractMachineProcessor implements IMachineProcessor { + + private static final TailSessionHolder tailSessionHolder = SpringHolder.getBean(TailSessionHolder.class); + + protected Long id; + + protected String logAbsolutePath; + + protected OutputStream logStream; + + protected Date startTime, endTime; + + /** + * 处理器 + */ + protected List handlerList; + + /** + * 是否已终止 + */ + protected volatile boolean terminated; + + public AbstractMachineProcessor(Long id) { + this.id = id; + } + + @Override + public void run() { + // 检查状态 + if (!this.checkCanRunnable()) { + return; + } + Exception ex = null; + boolean isMainError = false; + // 执行 + try { + // 更新状态 + this.updateStatus(MachineProcessorStatus.RUNNABLE); + // 打开日志 + this.openLogger(); + // 打开机器连接 + this.openMachineSession(); + // 执行 + for (IActionHandler handler : handlerList) { + if (ex == null && !terminated) { + try { + // 执行 + handler.exec(); + } catch (Exception e) { + // 强制停止的异常不算异常 + if (!terminated) { + ex = e; + } + } + } else { + // 跳过 + handler.skip(); + } + } + // 完成回调 + this.completeCallback(ex); + } catch (Exception e) { + // 异常 + ex = e; + isMainError = true; + } + // 回调 + try { + if (terminated) { + // 停止回调 + this.terminatedCallback(); + } else if (ex == null) { + // 成功回调 + this.successCallback(); + } else if (ex instanceof DisabledException) { + // 机器未启用回调 + this.machineDisableCallback(); + } else { + // 执行失败回调 + this.exceptionCallback(isMainError, ex); + } + } finally { + // 释放资源 + this.close(); + } + } + + /** + * 检查是否可执行 + * + * @return 是否可执行 + */ + protected abstract boolean checkCanRunnable(); + + /** + * 打开日志 + */ + protected void openLogger() { + String logPath = Files1.getPath(SystemEnvAttr.LOG_PATH.getValue(), this.getLogPath()); + File logFile = new File(logPath); + Files1.touch(logFile); + this.logStream = Files1.openOutputStreamFastSafe(logFile); + this.logAbsolutePath = logFile.getAbsolutePath(); + // 拼接开始日志 + this.appendStartedLog(); + } + + /** + * 获取日志文件路径 + * + * @return logPath + */ + protected abstract String getLogPath(); + + /** + * 打开session + */ + protected abstract void openMachineSession(); + + /** + * 更新状态 + * + * @param status status + */ + protected abstract void updateStatus(MachineProcessorStatus status); + + /** + * 拼接开始日志 + */ + protected abstract void appendStartedLog(); + + /** + * 完成回调 + * + * @param e e + */ + protected void completeCallback(Exception e) { + log.info("机器任务执行-完成 relId: {}", id); + } + + /** + * 停止回调 + */ + protected void terminatedCallback() { + log.info("机器任务执行-停止 relId: {}", id); + // 修改状态 + this.updateStatus(MachineProcessorStatus.TERMINATED); + // 拼接日志 + StringBuilder log = new StringBuilder(Const.LF_2) + .append(Utils.getStainKeyWords("# 主机任务手动停止", StainCode.GLOSS_YELLOW)) + .append(Letters.TAB) + .append(Utils.getStainKeyWords(Dates.format(endTime), StainCode.GLOSS_BLUE)) + .append(Const.LF); + this.appendLog(log.toString()); + } + + /** + * 成功回调 + */ + protected void successCallback() { + log.info("机器任务执行-成功 relId: {}", id); + // 修改状态 + this.updateStatus(MachineProcessorStatus.FINISH); + long used = endTime.getTime() - startTime.getTime(); + // 拼接日志 + StringBuilder log = new StringBuilder(Const.LF_2) + .append(Utils.getStainKeyWords("# 主机任务执行完成", StainCode.GLOSS_GREEN)) + .append(Const.TAB) + .append(Utils.getStainKeyWords(Dates.format(endTime), StainCode.GLOSS_BLUE)) + .append(" used ") + .append(Utils.getStainKeyWords(Utils.interval(used), StainCode.GLOSS_BLUE)) + .append(" (") + .append(StainCode.prefix(StainCode.GLOSS_BLUE)) + .append(used) + .append("ms") + .append(StainCode.SUFFIX) + .append(")\n"); + // 拼接日志 + this.appendLog(log.toString()); + } + + /** + * 机器未启用回调 + */ + private void machineDisableCallback() { + log.info("机器任务执行-机器未启用 relId: {}", id); + // 更新状态 + this.updateStatus(MachineProcessorStatus.TERMINATED); + // 拼接日志 + StringBuilder log = new StringBuilder(Const.LF_2) + .append(Utils.getStainKeyWords("# 主机任务执行机器未启用", StainCode.GLOSS_YELLOW)) + .append(Letters.TAB) + .append(Utils.getStainKeyWords(Dates.format(endTime), StainCode.GLOSS_BLUE)) + .append(Const.LF); + this.appendLog(log.toString()); + } + + /** + * 异常回调 + * + * @param isMainError isMainError + * @param ex ex + */ + protected void exceptionCallback(boolean isMainError, Exception ex) { + log.error("机器任务执行-失败 relId: {}, isMainError: {}", id, isMainError, ex); + // 更新状态 + this.updateStatus(MachineProcessorStatus.FAILURE); + // 拼接日志 + StringBuilder log = new StringBuilder(Const.LF_2) + .append(Utils.getStainKeyWords("# 主机任务执行失败", StainCode.GLOSS_RED)) + .append(Letters.TAB) + .append(Utils.getStainKeyWords(Dates.format(endTime), StainCode.GLOSS_BLUE)) + .append(Letters.LF); + // 拼接异常 + if (isMainError) { + log.append(Const.LF); + if (ex instanceof LogException) { + log.append(Utils.getStainKeyWords(ex.getMessage(), StainCode.GLOSS_RED)); + } else { + log.append(Exceptions.getStackTraceAsString(ex)); + } + log.append(Const.LF); + } + this.appendLog(log.toString()); + } + + /** + * 拼接日志 + * + * @param log log + */ + @SneakyThrows + protected void appendLog(String log) { + logStream.write(Strings.bytes(log)); + logStream.flush(); + } + + @Override + public void terminate() { + // 设置状态为已停止 + this.terminated = true; + // 结束正在执行的action + Lists.stream(handlerList) + .filter(s -> ActionStatus.RUNNABLE.equals(s.getStatus())) + .forEach(IActionHandler::terminate); + } + + @Override + public void write(String command) { + Lists.stream(handlerList) + .filter(s -> ActionStatus.RUNNABLE.equals(s.getStatus())) + .forEach(s -> s.write(command)); + } + + @Override + public void close() { + // 关闭日志流 + Streams.close(logStream); + // 异步关闭正在tail的日志 + tailSessionHolder.asyncCloseTailFile(Const.HOST_MACHINE_ID, logAbsolutePath); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/machine/BuildMachineProcessor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/machine/BuildMachineProcessor.java new file mode 100644 index 0000000..4bfcc52 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/machine/BuildMachineProcessor.java @@ -0,0 +1,299 @@ + +package cn.orionsec.ops.handler.app.machine; + +import cn.orionsec.kit.lang.able.Executable; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.Threads; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.lang.utils.io.Streams; +import cn.orionsec.kit.lang.utils.io.compress.CompressTypeEnum; +import cn.orionsec.kit.lang.utils.io.compress.FileCompressor; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.kit.net.host.SessionStore; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.SchedulerPools; +import cn.orionsec.ops.constant.app.ActionType; +import cn.orionsec.ops.constant.app.ApplicationEnvAttr; +import cn.orionsec.ops.constant.app.BuildStatus; +import cn.orionsec.ops.constant.app.StageType; +import cn.orionsec.ops.constant.common.StainCode; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.message.MessageType; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.dao.ApplicationBuildDAO; +import cn.orionsec.ops.entity.domain.ApplicationActionLogDO; +import cn.orionsec.ops.entity.domain.ApplicationBuildDO; +import cn.orionsec.ops.handler.app.action.IActionHandler; +import cn.orionsec.ops.handler.app.action.MachineActionStore; +import cn.orionsec.ops.handler.app.build.BuildSessionHolder; +import cn.orionsec.ops.service.api.ApplicationActionLogService; +import cn.orionsec.ops.service.api.ApplicationEnvService; +import cn.orionsec.ops.service.api.MachineInfoService; +import cn.orionsec.ops.service.api.WebSideMessageService; +import cn.orionsec.ops.utils.Utils; +import com.alibaba.fastjson.JSON; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.util.Date; +import java.util.List; +import java.util.Map; + +@Slf4j +public class BuildMachineProcessor extends AbstractMachineProcessor implements Executable { + + private static final ApplicationBuildDAO applicationBuildDAO = SpringHolder.getBean(ApplicationBuildDAO.class); + + private static final MachineInfoService machineInfoService = SpringHolder.getBean(MachineInfoService.class); + + private static final ApplicationActionLogService applicationActionLogService = SpringHolder.getBean(ApplicationActionLogService.class); + + private static final ApplicationEnvService applicationEnvService = SpringHolder.getBean(ApplicationEnvService.class); + + private static final BuildSessionHolder buildSessionHolder = SpringHolder.getBean(BuildSessionHolder.class); + + private static final WebSideMessageService webSideMessageService = SpringHolder.getBean(WebSideMessageService.class); + + private final MachineActionStore store; + + private ApplicationBuildDO record; + + public BuildMachineProcessor(Long id) { + super(id); + this.store = new MachineActionStore(); + } + + @Override + public void exec() { + log.info("应用构建任务执行提交 buildId: {}", id); + Threads.start(this, SchedulerPools.APP_BUILD_SCHEDULER); + } + + @Override + public void run() { + log.info("应用构建任务执行开始 buildId: {}", id); + // 初始化数据 + this.initData(); + // 执行 + super.run(); + } + + /** + * 初始化数据 + */ + private void initData() { + // 查询build + this.record = applicationBuildDAO.selectById(id); + log.info("应用构建任务-获取数据-build buildId: {}, record: {}", id, JSON.toJSONString(record)); + // 检查状态 + if (record == null || !BuildStatus.WAIT.getStatus().equals(record.getBuildStatus())) { + return; + } + // 查询action + List actions = applicationActionLogService.selectActionByRelId(id, StageType.BUILD); + actions.forEach(s -> store.getActions().put(s.getId(), s)); + log.info("应用构建任务-获取数据-action buildId: {}, actions: {}", id, JSON.toJSONString(actions)); + // 插入store + Long repoId = record.getRepoId(); + store.setRelId(id); + store.setRepoId(repoId); + store.setBranchName(record.getBranchName()); + store.setCommitId(record.getCommitId()); + if (repoId != null) { + String repoClonePath = Files1.getPath(SystemEnvAttr.REPO_PATH.getValue(), repoId + "/" + record.getId()); + store.setRepoClonePath(repoClonePath); + } + // 创建handler + this.handlerList = IActionHandler.createHandler(actions, store); + } + + @Override + protected boolean checkCanRunnable() { + return record != null && BuildStatus.WAIT.getStatus().equals(record.getBuildStatus()); + } + + @Override + protected void completeCallback(Exception e) { + super.completeCallback(e); + if (e == null && !terminated) { + // 复制产物文件 + this.copyBundleFile(); + } + } + + @Override + protected void successCallback() { + // 完成回调 + super.successCallback(); + // 发送站内信 + Map params = Maps.newMap(); + params.put(EventKeys.ID, record.getId()); + params.put(EventKeys.SEQ, record.getBuildSeq()); + params.put(EventKeys.PROFILE_NAME, record.getProfileName()); + params.put(EventKeys.APP_NAME, record.getAppName()); + params.put(EventKeys.BUILD_SEQ, record.getBuildSeq()); + webSideMessageService.addMessage(MessageType.BUILD_SUCCESS, record.getId(), record.getCreateUserId(), record.getCreateUserName(), params); + } + + @Override + protected void exceptionCallback(boolean isMainError, Exception ex) { + super.exceptionCallback(isMainError, ex); + // 发送站内信 + Map params = Maps.newMap(); + params.put(EventKeys.ID, record.getId()); + params.put(EventKeys.SEQ, record.getBuildSeq()); + params.put(EventKeys.APP_NAME, record.getAppName()); + params.put(EventKeys.PROFILE_NAME, record.getProfileName()); + params.put(EventKeys.BUILD_SEQ, record.getBuildSeq()); + webSideMessageService.addMessage(MessageType.BUILD_FAILURE, record.getId(), record.getCreateUserId(), record.getCreateUserName(), params); + } + + @Override + protected void openLogger() { + super.openLogger(); + store.setSuperLogStream(this.logStream); + } + + /** + * 复制产物文件 + */ + @SneakyThrows + private void copyBundleFile() { + // 查询应用产物目录 + String bundlePath = applicationEnvService.getAppEnvValue(record.getAppId(), record.getProfileId(), ApplicationEnvAttr.BUNDLE_PATH.getKey()); + if (!bundlePath.startsWith(Const.SLASH) && !Files1.isWindowsPath(bundlePath) && store.getRepoClonePath() != null) { + // 基于代码目录的相对路径 + bundlePath = Files1.getPath(store.getRepoClonePath(), bundlePath); + } + // 检查产物文件是否存在 + File bundleFile = new File(bundlePath); + if (!bundleFile.exists()) { + throw Exceptions.log("***** 构建产物不存在 " + bundlePath); + } + // 复制到dist目录下 + String copyBundlePath = Files1.getPath(SystemEnvAttr.DIST_PATH.getValue(), record.getBundlePath()); + StringBuilder copyLog = new StringBuilder(Const.LF_3) + .append(StainCode.prefix(StainCode.GLOSS_GREEN)) + .append("***** 已生成产物文件 ") + .append(StainCode.SUFFIX) + .append(Utils.getStainKeyWords(copyBundlePath, StainCode.GLOSS_BLUE)) + .append(Const.LF); + this.appendLog(copyLog.toString()); + if (bundleFile.isFile()) { + Files1.copy(bundleFile, new File(copyBundlePath)); + } else { + // 复制文件夹 + Files1.copyDir(bundleFile, new File(copyBundlePath), false); + // 文件夹打包 + String compressFile = copyBundlePath + "." + Const.SUFFIX_ZIP; + FileCompressor compressor = CompressTypeEnum.ZIP.compressor().get(); + compressor.addFile(bundleFile); + compressor.setAbsoluteCompressPath(compressFile); + compressor.compress(); + StringBuilder compressLog = new StringBuilder() + .append(StainCode.prefix(StainCode.GLOSS_GREEN)) + .append("***** 已生成产物文件zip ") + .append(StainCode.SUFFIX) + .append(Utils.getStainKeyWords(compressFile, StainCode.GLOSS_BLUE)) + .append(Const.LF); + this.appendLog(compressLog.toString()); + } + } + + @Override + protected String getLogPath() { + return record.getLogPath(); + } + + @Override + protected void openMachineSession() { + boolean hasCommand = store.getActions().values().stream() + .map(ApplicationActionLogDO::getActionType) + .anyMatch(ActionType.BUILD_COMMAND.getType()::equals); + if (!hasCommand) { + return; + } + // 打开session + SessionStore sessionStore = machineInfoService.openSessionStore(Const.HOST_MACHINE_ID); + store.setMachineId(Const.HOST_MACHINE_ID); + store.setSessionStore(sessionStore); + } + + @Override + protected void updateStatus(MachineProcessorStatus status) { + Date now = new Date(); + ApplicationBuildDO update = new ApplicationBuildDO(); + update.setId(id); + update.setBuildStatus(BuildStatus.valueOf(status.name()).getStatus()); + update.setUpdateTime(now); + switch (status) { + case RUNNABLE: + this.startTime = now; + update.setBuildStartTime(now); + // 添加session + buildSessionHolder.addSession(id, this); + break; + case FINISH: + case FAILURE: + case TERMINATED: + this.endTime = now; + update.setBuildEndTime(now); + break; + default: + break; + } + // 更新 + applicationBuildDAO.updateById(update); + } + + @Override + protected void appendStartedLog() { + StringBuilder log = new StringBuilder() + .append(Utils.getStainKeyWords("# 开始执行主机构建任务 ", StainCode.GLOSS_GREEN)) + .append(StainCode.prefix(StainCode.GLOSS_BLUE)) + .append("#").append(record.getBuildSeq()) + .append(StainCode.SUFFIX) + .append(Const.LF); + log.append("构建应用: ") + .append(Utils.getStainKeyWords(record.getAppName(), StainCode.GLOSS_BLUE)) + .append(Const.LF); + log.append("构建环境: ") + .append(Utils.getStainKeyWords(record.getProfileName(), StainCode.GLOSS_BLUE)) + .append(Const.LF); + log.append("执行用户: ") + .append(Utils.getStainKeyWords(record.getCreateUserName(), StainCode.GLOSS_BLUE)) + .append(Const.LF); + log.append("开始时间: ") + .append(Utils.getStainKeyWords(Dates.format(startTime), StainCode.GLOSS_BLUE)) + .append(Const.LF); + if (!Strings.isBlank(record.getDescription())) { + log.append("构建描述: ") + .append(Utils.getStainKeyWords(record.getDescription(), StainCode.GLOSS_BLUE)) + .append(Const.LF); + } + if (!Strings.isBlank(record.getBranchName())) { + log.append("branch: ") + .append(Utils.getStainKeyWords(record.getBranchName(), StainCode.GLOSS_BLUE)) + .append(Const.LF); + log.append("commit: ") + .append(Utils.getStainKeyWords(record.getCommitId(), StainCode.GLOSS_BLUE)) + .append(Const.LF); + } + this.appendLog(log.toString()); + } + + @Override + public void close() { + // 释放资源 + super.close(); + // 释放连接 + Streams.close(store.getSessionStore()); + // 移除session + buildSessionHolder.removeSession(id); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/machine/IMachineProcessor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/machine/IMachineProcessor.java new file mode 100644 index 0000000..2764bd3 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/machine/IMachineProcessor.java @@ -0,0 +1,26 @@ + +package cn.orionsec.ops.handler.app.machine; + +import cn.orionsec.kit.lang.able.SafeCloseable; + +public interface IMachineProcessor extends Runnable, SafeCloseable { + + /** + * 终止 + */ + void terminate(); + + /** + * 输入命令 + * + * @param command command + */ + void write(String command); + + /** + * 跳过 + */ + default void skip() { + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/machine/MachineProcessorStatus.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/machine/MachineProcessorStatus.java new file mode 100644 index 0000000..f9d722e --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/machine/MachineProcessorStatus.java @@ -0,0 +1,38 @@ + +package cn.orionsec.ops.handler.app.machine; + +public enum MachineProcessorStatus { + + /** + * 未开始 + */ + WAIT, + + /** + * 进行中 + */ + RUNNABLE, + + /** + * 已完成 + */ + FINISH, + + /** + * 执行失败 + */ + FAILURE, + + /** + * 已跳过 + */ + SKIPPED, + + /** + * 已终止 + */ + TERMINATED, + + ; + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/machine/ReleaseMachineProcessor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/machine/ReleaseMachineProcessor.java new file mode 100644 index 0000000..ec03605 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/machine/ReleaseMachineProcessor.java @@ -0,0 +1,194 @@ + +package cn.orionsec.ops.handler.app.machine; + +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.io.Streams; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.kit.net.host.SessionStore; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.app.ActionStatus; +import cn.orionsec.ops.constant.app.StageType; +import cn.orionsec.ops.constant.common.StainCode; +import cn.orionsec.ops.dao.ApplicationMachineDAO; +import cn.orionsec.ops.dao.ApplicationReleaseMachineDAO; +import cn.orionsec.ops.entity.domain.*; +import cn.orionsec.ops.handler.app.action.IActionHandler; +import cn.orionsec.ops.handler.app.action.MachineActionStore; +import cn.orionsec.ops.service.api.ApplicationActionLogService; +import cn.orionsec.ops.service.api.MachineInfoService; +import cn.orionsec.ops.utils.Utils; +import com.alibaba.fastjson.JSON; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import java.util.Date; +import java.util.List; + +@Slf4j +public class ReleaseMachineProcessor extends AbstractMachineProcessor { + + protected static ApplicationReleaseMachineDAO applicationReleaseMachineDAO = SpringHolder.getBean(ApplicationReleaseMachineDAO.class); + + protected static ApplicationMachineDAO applicationMachineDAO = SpringHolder.getBean(ApplicationMachineDAO.class); + + protected static ApplicationActionLogService applicationActionLogService = SpringHolder.getBean(ApplicationActionLogService.class); + + protected static MachineInfoService machineInfoService = SpringHolder.getBean(MachineInfoService.class); + + private final ApplicationReleaseDO release; + + private final ApplicationReleaseMachineDO releaseMachine; + + private final MachineActionStore store; + + @Getter + private volatile ActionStatus status; + + public ReleaseMachineProcessor(ApplicationReleaseDO release, ApplicationReleaseMachineDO releaseMachine) { + super(releaseMachine.getId()); + this.release = release; + this.releaseMachine = releaseMachine; + this.status = ActionStatus.of(releaseMachine.getRunStatus()); + this.store = new MachineActionStore(); + store.setRelId(releaseMachine.getId()); + store.setMachineId(releaseMachine.getMachineId()); + store.setBundlePath(release.getBundlePath()); + store.setTransferPath(release.getTransferPath()); + store.setTransferMode(release.getTransferMode()); + } + + @Override + public void run() { + log.info("应用发布任务执行开始 releaseId: {}", id); + // 初始化数据 + this.initData(); + // 执行 + super.run(); + } + + /** + * 初始化数据 + */ + private void initData() { + // 查询机器发布操作 + List actions = applicationActionLogService.selectActionByRelId(id, StageType.RELEASE); + actions.forEach(s -> store.getActions().put(s.getId(), s)); + log.info("应用发布器-获取数据-action releaseId: {}, actions: {}", id, JSON.toJSONString(actions)); + // 创建handler + this.handlerList = IActionHandler.createHandler(actions, store); + } + + @Override + public void skip() { + if (ActionStatus.WAIT.equals(status)) { + // 只能跳过等待中的任务 + this.updateStatus(MachineProcessorStatus.SKIPPED); + } + } + + @Override + protected boolean checkCanRunnable() { + return ActionStatus.WAIT.equals(status); + } + + @Override + protected void openLogger() { + super.openLogger(); + store.setSuperLogStream(this.logStream); + } + + @Override + protected String getLogPath() { + return releaseMachine.getLogPath(); + } + + @Override + protected void successCallback() { + // 完成回调 + super.successCallback(); + // 更新应用机器发布版本 + this.updateAppMachineVersion(); + } + + @Override + protected void exceptionCallback(boolean isMainError, Exception ex) { + super.exceptionCallback(isMainError, ex); + throw Exceptions.runtime(ex); + } + + @Override + protected void openMachineSession() { + // 打开目标机器session + Long machineId = releaseMachine.getMachineId(); + MachineInfoDO machine = machineInfoService.selectById(machineId); + SessionStore sessionStore = machineInfoService.openSessionStore(machine); + store.setSessionStore(sessionStore); + store.setMachineUsername(machine.getUsername()); + store.setMachineHost(machine.getMachineHost()); + } + + @Override + protected void updateStatus(MachineProcessorStatus processorStatus) { + this.status = ActionStatus.valueOf(processorStatus.name()); + Date now = new Date(); + ApplicationReleaseMachineDO update = new ApplicationReleaseMachineDO(); + update.setId(id); + update.setRunStatus(status.getStatus()); + update.setUpdateTime(now); + switch (processorStatus) { + case RUNNABLE: + this.startTime = now; + update.setStartTime(now); + break; + case FINISH: + case TERMINATED: + case FAILURE: + this.endTime = now; + update.setEndTime(now); + break; + default: + break; + } + applicationReleaseMachineDAO.updateById(update); + } + + @Override + protected void appendStartedLog() { + StringBuilder log = new StringBuilder() + .append(Utils.getStainKeyWords("# 开始执行主机发布任务", StainCode.GLOSS_GREEN)) + .append(Const.LF); + log.append("机器名称: ") + .append(Utils.getStainKeyWords(releaseMachine.getMachineName(), StainCode.GLOSS_BLUE)) + .append(Const.LF); + log.append("发布主机: ") + .append(Utils.getStainKeyWords(releaseMachine.getMachineHost(), StainCode.GLOSS_BLUE)) + .append(Const.LF); + log.append("开始时间: ") + .append(Utils.getStainKeyWords(Dates.format(startTime), StainCode.GLOSS_BLUE)) + .append(Const.LF); + this.appendLog(log.toString()); + } + + /** + * 更新应用机器版本 + */ + private void updateAppMachineVersion() { + ApplicationMachineDO update = new ApplicationMachineDO(); + update.setAppId(release.getAppId()); + update.setProfileId(release.getProfileId()); + update.setMachineId(releaseMachine.getMachineId()); + update.setReleaseId(release.getId()); + update.setBuildId(release.getBuildId()); + update.setBuildSeq(release.getBuildSeq()); + applicationMachineDAO.updateAppVersion(update); + } + + @Override + public void close() { + // 释放资源 + super.close(); + // 释放连接 + Streams.close(store.getSessionStore()); + } +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/IPipelineProcessor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/IPipelineProcessor.java new file mode 100644 index 0000000..86477ba --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/IPipelineProcessor.java @@ -0,0 +1,44 @@ + +package cn.orionsec.ops.handler.app.pipeline; + +import cn.orionsec.kit.lang.able.Executable; + +public interface IPipelineProcessor extends Executable, Runnable { + + /** + * 获取明细id + * + * @return taskId + */ + Long getTaskId(); + + /** + * 停止执行 + */ + void terminate(); + + /** + * 停止执行详情 + * + * @param id id + */ + void terminateDetail(Long id); + + /** + * 跳过执行详情 + * + * @param id id + */ + void skipDetail(Long id); + + /** + * 获取流水线执行器 + * + * @param id id + * @return 流水线执行器 + */ + static IPipelineProcessor with(Long id) { + return new PipelineProcessor(id); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/PipelineProcessor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/PipelineProcessor.java new file mode 100644 index 0000000..cc9904d --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/PipelineProcessor.java @@ -0,0 +1,247 @@ + +package cn.orionsec.ops.handler.app.pipeline; + +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Threads; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.SchedulerPools; +import cn.orionsec.ops.constant.app.PipelineDetailStatus; +import cn.orionsec.ops.constant.app.PipelineStatus; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.message.MessageType; +import cn.orionsec.ops.dao.ApplicationPipelineTaskDAO; +import cn.orionsec.ops.entity.domain.ApplicationPipelineTaskDO; +import cn.orionsec.ops.entity.domain.ApplicationPipelineTaskDetailDO; +import cn.orionsec.ops.handler.app.pipeline.stage.IStageHandler; +import cn.orionsec.ops.service.api.ApplicationPipelineTaskDetailService; +import cn.orionsec.ops.service.api.WebSideMessageService; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Map; + +@Slf4j +public class PipelineProcessor implements IPipelineProcessor { + + private static final ApplicationPipelineTaskDAO applicationPipelineTaskDAO = SpringHolder.getBean(ApplicationPipelineTaskDAO.class); + + private static final ApplicationPipelineTaskDetailService applicationPipelineTaskDetailService = SpringHolder.getBean(ApplicationPipelineTaskDetailService.class); + + private static final PipelineSessionHolder pipelineSessionHolder = SpringHolder.getBean(PipelineSessionHolder.class); + + private static final WebSideMessageService webSideMessageService = SpringHolder.getBean(WebSideMessageService.class); + + @Getter + private final Long taskId; + + private ApplicationPipelineTaskDO task; + + private final Map stageHandlers; + + private volatile IStageHandler currentHandler; + + private volatile boolean terminated; + + public PipelineProcessor(Long taskId) { + this.taskId = taskId; + this.stageHandlers = Maps.newLinkedMap(); + } + + @Override + public void exec() { + log.info("已提交应用流水线任务 id: {}", taskId); + Threads.start(this, SchedulerPools.PIPELINE_SCHEDULER); + } + + @Override + public void run() { + log.info("开始执行应用流水线 id: {}", taskId); + Exception ex = null; + try { + // 获取流水线数据 + this.getPipelineData(); + // 检查状态 + if (task != null && !PipelineStatus.WAIT_RUNNABLE.getStatus().equals(task.getExecStatus()) + && !PipelineStatus.WAIT_SCHEDULE.getStatus().equals(task.getExecStatus())) { + return; + } + // 修改状态 + this.updateStatus(PipelineStatus.RUNNABLE); + // 添加会话 + pipelineSessionHolder.addSession(this); + // 执行 + this.handlerPipeline(); + } catch (Exception e) { + ex = e; + } + try { + // 回调 + if (terminated) { + // 停止回调 + this.terminatedCallback(); + } else if (ex == null) { + // 成功回调 + this.completeCallback(); + } else { + // 异常回调 + this.exceptionCallback(ex); + } + } finally { + pipelineSessionHolder.removeSession(taskId); + } + } + + /** + * 获取流水线数据 + */ + private void getPipelineData() { + // 获取主表 + this.task = applicationPipelineTaskDAO.selectById(taskId); + if (task == null) { + return; + } + PipelineStatus status = PipelineStatus.of(task.getExecStatus()); + if (!PipelineStatus.WAIT_RUNNABLE.equals(status) + && !PipelineStatus.WAIT_SCHEDULE.equals(status)) { + throw Exceptions.argument(MessageConst.ILLEGAL_STATUS); + } + // 获取详情 + List details = applicationPipelineTaskDetailService.selectTaskDetails(taskId); + for (ApplicationPipelineTaskDetailDO detail : details) { + stageHandlers.put(detail.getId(), IStageHandler.with(task, detail)); + } + } + + /** + * 执行流水线操作 + * + * @throws Exception exception + */ + private void handlerPipeline() throws Exception { + Exception ex = null; + Collection handlers = stageHandlers.values(); + for (IStageHandler stageHandler : handlers) { + this.currentHandler = stageHandler; + // 停止或异常则跳过 + if (terminated || ex != null) { + stageHandler.skip(); + continue; + } + // 执行 + try { + stageHandler.exec(); + } catch (Exception e) { + ex = e; + } + } + this.currentHandler = null; + // 异常返回 + if (ex != null) { + throw ex; + } + // 全部停止 + final boolean allTerminated = handlers.stream() + .map(IStageHandler::getStatus) + .filter(s -> !PipelineDetailStatus.SKIPPED.equals(s)) + .allMatch(PipelineDetailStatus.TERMINATED::equals); + if (allTerminated) { + this.terminated = true; + } + } + + /** + * 停止回调 + */ + private void terminatedCallback() { + log.info("应用流水线执行停止 id: {}", taskId); + this.updateStatus(PipelineStatus.TERMINATED); + } + + /** + * 完成回调 + */ + private void completeCallback() { + log.info("应用流水线执行完成 id: {}", taskId); + this.updateStatus(PipelineStatus.FINISH); + // 发送站内信 + Map params = Maps.newMap(); + params.put(EventKeys.ID, task.getId()); + params.put(EventKeys.NAME, task.getPipelineName()); + params.put(EventKeys.TITLE, task.getExecTitle()); + webSideMessageService.addMessage(MessageType.PIPELINE_EXEC_SUCCESS, task.getId(), task.getExecUserId(), task.getExecUserName(), params); + + } + + /** + * 异常回调 + * + * @param ex ex + */ + private void exceptionCallback(Exception ex) { + log.error("应用流水线执行失败 id: {}", taskId, ex); + this.updateStatus(PipelineStatus.FAILURE); + // 发送站内信 + Map params = Maps.newMap(); + params.put(EventKeys.ID, task.getId()); + params.put(EventKeys.NAME, task.getPipelineName()); + params.put(EventKeys.TITLE, task.getExecTitle()); + webSideMessageService.addMessage(MessageType.PIPELINE_EXEC_FAILURE, task.getId(), task.getExecUserId(), task.getExecUserName(), params); + } + + /** + * 更新状态 + * + * @param status status + */ + private void updateStatus(PipelineStatus status) { + Date now = new Date(); + ApplicationPipelineTaskDO update = new ApplicationPipelineTaskDO(); + update.setId(taskId); + update.setExecStatus(status.getStatus()); + update.setUpdateTime(now); + switch (status) { + case RUNNABLE: + update.setExecStartTime(now); + break; + case FINISH: + case TERMINATED: + case FAILURE: + update.setExecEndTime(now); + break; + default: + break; + } + applicationPipelineTaskDAO.updateById(update); + } + + @Override + public void terminate() { + log.info("应用流水线执行停止 id: {}", taskId); + this.terminated = true; + if (currentHandler != null) { + currentHandler.terminate(); + } + } + + @Override + public void terminateDetail(Long id) { + IStageHandler stageHandler = stageHandlers.get(id); + if (stageHandler != null) { + stageHandler.terminate(); + } + } + + @Override + public void skipDetail(Long id) { + IStageHandler stageHandler = stageHandlers.get(id); + if (stageHandler != null) { + stageHandler.skip(); + } + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/PipelineSessionHolder.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/PipelineSessionHolder.java new file mode 100644 index 0000000..53cae9e --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/PipelineSessionHolder.java @@ -0,0 +1,45 @@ + +package cn.orionsec.ops.handler.app.pipeline; + +import cn.orionsec.kit.lang.utils.collect.Maps; +import org.springframework.stereotype.Component; + +import java.util.concurrent.ConcurrentHashMap; + +@Component +public class PipelineSessionHolder { + + /** + * session + */ + private final ConcurrentHashMap session = Maps.newCurrentHashMap(); + + /** + * 添加 session + * + * @param processor processor + */ + public void addSession(IPipelineProcessor processor) { + session.put(processor.getTaskId(), processor); + } + + /** + * 获取 session + * + * @param id id + * @return session + */ + public IPipelineProcessor getSession(Long id) { + return session.get(id); + } + + /** + * 移除 session + * + * @param id id + */ + public void removeSession(Long id) { + session.remove(id); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/stage/AbstractStageHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/stage/AbstractStageHandler.java new file mode 100644 index 0000000..6552edd --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/stage/AbstractStageHandler.java @@ -0,0 +1,250 @@ + +package cn.orionsec.ops.handler.app.pipeline.stage; + +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.app.PipelineDetailStatus; +import cn.orionsec.ops.constant.app.PipelineLogStatus; +import cn.orionsec.ops.constant.app.StageType; +import cn.orionsec.ops.constant.user.RoleType; +import cn.orionsec.ops.dao.ApplicationPipelineTaskDetailDAO; +import cn.orionsec.ops.dao.ApplicationPipelineTaskLogDAO; +import cn.orionsec.ops.dao.UserInfoDAO; +import cn.orionsec.ops.entity.domain.ApplicationPipelineTaskDO; +import cn.orionsec.ops.entity.domain.ApplicationPipelineTaskDetailDO; +import cn.orionsec.ops.entity.domain.ApplicationPipelineTaskLogDO; +import cn.orionsec.ops.entity.domain.UserInfoDO; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.utils.UserHolder; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import java.util.Date; +import java.util.Optional; + +@Slf4j +public abstract class AbstractStageHandler implements IStageHandler { + + protected static final ApplicationPipelineTaskDetailDAO applicationPipelineTaskDetailDAO = SpringHolder.getBean(ApplicationPipelineTaskDetailDAO.class); + + protected static final ApplicationPipelineTaskLogDAO applicationPipelineTaskLogDAO = SpringHolder.getBean(ApplicationPipelineTaskLogDAO.class); + + protected static final UserInfoDAO userInfoDAO = SpringHolder.getBean(UserInfoDAO.class); + + protected Long detailId; + + protected ApplicationPipelineTaskDO task; + + protected ApplicationPipelineTaskDetailDO detail; + + protected StageType stageType; + + @Getter + protected volatile PipelineDetailStatus status; + + protected volatile boolean terminated; + + public AbstractStageHandler(ApplicationPipelineTaskDO task, ApplicationPipelineTaskDetailDO detail) { + this.task = task; + this.detail = detail; + this.detailId = detail.getId(); + this.status = PipelineDetailStatus.WAIT; + } + + @Override + public void exec() { + log.info("流水线阶段操作-开始执行 detailId: {}", detailId); + // 状态检查 + if (!this.checkCanRunnable()) { + return; + } + Exception ex = null; + // 执行 + try { + // 更新状态 + this.updateStatus(PipelineDetailStatus.RUNNABLE); + // 执行操作 + this.execStageTask(); + } catch (Exception e) { + ex = e; + } + // 回调 + try { + if (terminated) { + // 停止回调 + this.terminatedCallback(); + } else if (ex == null) { + // 成功回调 + this.successCallback(); + } else { + // 异常回调 + this.exceptionCallback(ex); + throw Exceptions.runtime(ex.getMessage(), ex); + } + } finally { + this.close(); + } + } + + /** + * 执行操作任务 + */ + protected abstract void execStageTask(); + + /** + * 检查是否可执行 + * + * @return 是否可执行 + */ + protected boolean checkCanRunnable() { + ApplicationPipelineTaskDetailDO detail = applicationPipelineTaskDetailDAO.selectById(detailId); + if (detail == null) { + return false; + } + return PipelineDetailStatus.WAIT.getStatus().equals(detail.getExecStatus()); + } + + /** + * 停止回调 + */ + protected void terminatedCallback() { + log.info("流水线阶段操作-终止回调 detailId: {}", detailId); + // 修改状态 + this.updateStatus(PipelineDetailStatus.TERMINATED); + } + + /** + * 成功回调 + */ + protected void successCallback() { + log.info("流水线阶段操作-成功回调 detailId: {}", detailId); + // 修改状态 + this.updateStatus(PipelineDetailStatus.FINISH); + } + + /** + * 异常回调 + * + * @param ex ex + */ + protected void exceptionCallback(Exception ex) { + log.error("流水线阶段操作-异常回调 detailId: {}", detailId, ex); + // 修改状态 + this.updateStatus(PipelineDetailStatus.FAILURE); + } + + /** + * 更新状态 + * + * @param status 状态 + */ + protected void updateStatus(PipelineDetailStatus status) { + this.status = status; + Date now = new Date(); + ApplicationPipelineTaskDetailDO update = new ApplicationPipelineTaskDetailDO(); + update.setId(detailId); + update.setExecStatus(status.getStatus()); + update.setUpdateTime(now); + switch (status) { + case RUNNABLE: + update.setExecStartTime(now); + break; + case FINISH: + case FAILURE: + case TERMINATED: + update.setExecEndTime(now); + break; + default: + break; + } + // 更新 + applicationPipelineTaskDetailDAO.updateById(update); + // 插入日志 + String appName = detail.getAppName(); + switch (status) { + case FINISH: + this.addLog(PipelineLogStatus.SUCCESS, appName); + break; + case FAILURE: + this.addLog(PipelineLogStatus.FAILURE, appName); + break; + case SKIPPED: + this.addLog(PipelineLogStatus.SKIP, appName); + break; + case TERMINATED: + this.addLog(PipelineLogStatus.TERMINATED, appName); + break; + default: + break; + } + } + + @Override + public void terminate() { + log.info("流水线阶段操作-终止 detailId: {}", detailId); + this.terminated = true; + } + + @Override + public void skip() { + log.info("流水线阶段操作-跳过 detailId: {}", detailId); + if (PipelineDetailStatus.WAIT.equals(status)) { + // 只能跳过等待中的任务 + this.updateStatus(PipelineDetailStatus.SKIPPED); + } + } + + /** + * 设置引用id + * + * @param relId relId + */ + protected void setRelId(Long relId) { + ApplicationPipelineTaskDetailDO update = new ApplicationPipelineTaskDetailDO(); + update.setId(detailId); + update.setRelId(relId); + update.setUpdateTime(new Date()); + applicationPipelineTaskDetailDAO.updateById(update); + } + + /** + * 添加日志 + * + * @param logStatus logStatus + * @param params 日志参数 + */ + protected void addLog(PipelineLogStatus logStatus, Object... params) { + ApplicationPipelineTaskLogDO log = new ApplicationPipelineTaskLogDO(); + log.setTaskId(task.getId()); + log.setTaskDetailId(detail.getId()); + log.setLogStatus(logStatus.getStatus()); + log.setStageType(stageType.getType()); + log.setLogInfo(logStatus.format(stageType, params)); + applicationPipelineTaskLogDAO.insert(log); + } + + /** + * 设置执行人用户上下文 + */ + protected void setExecuteUserContext() { + UserInfoDO userInfo = userInfoDAO.selectById(task.getExecUserId()); + UserDTO user = new UserDTO(); + user.setId(task.getExecUserId()); + user.setUsername(task.getExecUserName()); + Integer roleType = Optional.ofNullable(userInfo) + .map(UserInfoDO::getRoleType) + .orElseGet(() -> task.getExecUserId().equals(task.getAuditUserId()) + ? RoleType.ADMINISTRATOR.getType() + : RoleType.DEVELOPER.getType()); + user.setRoleType(roleType); + UserHolder.set(user); + } + + /** + * 释放资源 + */ + protected void close() { + UserHolder.remove(); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/stage/BuildStageHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/stage/BuildStageHandler.java new file mode 100644 index 0000000..4697160 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/stage/BuildStageHandler.java @@ -0,0 +1,93 @@ + +package cn.orionsec.ops.handler.app.pipeline.stage; + +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.app.BuildStatus; +import cn.orionsec.ops.constant.app.PipelineLogStatus; +import cn.orionsec.ops.constant.app.StageType; +import cn.orionsec.ops.dao.ApplicationBuildDAO; +import cn.orionsec.ops.entity.domain.ApplicationBuildDO; +import cn.orionsec.ops.entity.domain.ApplicationPipelineTaskDO; +import cn.orionsec.ops.entity.domain.ApplicationPipelineTaskDetailDO; +import cn.orionsec.ops.entity.dto.app.ApplicationPipelineStageConfigDTO; +import cn.orionsec.ops.entity.request.app.ApplicationBuildRequest; +import cn.orionsec.ops.handler.app.build.BuildSessionHolder; +import cn.orionsec.ops.handler.app.machine.BuildMachineProcessor; +import cn.orionsec.ops.handler.app.machine.IMachineProcessor; +import cn.orionsec.ops.service.api.ApplicationBuildService; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class BuildStageHandler extends AbstractStageHandler { + + private Long buildId; + + private static final ApplicationBuildService applicationBuildService = SpringHolder.getBean(ApplicationBuildService.class); + + private static final ApplicationBuildDAO applicationBuildDAO = SpringHolder.getBean(ApplicationBuildDAO.class); + + private static final BuildSessionHolder buildSessionHolder = SpringHolder.getBean(BuildSessionHolder.class); + + public BuildStageHandler(ApplicationPipelineTaskDO task, ApplicationPipelineTaskDetailDO detail) { + super(task, detail); + this.stageType = StageType.BUILD; + } + + @Override + protected void execStageTask() { + // 设置用户上下文 + this.setExecuteUserContext(); + // 参数 + ApplicationBuildRequest request = new ApplicationBuildRequest(); + request.setAppId(detail.getAppId()); + request.setProfileId(task.getProfileId()); + // 配置 + ApplicationPipelineStageConfigDTO config = JSON.parseObject(detail.getStageConfig(), ApplicationPipelineStageConfigDTO.class); + request.setBranchName(config.getBranchName()); + request.setCommitId(config.getCommitId()); + request.setDescription(config.getDescription()); + log.info("执行流水线任务-构建阶段-开始创建 detailId: {}, 参数: {}", detailId, JSON.toJSONString(request)); + // 创建构建任务 + this.buildId = applicationBuildService.submitBuildTask(request, false); + // 设置构建id + this.setRelId(buildId); + // 插入创建日志 + ApplicationBuildDO build = applicationBuildDAO.selectById(buildId); + this.addLog(PipelineLogStatus.CREATE, detail.getAppName(), build.getBuildSeq()); + log.info("执行流水线任务-构建阶段-创建完成开始执行 detailId: {}, buildId: {}", detailId, buildId); + // 插入执行日志 + this.addLog(PipelineLogStatus.EXEC, detail.getAppName()); + // 执行构建任务 + new BuildMachineProcessor(buildId).run(); + // 检查执行结果 + build = applicationBuildDAO.selectById(buildId); + if (BuildStatus.FAILURE.getStatus().equals(build.getBuildStatus())) { + // 异常抛出 + throw Exceptions.runtime(MessageConst.OPERATOR_ERROR); + } else if (BuildStatus.TERMINATED.getStatus().equals(build.getBuildStatus())) { + this.terminated = true; + } + } + + @Override + public void terminate() { + super.terminate(); + // 获取数据 + ApplicationBuildDO build = applicationBuildDAO.selectById(buildId); + // 检查状态 + if (!BuildStatus.RUNNABLE.getStatus().equals(build.getBuildStatus())) { + return; + } + // 获取实例 + IMachineProcessor session = buildSessionHolder.getSession(buildId); + if (session == null) { + return; + } + // 调用终止 + session.terminate(); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/stage/IStageHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/stage/IStageHandler.java new file mode 100644 index 0000000..4e04716 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/stage/IStageHandler.java @@ -0,0 +1,49 @@ + +package cn.orionsec.ops.handler.app.pipeline.stage; + +import cn.orionsec.kit.lang.able.Executable; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.ops.constant.app.PipelineDetailStatus; +import cn.orionsec.ops.constant.app.StageType; +import cn.orionsec.ops.entity.domain.ApplicationPipelineTaskDO; +import cn.orionsec.ops.entity.domain.ApplicationPipelineTaskDetailDO; + +public interface IStageHandler extends Executable { + + /** + * 停止执行 + */ + void terminate(); + + /** + * 跳过执行 + */ + void skip(); + + /** + * 获取状态 + * + * @return status + */ + PipelineDetailStatus getStatus(); + + /** + * 获取阶段处理器 + * + * @param task task + * @param detail detail + * @return 阶段处理器 + */ + static IStageHandler with(ApplicationPipelineTaskDO task, ApplicationPipelineTaskDetailDO detail) { + StageType stageType = StageType.of(detail.getStageType()); + switch (stageType) { + case BUILD: + return new BuildStageHandler(task, detail); + case RELEASE: + return new ReleaseStageHandler(task, detail); + default: + throw Exceptions.argument(); + } + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/stage/ReleaseStageHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/stage/ReleaseStageHandler.java new file mode 100644 index 0000000..2a9309e --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/pipeline/stage/ReleaseStageHandler.java @@ -0,0 +1,183 @@ + +package cn.orionsec.ops.handler.app.pipeline.stage; + +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.CnConst; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.app.PipelineLogStatus; +import cn.orionsec.ops.constant.app.ReleaseStatus; +import cn.orionsec.ops.constant.app.StageType; +import cn.orionsec.ops.dao.ApplicationBuildDAO; +import cn.orionsec.ops.dao.ApplicationReleaseDAO; +import cn.orionsec.ops.entity.domain.*; +import cn.orionsec.ops.entity.dto.app.ApplicationPipelineStageConfigDTO; +import cn.orionsec.ops.entity.request.app.ApplicationReleaseRequest; +import cn.orionsec.ops.handler.app.release.IReleaseProcessor; +import cn.orionsec.ops.handler.app.release.ReleaseSessionHolder; +import cn.orionsec.ops.service.api.ApplicationMachineService; +import cn.orionsec.ops.service.api.ApplicationReleaseService; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; + +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +public class ReleaseStageHandler extends AbstractStageHandler { + + private static final ApplicationBuildDAO applicationBuildDAO = SpringHolder.getBean(ApplicationBuildDAO.class); + + private static final ApplicationReleaseDAO applicationReleaseDAO = SpringHolder.getBean(ApplicationReleaseDAO.class); + + private static final ApplicationReleaseService applicationReleaseService = SpringHolder.getBean(ApplicationReleaseService.class); + + private static final ApplicationMachineService applicationMachineService = SpringHolder.getBean(ApplicationMachineService.class); + + private static final ReleaseSessionHolder releaseSessionHolder = SpringHolder.getBean(ReleaseSessionHolder.class); + + private Long releaseId; + + private ApplicationReleaseDO release; + + public ReleaseStageHandler(ApplicationPipelineTaskDO task, ApplicationPipelineTaskDetailDO detail) { + super(task, detail); + this.stageType = StageType.RELEASE; + } + + @Override + protected void execStageTask() { + // 创建 + this.createReleaseTask(); + // 审核 + this.auditReleaseTask(); + // 执行 + this.execReleaseTask(); + } + + /** + * 创建发布任务 + */ + protected void createReleaseTask() { + // 设置用户上下文 + this.setExecuteUserContext(); + Long profileId = task.getProfileId(); + Long appId = detail.getAppId(); + // 创建发布任务参数 + ApplicationReleaseRequest request = new ApplicationReleaseRequest(); + request.setProfileId(profileId); + request.setAppId(appId); + // 其他配置 + ApplicationPipelineStageConfigDTO config = JSON.parseObject(detail.getStageConfig(), ApplicationPipelineStageConfigDTO.class); + request.setDescription(config.getDescription()); + // 标题 + request.setTitle(Strings.def(config.getTitle(), () -> CnConst.RELEASE + Const.SPACE + detail.getAppName())); + // 构建id + Long buildId = config.getBuildId(); + ApplicationBuildDO build = null; + if (buildId == null) { + // 最新版本 + List buildList = applicationBuildDAO.selectBuildReleaseList(appId, profileId, 1); + if (!buildList.isEmpty()) { + build = buildList.get(0); + buildId = build.getId(); + } + } else { + build = applicationBuildDAO.selectById(buildId); + } + if (build == null) { + throw Exceptions.argument(Strings.format(MessageConst.APP_LAST_BUILD_VERSION_ABSENT, detail.getAppName())); + } + request.setBuildId(buildId); + config.setBuildId(buildId); + // 发布机器 + List machineIdList = config.getMachineIdList(); + if (Lists.isEmpty(machineIdList)) { + // 全部机器 + machineIdList = applicationMachineService.getAppProfileMachineList(appId, profileId).stream() + .map(ApplicationMachineDO::getMachineId) + .collect(Collectors.toList()); + } + request.setMachineIdList(machineIdList); + config.setMachineIdList(machineIdList); + // 更新详情配置 + String configJson = JSON.toJSONString(config); + detail.setStageConfig(configJson); + ApplicationPipelineTaskDetailDO updateConfig = new ApplicationPipelineTaskDetailDO(); + updateConfig.setId(detailId); + updateConfig.setStageConfig(configJson); + applicationPipelineTaskDetailDAO.updateById(updateConfig); + // 创建发布任务 + log.info("执行流水线任务-发布阶段-开始创建 detailId: {}, 参数: {}", detailId, JSON.toJSONString(request)); + this.releaseId = applicationReleaseService.submitAppRelease(request); + // 设置发布id + this.setRelId(releaseId); + // 插入日志 + this.addLog(PipelineLogStatus.CREATE, detail.getAppName(), build.getBuildSeq()); + } + + /** + * 审核发布任务 + */ + private void auditReleaseTask() { + // 查询发布任务 + this.release = applicationReleaseDAO.selectById(releaseId); + if (!ReleaseStatus.WAIT_AUDIT.getStatus().equals(release.getReleaseStatus())) { + return; + } + ApplicationReleaseDO update = new ApplicationReleaseDO(); + update.setId(releaseId); + update.setAuditUserId(task.getAuditUserId()); + update.setAuditUserName(task.getAuditUserName()); + update.setAuditReason(MessageConst.AUTO_AUDIT_RESOLVE); + update.setAuditTime(new Date()); + log.info("执行流水线任务-发布阶段-审核 detailId: {}, releaseId: {}, 参数: {}", detailId, releaseId, JSON.toJSONString(update)); + applicationReleaseDAO.updateById(update); + } + + /** + * 执行发布任务 + */ + private void execReleaseTask() { + log.info("执行流水线任务-发布阶段-开始执行 detailId: {}, releaseId: {}", detailId, releaseId); + // 提交发布任务 + applicationReleaseService.runnableAppRelease(releaseId, true, false); + // 插入执行日志 + this.addLog(PipelineLogStatus.EXEC, detail.getAppName()); + // 执行发布任务 + IReleaseProcessor.with(release).run(); + // 检查执行结果 + this.release = applicationReleaseDAO.selectById(releaseId); + if (ReleaseStatus.FAILURE.getStatus().equals(release.getReleaseStatus())) { + // 异常抛出 + throw Exceptions.runtime(MessageConst.OPERATOR_ERROR); + } else if (ReleaseStatus.TERMINATED.getStatus().equals(release.getReleaseStatus())) { + // 停止 + this.terminated = true; + } + } + + @Override + public void terminate() { + super.terminate(); + // 获取数据 + this.release = applicationReleaseDAO.selectById(releaseId); + // 检查状态 + if (!ReleaseStatus.RUNNABLE.getStatus().equals(release.getReleaseStatus())) { + return; + } + // 获取实例 + IReleaseProcessor session = releaseSessionHolder.getSession(releaseId); + if (session == null) { + return; + } + // 调用终止 + session.terminateAll(); + } + +} + diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/release/AbstractReleaseProcessor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/release/AbstractReleaseProcessor.java new file mode 100644 index 0000000..6c49b23 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/release/AbstractReleaseProcessor.java @@ -0,0 +1,219 @@ + +package cn.orionsec.ops.handler.app.release; + +import cn.orionsec.kit.lang.utils.Threads; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.SchedulerPools; +import cn.orionsec.ops.constant.app.ReleaseStatus; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.message.MessageType; +import cn.orionsec.ops.dao.ApplicationReleaseDAO; +import cn.orionsec.ops.entity.domain.ApplicationReleaseDO; +import cn.orionsec.ops.entity.domain.ApplicationReleaseMachineDO; +import cn.orionsec.ops.handler.app.machine.ReleaseMachineProcessor; +import cn.orionsec.ops.service.api.ApplicationReleaseMachineService; +import cn.orionsec.ops.service.api.WebSideMessageService; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +@Slf4j +public abstract class AbstractReleaseProcessor implements IReleaseProcessor { + + protected static final ApplicationReleaseDAO applicationReleaseDAO = SpringHolder.getBean(ApplicationReleaseDAO.class); + + protected static final ApplicationReleaseMachineService applicationReleaseMachineService = SpringHolder.getBean(ApplicationReleaseMachineService.class); + + protected static final ReleaseSessionHolder releaseSessionHolder = SpringHolder.getBean(ReleaseSessionHolder.class); + + private static final WebSideMessageService webSideMessageService = SpringHolder.getBean(WebSideMessageService.class); + + @Getter + private final Long releaseId; + + protected ApplicationReleaseDO release; + + protected Map machineProcessors; + + protected volatile boolean terminated; + + public AbstractReleaseProcessor(Long releaseId) { + this.releaseId = releaseId; + this.machineProcessors = Maps.newLinkedMap(); + } + + @Override + public void exec() { + log.info("已提交应用发布执行任务 id: {}", releaseId); + Threads.start(this, SchedulerPools.RELEASE_MAIN_SCHEDULER); + } + + @Override + public void run() { + log.info("应用发布任务执行开始 id: {}", releaseId); + // 执行 + Exception ex = null; + try { + // 查询数据 + this.getReleaseData(); + // 检查状态 + if (release != null && !ReleaseStatus.WAIT_RUNNABLE.getStatus().equals(release.getReleaseStatus()) + && !ReleaseStatus.WAIT_SCHEDULE.getStatus().equals(release.getReleaseStatus())) { + return; + } + // 修改状态 + this.updateStatus(ReleaseStatus.RUNNABLE); + // 添加会话 + releaseSessionHolder.addSession(this); + // 执行 + this.handler(); + } catch (Exception e) { + log.error("应用发布任务执行初始化失败 id: {}, {}", releaseId, e); + ex = e; + } + // 回调 + try { + if (terminated) { + // 停止回调 + this.terminatedCallback(); + } else if (ex == null) { + // 成功回调 + this.completeCallback(); + } else { + // 异常回调 + this.exceptionCallback(ex); + } + } finally { + // 释放资源 + this.close(); + } + } + + /** + * 处理器 + * + * @throws Exception Exception + */ + protected abstract void handler() throws Exception; + + @Override + public void terminateAll() { + this.terminated = true; + } + + @Override + public void terminateMachine(Long releaseMachineId) { + ReleaseMachineProcessor processor = machineProcessors.get(releaseMachineId); + if (processor != null) { + processor.terminate(); + } + } + + @Override + public void skipMachine(Long releaseMachineId) { + ReleaseMachineProcessor processor = machineProcessors.get(releaseMachineId); + if (processor != null) { + processor.skip(); + } + } + + @Override + public void writeMachine(Long releaseMachineId, String command) { + ReleaseMachineProcessor processor = machineProcessors.get(releaseMachineId); + if (processor != null) { + processor.write(command); + } + } + + /** + * 获取发布数据 + */ + protected void getReleaseData() { + // 查询发布信息主表 + this.release = applicationReleaseDAO.selectById(releaseId); + if (release == null) { + return; + } + if (!ReleaseStatus.WAIT_RUNNABLE.getStatus().equals(release.getReleaseStatus()) + && !ReleaseStatus.WAIT_SCHEDULE.getStatus().equals(release.getReleaseStatus())) { + return; + } + // 查询发布机器 + List machines = applicationReleaseMachineService.getReleaseMachines(releaseId); + for (ApplicationReleaseMachineDO machine : machines) { + machineProcessors.put(machine.getId(), new ReleaseMachineProcessor(release, machine)); + } + } + + /** + * 停止回调 + */ + protected void terminatedCallback() { + log.info("应用发布任务执行执行停止 id: {}", releaseId); + this.updateStatus(ReleaseStatus.TERMINATED); + } + + /** + * 完成回调 + */ + protected void completeCallback() { + log.info("应用发布任务执行执行完成 id: {}", releaseId); + this.updateStatus(ReleaseStatus.FINISH); + // 发送站内信 + Map params = Maps.newMap(); + params.put(EventKeys.ID, release.getId()); + params.put(EventKeys.TITLE, release.getReleaseTitle()); + webSideMessageService.addMessage(MessageType.RELEASE_SUCCESS, release.getId(), release.getReleaseUserId(), release.getReleaseUserName(), params); + } + + /** + * 异常回调 + * + * @param ex ex + */ + protected void exceptionCallback(Exception ex) { + log.error("应用发布任务执行执行失败 id: {}", releaseId, ex); + this.updateStatus(ReleaseStatus.FAILURE); + // 发送站内信 + Map params = Maps.newMap(); + params.put(EventKeys.ID, release.getId()); + params.put(EventKeys.TITLE, release.getReleaseTitle()); + webSideMessageService.addMessage(MessageType.RELEASE_FAILURE, release.getId(), release.getReleaseUserId(), release.getReleaseUserName(), params); + } + + /** + * 更新状态 + * + * @param status status + */ + protected void updateStatus(ReleaseStatus status) { + Date now = new Date(); + ApplicationReleaseDO update = new ApplicationReleaseDO(); + update.setId(releaseId); + update.setReleaseStatus(status.getStatus()); + update.setUpdateTime(now); + switch (status) { + case RUNNABLE: + update.setReleaseStartTime(now); + break; + case FINISH: + case TERMINATED: + case FAILURE: + update.setReleaseEndTime(now); + break; + default: + break; + } + applicationReleaseDAO.updateById(update); + } + + @Override + public void close() { + releaseSessionHolder.removeSession(releaseId); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/release/IReleaseProcessor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/release/IReleaseProcessor.java new file mode 100644 index 0000000..673ef30 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/release/IReleaseProcessor.java @@ -0,0 +1,62 @@ + +package cn.orionsec.ops.handler.app.release; + +import cn.orionsec.kit.lang.able.Executable; +import cn.orionsec.kit.lang.able.SafeCloseable; +import cn.orionsec.kit.lang.function.select.Branches; +import cn.orionsec.kit.lang.function.select.Selector; +import cn.orionsec.ops.constant.common.SerialType; +import cn.orionsec.ops.entity.domain.ApplicationReleaseDO; + +public interface IReleaseProcessor extends Executable, Runnable, SafeCloseable { + + /** + * 获取id + * + * @return id + */ + Long getReleaseId(); + + /** + * 终止 + */ + void terminateAll(); + + /** + * 终止机器操作 + * + * @param releaseMachineId releaseMachineId + */ + void terminateMachine(Long releaseMachineId); + + /** + * 跳过机器操作 + * + * @param releaseMachineId 机器id + */ + void skipMachine(Long releaseMachineId); + + /** + * 输入机器命令 + * + * @param releaseMachineId 机器id + * @param command command + */ + void writeMachine(Long releaseMachineId, String command); + + /** + * 获取发布执行器 + * + * @param release release + * @return 执行器 + */ + static IReleaseProcessor with(ApplicationReleaseDO release) { + return Selector.of(SerialType.of(release.getReleaseSerialize())) + .test(Branches.eq(SerialType.SERIAL) + .then(() -> new SerialReleaseProcessor(release.getId()))) + .test(Branches.eq(SerialType.PARALLEL) + .then(() -> new ParallelReleaseProcessor(release.getId()))) + .get(); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/release/ParallelReleaseProcessor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/release/ParallelReleaseProcessor.java new file mode 100644 index 0000000..406d529 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/release/ParallelReleaseProcessor.java @@ -0,0 +1,51 @@ + +package cn.orionsec.ops.handler.app.release; + +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Threads; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.SchedulerPools; +import cn.orionsec.ops.constant.app.ActionStatus; +import cn.orionsec.ops.handler.app.machine.ReleaseMachineProcessor; + +import java.util.Collection; + +public class ParallelReleaseProcessor extends AbstractReleaseProcessor { + + public ParallelReleaseProcessor(Long releaseId) { + super(releaseId); + } + + @Override + protected void handler() throws Exception { + Collection processor = machineProcessors.values(); + Threads.blockRun(processor, SchedulerPools.RELEASE_MACHINE_SCHEDULER); + // 检查是否停止 + if (terminated) { + return; + } + // 全部停止 + final boolean allTerminated = processor.stream() + .map(ReleaseMachineProcessor::getStatus) + .allMatch(ActionStatus.TERMINATED::equals); + if (allTerminated) { + this.terminated = true; + return; + } + // 全部完成 + boolean allFinish = processor.stream() + .map(ReleaseMachineProcessor::getStatus) + .filter(s -> !ActionStatus.TERMINATED.equals(s)) + .allMatch(ActionStatus.FINISH::equals); + if (!allFinish) { + throw Exceptions.log(MessageConst.OPERATOR_NOT_ALL_SUCCESS); + } + } + + @Override + public void terminateAll() { + super.terminateAll(); + machineProcessors.values().forEach(ReleaseMachineProcessor::terminate); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/release/ReleaseSessionHolder.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/release/ReleaseSessionHolder.java new file mode 100644 index 0000000..9dbbcaf --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/release/ReleaseSessionHolder.java @@ -0,0 +1,45 @@ + +package cn.orionsec.ops.handler.app.release; + +import cn.orionsec.kit.lang.utils.collect.Maps; +import org.springframework.stereotype.Component; + +import java.util.concurrent.ConcurrentHashMap; + +@Component +public class ReleaseSessionHolder { + + /** + * session + */ + private final ConcurrentHashMap session = Maps.newCurrentHashMap(); + + /** + * 添加 session + * + * @param processor processor + */ + public void addSession(IReleaseProcessor processor) { + session.put(processor.getReleaseId(), processor); + } + + /** + * 获取 session + * + * @param id id + * @return session + */ + public IReleaseProcessor getSession(Long id) { + return session.get(id); + } + + /** + * 移除 session + * + * @param id id + */ + public void removeSession(Long id) { + session.remove(id); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/release/SerialReleaseProcessor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/release/SerialReleaseProcessor.java new file mode 100644 index 0000000..16cfa23 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/app/release/SerialReleaseProcessor.java @@ -0,0 +1,64 @@ + +package cn.orionsec.ops.handler.app.release; + +import cn.orionsec.ops.constant.app.ActionStatus; +import cn.orionsec.ops.constant.common.ExceptionHandlerType; +import cn.orionsec.ops.handler.app.machine.IMachineProcessor; +import cn.orionsec.ops.handler.app.machine.ReleaseMachineProcessor; + +import java.util.Collection; + +public class SerialReleaseProcessor extends AbstractReleaseProcessor { + + public SerialReleaseProcessor(Long releaseId) { + super(releaseId); + } + + @Override + protected void handler() throws Exception { + // 异常处理策略 + final boolean errorSkipAll = ExceptionHandlerType.SKIP_ALL.getType().equals(release.getExceptionHandler()); + Exception ex = null; + Collection processors = machineProcessors.values(); + for (ReleaseMachineProcessor processor : processors) { + // 停止则跳过 + if (terminated) { + processor.skip(); + continue; + } + // 发生异常并且异常处理策略是跳过所有则跳过 + if (ex != null && errorSkipAll) { + processor.skip(); + continue; + } + // 执行 + try { + processor.run(); + } catch (Exception e) { + ex = e; + } + } + // 异常返回 + if (ex != null) { + throw ex; + } + // 全部停止 + final boolean allTerminated = processors.stream() + .map(ReleaseMachineProcessor::getStatus) + .filter(s -> !ActionStatus.SKIPPED.equals(s)) + .allMatch(ActionStatus.TERMINATED::equals); + if (allTerminated) { + this.terminated = true; + } + } + + @Override + public void terminateAll() { + super.terminateAll(); + // 获取当前执行中的机器执行器 + machineProcessors.values().stream() + .filter(s -> s.getStatus().equals(ActionStatus.RUNNABLE)) + .forEach(IMachineProcessor::terminate); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/exec/CommandExecHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/exec/CommandExecHandler.java new file mode 100644 index 0000000..8b77e0c --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/exec/CommandExecHandler.java @@ -0,0 +1,357 @@ + +package cn.orionsec.ops.handler.exec; + +import cn.orionsec.kit.lang.constant.Letters; +import cn.orionsec.kit.lang.exception.DisabledException; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.Threads; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.lang.utils.io.Streams; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.kit.net.host.SessionStore; +import cn.orionsec.kit.net.host.ssh.ExitCode; +import cn.orionsec.kit.net.host.ssh.command.CommandExecutor; +import cn.orionsec.kit.net.host.ssh.command.CommandExecutors; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.SchedulerPools; +import cn.orionsec.ops.constant.command.ExecStatus; +import cn.orionsec.ops.constant.common.StainCode; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.message.MessageType; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.dao.CommandExecDAO; +import cn.orionsec.ops.entity.domain.CommandExecDO; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.handler.tail.TailSessionHolder; +import cn.orionsec.ops.service.api.MachineInfoService; +import cn.orionsec.ops.service.api.WebSideMessageService; +import cn.orionsec.ops.utils.Utils; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.OutputStream; +import java.util.Date; +import java.util.Map; + +@Slf4j +public class CommandExecHandler implements IExecHandler { + + private static final CommandExecDAO commandExecDAO = SpringHolder.getBean(CommandExecDAO.class); + + private static final MachineInfoService machineInfoService = SpringHolder.getBean(MachineInfoService.class); + + private static final ExecSessionHolder execSessionHolder = SpringHolder.getBean(ExecSessionHolder.class); + + private static final TailSessionHolder tailSessionHolder = SpringHolder.getBean(TailSessionHolder.class); + + private static final WebSideMessageService webSideMessageService = SpringHolder.getBean(WebSideMessageService.class); + + private final Long execId; + + private CommandExecDO record; + + private MachineInfoDO machine; + + private SessionStore sessionStore; + + private CommandExecutor executor; + + private int exitCode; + + private String logPath; + + private OutputStream logOutputStream; + + private Date startTime, endTime; + + private volatile boolean terminated; + + protected CommandExecHandler(Long execId) { + this.execId = execId; + } + + @Override + public void exec() { + log.info("execHandler-提交 execId: {}", execId); + Threads.start(this, SchedulerPools.EXEC_SCHEDULER); + } + + @Override + public void run() { + log.info("execHandler-执行开始 execId: {}", execId); + // 获取执行数据 + this.getExecData(); + // 检查状态 + if (record == null || !ExecStatus.WAITING.getStatus().equals(record.getExecStatus())) { + return; + } + // 执行 + Exception ex = null; + try { + // 更新状态 + this.updateStatus(ExecStatus.RUNNABLE); + execSessionHolder.addSession(execId, this); + // 打开日志 + this.openLogger(); + // 打开executor + this.sessionStore = machineInfoService.openSessionStore(machine); + this.executor = sessionStore.getCommandExecutor(Strings.replaceCRLF(record.getExecCommand())); + // 执行命令 + CommandExecutors.execCommand(executor, logOutputStream); + } catch (Exception e) { + ex = e; + } + // 回调 + try { + if (terminated) { + // 停止回调 + this.terminatedCallback(); + } else if (ex == null) { + // 完成回调 + this.completeCallback(); + } else if (ex instanceof DisabledException) { + // 机器未启用回调 + this.machineDisableCallback(); + } else { + // 执行失败回调 + this.exceptionCallback(ex); + } + } finally { + // 释放资源 + this.close(); + } + } + + /** + * 获取执行数据 + */ + private void getExecData() { + this.record = commandExecDAO.selectById(execId); + if (record == null) { + return; + } + if (!ExecStatus.WAITING.getStatus().equals(record.getExecStatus())) { + return; + } + // 查询机器信息 + this.machine = machineInfoService.selectById(record.getMachineId()); + // 设置日志信息 + File logFile = new File(Files1.getPath(SystemEnvAttr.LOG_PATH.getValue(), record.getLogPath())); + Files1.touch(logFile); + this.logPath = logFile.getAbsolutePath(); + } + + @Override + public void write(String out) { + executor.write(out); + } + + @Override + public void terminate() { + log.info("execHandler-停止 execId: {}", execId); + this.terminated = true; + Streams.close(executor); + } + + /** + * 打开日志 + */ + @SneakyThrows + private void openLogger() { + // 打开日志流 + log.info("execHandler-打开日志流 {} {}", execId, logPath); + File logFile = new File(logPath); + this.logOutputStream = Files1.openOutputStreamFast(logFile); + StringBuilder sb = new StringBuilder() + .append(Utils.getStainKeyWords("# 准备执行命令", StainCode.GLOSS_GREEN)) + .append(Letters.LF) + .append("@ssh: ") + .append(StainCode.prefix(StainCode.GLOSS_BLUE)) + .append(machine.getUsername()).append("@") + .append(machine.getMachineHost()).append(":") + .append(machine.getSshPort()) + .append(StainCode.SUFFIX) + .append(Letters.LF); + sb.append("执行用户: ") + .append(Utils.getStainKeyWords(record.getUserName(), StainCode.GLOSS_BLUE)) + .append(Letters.LF); + sb.append("执行任务: ") + .append(Utils.getStainKeyWords(execId, StainCode.GLOSS_BLUE)) + .append(Letters.LF); + sb.append("执行机器: ") + .append(Utils.getStainKeyWords(machine.getMachineName(), StainCode.GLOSS_BLUE)) + .append(Letters.LF); + sb.append("开始时间: ") + .append(Utils.getStainKeyWords(Dates.format(startTime), StainCode.GLOSS_BLUE)) + .append(Letters.LF); + String description = record.getDescription(); + if (!Strings.isBlank(description)) { + sb.append("执行描述: ") + .append(Utils.getStainKeyWords(description, StainCode.GLOSS_BLUE)) + .append(Letters.LF); + } + sb.append(Letters.LF) + .append(Utils.getStainKeyWords("# 执行命令", StainCode.GLOSS_GREEN)) + .append(Letters.LF) + .append(StainCode.prefix(StainCode.GLOSS_CYAN)) + .append(Utils.getEndLfWithEof(record.getExecCommand())) + .append(StainCode.SUFFIX) + .append(Utils.getStainKeyWords("# 开始执行", StainCode.GLOSS_GREEN)) + .append(Letters.LF); + logOutputStream.write(Strings.bytes(sb.toString())); + logOutputStream.flush(); + } + + /** + * 停止回调 + */ + private void terminatedCallback() { + log.info("execHandler-执行停止 execId: {}", execId); + // 更新状态 + this.updateStatus(ExecStatus.TERMINATED); + // 拼接日志 + StringBuilder log = new StringBuilder(Const.LF) + .append(Utils.getStainKeyWords("# 命令执行停止", StainCode.GLOSS_YELLOW)) + .append(Letters.TAB) + .append(Utils.getStainKeyWords(Dates.format(endTime), StainCode.GLOSS_BLUE)) + .append(Const.LF); + this.appendLog(log.toString()); + } + + /** + * 完成回调 + */ + private void completeCallback() { + this.exitCode = executor.getExitCode(); + log.info("execHandler-执行完成 execId: {} exitCode: {}", execId, exitCode); + // 更新状态 + this.updateStatus(ExecStatus.COMPLETE); + // 拼接日志 + long used = endTime.getTime() - startTime.getTime(); + StringBuilder sb = new StringBuilder() + .append(Letters.LF) + .append(Utils.getStainKeyWords("# 命令执行完毕", StainCode.GLOSS_GREEN)) + .append(Letters.LF); + sb.append("exitcode: ") + .append(ExitCode.isSuccess(exitCode) + ? Utils.getStainKeyWords(exitCode, StainCode.GLOSS_BLUE) + : Utils.getStainKeyWords(exitCode, StainCode.GLOSS_RED)) + .append(Letters.LF); + sb.append("结束时间: ") + .append(Utils.getStainKeyWords(Dates.format(endTime), StainCode.GLOSS_BLUE)) + .append(" used ") + .append(Utils.getStainKeyWords(Utils.interval(used), StainCode.GLOSS_BLUE)) + .append(" (") + .append(StainCode.prefix(StainCode.GLOSS_BLUE)) + .append(used) + .append("ms") + .append(StainCode.SUFFIX) + .append(")\n"); + this.appendLog(sb.toString()); + // 发送站内信 + Map params = Maps.newMap(); + params.put(EventKeys.ID, record.getId()); + params.put(EventKeys.NAME, record.getMachineName()); + webSideMessageService.addMessage(MessageType.EXEC_SUCCESS, record.getId(), record.getUserId(), record.getUserName(), params); + } + + /** + * 机器未启用回调 + */ + private void machineDisableCallback() { + log.info("execHandler-机器停用停止 execId: {}", execId); + // 更新状态 + this.updateStatus(ExecStatus.TERMINATED); + // 拼接日志 + StringBuilder log = new StringBuilder() + .append(Const.LF) + .append(Utils.getStainKeyWords("# 命令执行机器未启用", StainCode.GLOSS_YELLOW)) + .append(Letters.TAB) + .append(Utils.getStainKeyWords(Dates.format(endTime), StainCode.GLOSS_BLUE)) + .append(Const.LF); + this.appendLog(log.toString()); + } + + /** + * 异常回调 + * + * @param e e + */ + private void exceptionCallback(Exception e) { + log.error("execHandler-执行失败 execId: {}", execId, e); + // 更新状态 + this.updateStatus(ExecStatus.EXCEPTION); + // 拼接日志 + StringBuilder log = new StringBuilder() + .append(Const.LF) + .append(Utils.getStainKeyWords("# 命令执行异常", StainCode.GLOSS_RED)) + .append(Letters.TAB) + .append(Utils.getStainKeyWords(Dates.format(endTime), StainCode.GLOSS_BLUE)) + .append(Letters.LF) + .append(Exceptions.getStackTraceAsString(e)) + .append(Const.LF); + this.appendLog(log.toString()); + // 发送站内信 + Map params = Maps.newMap(); + params.put(EventKeys.ID, record.getId()); + params.put(EventKeys.NAME, record.getMachineName()); + webSideMessageService.addMessage(MessageType.EXEC_FAILURE, record.getId(), record.getUserId(), record.getUserName(), params); + } + + @SneakyThrows + private void appendLog(String log) { + logOutputStream.write(Strings.bytes(log)); + logOutputStream.flush(); + } + + /** + * 更新状态 + * + * @param status status + */ + private void updateStatus(ExecStatus status) { + Date now = new Date(); + // 更新 + CommandExecDO update = new CommandExecDO(); + update.setId(execId); + update.setExecStatus(status.getStatus()); + update.setUpdateTime(now); + switch (status) { + case RUNNABLE: + this.startTime = now; + update.setStartDate(now); + break; + case COMPLETE: + this.endTime = now; + update.setEndDate(now); + update.setExitCode(exitCode); + break; + case EXCEPTION: + case TERMINATED: + this.endTime = now; + update.setEndDate(now); + break; + default: + } + int effect = commandExecDAO.updateById(update); + log.info("execHandler-更新状态 id: {}, status: {}, effect: {}", execId, status, effect); + } + + @Override + public void close() { + log.info("execHandler-关闭 id: {}", execId); + // 移除会话 + execSessionHolder.removeSession(execId); + // 释放资源 + Streams.close(executor); + Streams.close(sessionStore); + Streams.close(logOutputStream); + // 异步关闭正在tail的日志 + tailSessionHolder.asyncCloseTailFile(Const.HOST_MACHINE_ID, logPath); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/exec/ExecSessionHolder.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/exec/ExecSessionHolder.java new file mode 100644 index 0000000..946e99c --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/exec/ExecSessionHolder.java @@ -0,0 +1,48 @@ + +package cn.orionsec.ops.handler.exec; + +import cn.orionsec.kit.lang.utils.collect.Maps; +import org.springframework.stereotype.Component; + +import java.util.Map; + +@Component +public class ExecSessionHolder { + + /** + * key: execId + * value: IExecHandler + */ + private final Map holder = Maps.newCurrentHashMap(); + + /** + * 添加session + * + * @param id id + * @param session session + */ + public void addSession(Long id, IExecHandler session) { + holder.put(id, session); + } + + /** + * 获取session + * + * @param id id + * @return session + */ + public IExecHandler getSession(Long id) { + return holder.get(id); + } + + /** + * 删除session + * + * @param id id + * @return session + */ + public IExecHandler removeSession(Long id) { + return holder.remove(id); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/exec/IExecHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/exec/IExecHandler.java new file mode 100644 index 0000000..32bf917 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/exec/IExecHandler.java @@ -0,0 +1,31 @@ + +package cn.orionsec.ops.handler.exec; + +import cn.orionsec.kit.lang.able.Executable; +import cn.orionsec.kit.lang.able.SafeCloseable; + +public interface IExecHandler extends Runnable, Executable, SafeCloseable { + + /** + * 写入 + * + * @param out out + */ + void write(String out); + + /** + * 停止 + */ + void terminate(); + + /** + * 获取实际执行 handler + * + * @param execId execId + * @return handler + */ + static IExecHandler with(Long execId) { + return new CommandExecHandler(execId); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/http/HttpApiDefined.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/http/HttpApiDefined.java new file mode 100644 index 0000000..4e06e87 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/http/HttpApiDefined.java @@ -0,0 +1,22 @@ + +package cn.orionsec.ops.handler.http; + +import cn.orionsec.kit.http.support.HttpMethod; + +public interface HttpApiDefined { + + /** + * 请求路径 + * + * @return 路径 + */ + String getPath(); + + /** + * 请求方法 + * + * @return 方法 + */ + HttpMethod getMethod(); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/http/HttpApiRequest.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/http/HttpApiRequest.java new file mode 100644 index 0000000..7f9a4fa --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/http/HttpApiRequest.java @@ -0,0 +1,52 @@ + +package cn.orionsec.ops.handler.http; + +import cn.orionsec.kit.http.ok.OkRequest; +import cn.orionsec.kit.lang.constant.StandardContentType; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.TypeReference; + +public class HttpApiRequest extends OkRequest { + + public HttpApiRequest(String url, HttpApiDefined api) { + this.url = url + api.getPath(); + this.method = api.getMethod().method(); + } + + /** + * 设置 json body + * + * @param body body + * @return this + */ + public HttpApiRequest jsonBody(Object body) { + this.contentType = StandardContentType.APPLICATION_JSON; + this.body(JSON.toJSONString(body)); + return this; + } + + /** + * 请求获取 httpWrapper + * + * @param dataClass T + * @param T + * @return httpWrapper + */ + public HttpWrapper getHttpWrapper(Class dataClass) { + return this.await().getJsonObjectBody(new TypeReference>(dataClass) { + }); + } + + /** + * 请求获取 json + * + * @param type type + * @param T + * @return T + */ + public T getJson(TypeReference type) { + return this.await().getJsonObjectBody(type); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/http/HttpApiRequester.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/http/HttpApiRequester.java new file mode 100644 index 0000000..1fbea97 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/http/HttpApiRequester.java @@ -0,0 +1,72 @@ + +package cn.orionsec.ops.handler.http; + +import cn.orionsec.kit.http.ok.OkResponse; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import com.alibaba.fastjson.TypeReference; + +public interface HttpApiRequester { + + /** + * 请求 api + * + * @return OkResponse + */ + default OkResponse await() { + return this.getRequest().await(); + } + + /** + * 请求 api + * + * @param dataClass dataClass + * @param T + * @return HttpWrapper + */ + default HttpWrapper request(Class dataClass) { + return this.getRequest().getHttpWrapper(dataClass); + } + + /** + * 请求 api + * + * @param type type + * @param T + * @return T + */ + default T request(TypeReference type) { + return this.getRequest().getJson(type); + } + + /** + * 请求 api + * + * @param requestBody requestBody + * @param dataClass dataClass + * @param T + * @return HttpWrapper + */ + default HttpWrapper request(Object requestBody, Class dataClass) { + return this.getRequest().jsonBody(requestBody).getHttpWrapper(dataClass); + } + + /** + * 请求 api + * + * @param requestBody requestBody + * @param type type + * @param T + * @return T + */ + default T request(Object requestBody, TypeReference type) { + return this.getRequest().jsonBody(requestBody).getJson(type); + } + + /** + * 获取 api request + * + * @return request + */ + HttpApiRequest getRequest(); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/http/MachineMonitorHttpApi.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/http/MachineMonitorHttpApi.java new file mode 100644 index 0000000..4f7e954 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/http/MachineMonitorHttpApi.java @@ -0,0 +1,79 @@ + +package cn.orionsec.ops.handler.http; + +import cn.orionsec.kit.http.support.HttpMethod; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum MachineMonitorHttpApi implements HttpApiDefined { + + /** + * 端点 ping + */ + ENDPOINT_PING("/orion/machine-monitor-agent/api/endpoint/ping", HttpMethod.GET), + + /** + * 端点 version + */ + ENDPOINT_VERSION("/orion/machine-monitor-agent/api/endpoint/version", HttpMethod.GET), + + /** + * 端点 sync + */ + ENDPOINT_SYNC("/orion/machine-monitor-agent/api/endpoint/sync", HttpMethod.POST), + + /** + * 指标 获取机器基本指标 + */ + METRICS_BASE("/orion/machine-monitor-agent/api/metrics/base", HttpMethod.GET), + + /** + * 指标 获取系统负载 + */ + METRICS_SYSTEM_LOAD("/orion/machine-monitor-agent/api/metrics/system-load", HttpMethod.GET), + + /** + * 指标 获取硬盘名称 + */ + METRICS_DISK_NAME("/orion/machine-monitor-agent/api/metrics/disk-name", HttpMethod.GET), + + /** + * 指标 获取 top 进程 + */ + METRICS_TOP_PROCESSES("/orion/machine-monitor-agent/api/metrics/top-processes", HttpMethod.GET), + + /** + * 监控 获取cpu数据 + */ + MONITOR_CPU("/orion/machine-monitor-agent/api/monitor-statistic/cpu", HttpMethod.POST), + + /** + * 监控 获取内存数据 + */ + MONITOR_MEMORY("/orion/machine-monitor-agent/api/monitor-statistic/memory", HttpMethod.POST), + + /** + * 监控 获取网络数据 + */ + MONITOR_NET("/orion/machine-monitor-agent/api/monitor-statistic/net", HttpMethod.POST), + + /** + * 监控 获取磁盘数据 + */ + MONITOR_DISK("/orion/machine-monitor-agent/api/monitor-statistic/disk", HttpMethod.POST), + + ; + + /** + * 请求路径 + */ + private final String path; + + /** + * 请求方法 + */ + private final HttpMethod method; + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/http/MachineMonitorHttpApiRequester.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/http/MachineMonitorHttpApiRequester.java new file mode 100644 index 0000000..423bbb2 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/http/MachineMonitorHttpApiRequester.java @@ -0,0 +1,30 @@ + +package cn.orionsec.ops.handler.http; + +import cn.orionsec.ops.constant.monitor.MonitorConst; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.NoArgsConstructor; + +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MachineMonitorHttpApiRequester implements HttpApiRequester { + + private static final String TAG = "machine-monitor"; + + private String url; + + private MachineMonitorHttpApi api; + + private String accessToken; + + @Override + public HttpApiRequest getRequest() { + HttpApiRequest request = new HttpApiRequest(url, api); + request.tag(TAG); + request.header(MonitorConst.DEFAULT_ACCESS_HEADER, accessToken); + return request; + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/monitor/MonitorAgentInstallTask.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/monitor/MonitorAgentInstallTask.java new file mode 100644 index 0000000..e6ffdbe --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/monitor/MonitorAgentInstallTask.java @@ -0,0 +1,240 @@ + +package cn.orionsec.ops.handler.monitor; + +import cn.orionsec.kit.lang.constant.Letters; +import cn.orionsec.kit.lang.utils.Arrays1; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.Threads; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.lang.utils.io.Streams; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.kit.net.host.SessionStore; +import cn.orionsec.kit.net.host.sftp.SftpExecutor; +import cn.orionsec.kit.net.host.ssh.ExitCode; +import cn.orionsec.kit.net.host.ssh.command.CommandExecutor; +import cn.orionsec.kit.net.host.ssh.command.CommandExecutors; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.message.MessageType; +import cn.orionsec.ops.constant.monitor.MonitorConst; +import cn.orionsec.ops.constant.monitor.MonitorStatus; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.domain.MachineMonitorDO; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.service.api.MachineEnvService; +import cn.orionsec.ops.service.api.MachineInfoService; +import cn.orionsec.ops.service.api.MachineMonitorService; +import cn.orionsec.ops.service.api.WebSideMessageService; +import cn.orionsec.ops.utils.PathBuilders; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.OutputStream; +import java.util.Map; + +@Slf4j +public class MonitorAgentInstallTask implements Runnable { + + private static final MachineInfoService machineInfoService = SpringHolder.getBean(MachineInfoService.class); + + private static final MachineEnvService machineEnvService = SpringHolder.getBean(MachineEnvService.class); + + private static final MachineMonitorService machineMonitorService = SpringHolder.getBean(MachineMonitorService.class); + + private static final WebSideMessageService webSideMessageService = SpringHolder.getBean(WebSideMessageService.class); + + private final Long machineId; + + private final UserDTO user; + + private SessionStore session; + + private MachineInfoDO machine; + + private OutputStream logStream; + + public MonitorAgentInstallTask(Long machineId, UserDTO user) { + this.machineId = machineId; + this.user = user; + } + + @Override + public void run() { + log.info("开始安装监控插件 machineId: {}", machineId); + try { + // 查询机器信息 + this.machine = machineInfoService.selectById(machineId); + // 打开日志流 + String logPath = PathBuilders.getInstallLogPath(machineId, MonitorConst.AGENT_FILE_NAME_PREFIX); + File logFile = new File(Files1.getPath(SystemEnvAttr.LOG_PATH.getValue(), logPath)); + Files1.touch(logFile); + this.logStream = Files1.openOutputStreamFast(logFile); + // 打开会话 + this.session = machineInfoService.openSessionStore(machineId); + String pluginDirectory = PathBuilders.getPluginPath(machine.getUsername()); + String startScriptPath = pluginDirectory + "/" + MonitorConst.START_SCRIPT_FILE_NAME; + // 传输 + this.transferAgentFile(pluginDirectory, startScriptPath); + // 启动 + this.startAgentApp(startScriptPath); + // 同步等待 + this.checkAgentRunStatus(); + // 拼接日志 + this.appendLog("安装成功 {}", Dates.current()); + } catch (Exception e) { + // 拼接日志 + this.appendLog("安装失败 {}", Exceptions.getStackTraceAsString(e)); + // 更新状态 + MachineMonitorDO update = new MachineMonitorDO(); + update.setMonitorStatus(MonitorStatus.NOT_START.getStatus()); + machineMonitorService.updateMonitorConfigByMachineId(machineId, update); + // 发送站内信 + this.sendWebSideMessage(MessageType.MACHINE_AGENT_INSTALL_FAILURE); + } finally { + Streams.close(session); + Streams.close(logStream); + } + } + + /** + * 传输文件 + * + * @param pluginDirectory pluginDirectory + * @param startScriptPath startScriptPath + */ + private void transferAgentFile(String pluginDirectory, String startScriptPath) { + // 传输脚本目录 + String agentPath = pluginDirectory + Const.LIB_DIR + "/" + MonitorConst.getAgentFileName(); + SftpExecutor executor = null; + try { + // 打开 sftp 连接 + String charset = machineEnvService.getSftpCharset(machineId); + executor = session.getSftpExecutor(charset); + executor.connect(); + // 传输启动脚本文件 + String startScript = this.getStartScript(agentPath); + this.appendLog("开始生成启动脚本 path: {}, command: \n{}", agentPath, startScript); + executor.write(startScriptPath, Strings.bytes(startScript)); + executor.changeMode(startScriptPath, 777); + // 传输 agent 文件 + File localAgentFile = new File(SystemEnvAttr.MACHINE_MONITOR_AGENT_PATH.getValue()); + // 查询文件是否存在 + long size = executor.getSize(agentPath); + long totalSize = localAgentFile.length(); + if (totalSize != size) { + // 传输文件 + this.appendLog("插件包不存在-开始传输 {} {}B", agentPath, totalSize); + executor.uploadFile(agentPath, localAgentFile); + this.appendLog("插件包传输完成 {}", agentPath); + } else { + this.appendLog("插件包已存在 {}", agentPath); + } + } catch (Exception e) { + throw Exceptions.sftp("文件上传失败", e); + } finally { + Streams.close(executor); + } + } + + /** + * 启动 agent 应用 + * + * @param startScriptPath startScriptPath + */ + private void startAgentApp(String startScriptPath) { + CommandExecutor executor = null; + try { + // 执行启动命令 + this.appendLog("开始执行启动脚本 path: {}", startScriptPath); + // executor = session.getCommandExecutor("bash -l " + startScriptPath); + executor = session.getCommandExecutor(startScriptPath); + executor.getChannel().setPty(false); + CommandExecutors.execCommand(executor, logStream); + int exitCode = executor.getExitCode(); + if (!ExitCode.isSuccess(exitCode)) { + throw Exceptions.runtime("执行启动失败"); + } + this.appendLog("命令执行完成 exit: {}", exitCode); + } catch (Exception e) { + throw Exceptions.runtime("执行启动异常", e); + } finally { + Streams.close(executor); + } + } + + /** + * 同步检查 agent 状态 + */ + private void checkAgentRunStatus() { + // 查询配置 + MachineMonitorDO monitor = machineMonitorService.selectByMachineId(machineId); + // 尝试进行同步 检查是否启动 + String version = null; + for (int i = 0; i < 5; i++) { + Threads.sleep(Const.MS_S_10); + version = machineMonitorService.syncMonitorAgent(machineId, monitor.getMonitorUrl(), monitor.getAccessToken()); + this.appendLog("检查agent状态 第{}次", i + 1); + if (version != null) { + break; + } + } + if (version == null) { + throw Exceptions.runtime("获取 agent 状态失败"); + } + this.appendLog("agent启动成功 version: {}", version); + // 更新状态以及版本 + MachineMonitorDO update = new MachineMonitorDO(); + update.setMonitorStatus(MonitorStatus.RUNNING.getStatus()); + update.setAgentVersion(version); + machineMonitorService.updateMonitorConfigByMachineId(machineId, update); + // 发送站内信 + this.sendWebSideMessage(MessageType.MACHINE_AGENT_INSTALL_SUCCESS); + } + + /** + * 发送站内信 + */ + private void sendWebSideMessage(MessageType type) { + Map params = Maps.newMap(); + params.put(EventKeys.NAME, machine.getMachineName()); + webSideMessageService.addMessage(type, machine.getId(), user.getId(), user.getUsername(), params); + } + + /** + * 获取启动脚本 + * + * @param agentJarPath agentJar 路径 + * @return 脚本内容 + */ + private String getStartScript(String agentJarPath) { + Map param = Maps.newMap(); + param.put("processName", MonitorConst.AGENT_FILE_NAME_PREFIX); + param.put("machineId", machineId); + param.put("agentJarPath", agentJarPath); + return Strings.format(MonitorConst.START_SCRIPT_VALUE, param).replaceAll("\r\n", "\n"); + } + + /** + * 拼接日志 + * + * @param logString log + * @param args args + */ + @SneakyThrows + private void appendLog(String logString, Object... args) { + if (!Arrays1.isEmpty(args)) { + log.info("安装监控插件-" + logString, args); + } + if (logStream != null) { + logStream.write(Strings.bytes(Strings.format(logString, args))); + logStream.write(Letters.LF); + logStream.flush(); + } + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/AbstractTaskProcessor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/AbstractTaskProcessor.java new file mode 100644 index 0000000..f9d2541 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/AbstractTaskProcessor.java @@ -0,0 +1,189 @@ + +package cn.orionsec.ops.handler.scheduler; + +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.scheduler.SchedulerTaskStatus; +import cn.orionsec.ops.dao.SchedulerTaskDAO; +import cn.orionsec.ops.dao.SchedulerTaskRecordDAO; +import cn.orionsec.ops.entity.domain.SchedulerTaskDO; +import cn.orionsec.ops.entity.domain.SchedulerTaskMachineRecordDO; +import cn.orionsec.ops.entity.domain.SchedulerTaskRecordDO; +import cn.orionsec.ops.handler.scheduler.machine.ITaskMachineHandler; +import cn.orionsec.ops.handler.scheduler.machine.TaskMachineHandler; +import cn.orionsec.ops.service.api.SchedulerTaskMachineRecordService; +import lombok.extern.slf4j.Slf4j; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +@Slf4j +public abstract class AbstractTaskProcessor implements ITaskProcessor { + + protected static final SchedulerTaskDAO schedulerTaskDAO = SpringHolder.getBean(SchedulerTaskDAO.class); + + protected static final SchedulerTaskRecordDAO schedulerTaskRecordDAO = SpringHolder.getBean(SchedulerTaskRecordDAO.class); + + protected static final SchedulerTaskMachineRecordService schedulerTaskMachineRecordService = SpringHolder.getBean(SchedulerTaskMachineRecordService.class); + + protected static final TaskSessionHolder taskSessionHolder = SpringHolder.getBean(TaskSessionHolder.class); + + protected final Long recordId; + + protected SchedulerTaskDO task; + + protected SchedulerTaskRecordDO record; + + protected final Map handlers; + + protected volatile boolean terminated; + + public AbstractTaskProcessor(Long recordId) { + this.recordId = recordId; + this.handlers = Maps.newLinkedMap(); + } + + @Override + public void run() { + log.info("开始执行调度任务-recordId: {}", recordId); + // 初始化 + try { + // 填充数据 + this.getTaskData(); + // 判断状态 + if (record == null || !SchedulerTaskStatus.WAIT.getStatus().equals(record.getTaskStatus())) { + return; + } + // 修改状态 + this.updateStatus(SchedulerTaskStatus.RUNNABLE); + } catch (Exception e) { + log.error("执行调度任务初始化失败-recordId: {}", recordId, e); + this.updateStatus(SchedulerTaskStatus.FAILURE); + return; + } + // 执行 + Exception ex = null; + try { + // 添加会话 + taskSessionHolder.addSession(recordId, this); + // 处理 + this.handler(); + } catch (Exception e) { + // 执行失败 + ex = e; + } + // 回调 + try { + if (terminated) { + // 停止回调 + this.updateStatus(SchedulerTaskStatus.TERMINATED); + log.info("执行调度任务执行停止-recordId: {}", recordId); + } else if (ex == null) { + // 完成回调 + this.updateStatus(SchedulerTaskStatus.SUCCESS); + log.info("执行调度任务执行成功-recordId: {}", recordId); + } else { + // 异常回调 + this.updateStatus(SchedulerTaskStatus.FAILURE); + log.error("执行调度任务执行失败-recordId: {}", recordId, ex); + } + } finally { + // 释放资源 + this.close(); + } + } + + /** + * 处理 + * + * @throws Exception 处理异常 + */ + protected abstract void handler() throws Exception; + + /** + * 填充数据 + */ + private void getTaskData() { + // 查询明细 + this.record = schedulerTaskRecordDAO.selectById(recordId); + if (record == null || !SchedulerTaskStatus.WAIT.getStatus().equals(record.getTaskStatus())) { + return; + } + // 查询任务 + this.task = schedulerTaskDAO.selectById(record.getTaskId()); + // 查询机器明细 + List machineRecords = schedulerTaskMachineRecordService.selectByRecordId(recordId); + for (SchedulerTaskMachineRecordDO machineRecord : machineRecords) { + handlers.put(machineRecord.getId(), new TaskMachineHandler(machineRecord.getId())); + } + } + + /** + * 更新状态 + * + * @param status status + */ + protected void updateStatus(SchedulerTaskStatus status) { + Date now = new Date(); + // 更新任务 + SchedulerTaskDO updateTask = new SchedulerTaskDO(); + updateTask.setId(task.getId()); + updateTask.setUpdateTime(now); + updateTask.setLatelyStatus(status.getStatus()); + // 更新明细 + SchedulerTaskRecordDO updateRecord = new SchedulerTaskRecordDO(); + updateRecord.setId(recordId); + updateRecord.setUpdateTime(now); + updateRecord.setTaskStatus(status.getStatus()); + switch (status) { + case RUNNABLE: + updateRecord.setStartTime(now); + break; + case SUCCESS: + case FAILURE: + case TERMINATED: + default: + updateRecord.setEndTime(now); + break; + } + schedulerTaskDAO.updateById(updateTask); + schedulerTaskRecordDAO.updateById(updateRecord); + } + + @Override + public void terminateAll() { + this.terminated = true; + } + + @Override + public void terminateMachine(Long recordMachineId) { + ITaskMachineHandler machineHandler = handlers.get(recordMachineId); + if (machineHandler != null) { + machineHandler.terminate(); + } + } + + @Override + public void skipMachine(Long recordMachineId) { + ITaskMachineHandler machineHandler = handlers.get(recordMachineId); + if (machineHandler != null) { + machineHandler.skip(); + } + } + + @Override + public void writeMachine(Long recordMachineId, String command) { + ITaskMachineHandler machineHandler = handlers.get(recordMachineId); + if (machineHandler != null) { + machineHandler.write(command); + } + } + + @Override + public void close() { + // 移除会话 + taskSessionHolder.removeSession(recordId); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/ITaskProcessor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/ITaskProcessor.java new file mode 100644 index 0000000..d6bda7c --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/ITaskProcessor.java @@ -0,0 +1,54 @@ + +package cn.orionsec.ops.handler.scheduler; + +import cn.orionsec.kit.lang.able.SafeCloseable; +import cn.orionsec.kit.lang.function.select.Branches; +import cn.orionsec.kit.lang.function.select.Selector; +import cn.orionsec.ops.constant.common.SerialType; + +public interface ITaskProcessor extends Runnable, SafeCloseable { + + /** + * 停止全部 + */ + void terminateAll(); + + /** + * 停止机器操作 + * + * @param recordMachineId recordMachineId + */ + void terminateMachine(Long recordMachineId); + + /** + * 跳过机器操作 + * + * @param recordMachineId recordMachineId + */ + void skipMachine(Long recordMachineId); + + /** + * 发送机器命令 + * + * @param recordMachineId recordMachineId + * @param command command + */ + void writeMachine(Long recordMachineId, String command); + + /** + * 获取实际执行处理器 + * + * @param recordId recordId + * @param type type + * @return 处理器 + */ + static ITaskProcessor with(Long recordId, SerialType type) { + return Selector.of(type) + .test(Branches.eq(SerialType.SERIAL) + .then(() -> new SerialTaskProcessor(recordId))) + .test(Branches.eq(SerialType.PARALLEL) + .then(() -> new ParallelTaskProcessor(recordId))) + .get(); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/ParallelTaskProcessor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/ParallelTaskProcessor.java new file mode 100644 index 0000000..b066c98 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/ParallelTaskProcessor.java @@ -0,0 +1,52 @@ + +package cn.orionsec.ops.handler.scheduler; + +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Threads; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.SchedulerPools; +import cn.orionsec.ops.constant.scheduler.SchedulerTaskMachineStatus; +import cn.orionsec.ops.handler.scheduler.machine.ITaskMachineHandler; + +import java.util.Collection; + +public class ParallelTaskProcessor extends AbstractTaskProcessor { + + public ParallelTaskProcessor(Long recordId) { + super(recordId); + } + + @Override + protected void handler() throws Exception { + // 阻塞执行所有任务 + Collection handlers = this.handlers.values(); + Threads.blockRun(handlers, SchedulerPools.SCHEDULER_TASK_MACHINE_SCHEDULER); + // 检查是否停止 + if (terminated) { + return; + } + // 全部停止 + final boolean allTerminated = handlers.stream() + .map(ITaskMachineHandler::getStatus) + .allMatch(SchedulerTaskMachineStatus.TERMINATED::equals); + if (allTerminated) { + this.terminated = true; + return; + } + // 全部成功 + final boolean allSuccess = handlers.stream() + .map(ITaskMachineHandler::getStatus) + .filter(s -> !SchedulerTaskMachineStatus.TERMINATED.equals(s)) + .allMatch(SchedulerTaskMachineStatus.SUCCESS::equals); + if (!allSuccess) { + throw Exceptions.log(MessageConst.OPERATOR_NOT_ALL_SUCCESS); + } + } + + @Override + public void terminateAll() { + super.terminateAll(); + handlers.values().forEach(ITaskMachineHandler::terminate); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/SerialTaskProcessor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/SerialTaskProcessor.java new file mode 100644 index 0000000..2f8c2a7 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/SerialTaskProcessor.java @@ -0,0 +1,64 @@ + +package cn.orionsec.ops.handler.scheduler; + +import cn.orionsec.ops.constant.common.ExceptionHandlerType; +import cn.orionsec.ops.constant.scheduler.SchedulerTaskMachineStatus; +import cn.orionsec.ops.handler.scheduler.machine.ITaskMachineHandler; + +import java.util.Collection; + +public class SerialTaskProcessor extends AbstractTaskProcessor { + + public SerialTaskProcessor(Long recordId) { + super(recordId); + } + + @Override + protected void handler() throws Exception { + // 异常处理策略 + final boolean errorSkipAll = ExceptionHandlerType.SKIP_ALL.getType().equals(task.getExceptionHandler()); + // 串行执行 + Exception ex = null; + Collection handlers = this.handlers.values(); + for (ITaskMachineHandler handler : handlers) { + // 停止跳过 + if (terminated) { + handler.skip(); + continue; + } + // 发生异常并且异常处理策略是跳过所有则跳过 + if (ex != null && errorSkipAll) { + handler.skip(); + continue; + } + // 执行 + try { + handler.run(); + } catch (Exception e) { + ex = e; + } + } + // 异常返回 + if (ex != null) { + throw ex; + } + // 全部停止 + final boolean allTerminated = handlers.stream() + .map(ITaskMachineHandler::getStatus) + .filter(s -> !SchedulerTaskMachineStatus.SKIPPED.equals(s)) + .allMatch(SchedulerTaskMachineStatus.TERMINATED::equals); + if (allTerminated) { + this.terminated = true; + } + } + + @Override + public void terminateAll() { + super.terminateAll(); + // 获取当前执行中的机器执行器 + handlers.values().stream() + .filter(s -> s.getStatus().equals(SchedulerTaskMachineStatus.RUNNABLE)) + .forEach(ITaskMachineHandler::terminate); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/TaskSessionHolder.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/TaskSessionHolder.java new file mode 100644 index 0000000..5a08b1e --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/TaskSessionHolder.java @@ -0,0 +1,48 @@ + +package cn.orionsec.ops.handler.scheduler; + +import cn.orionsec.kit.lang.utils.collect.Maps; +import org.springframework.stereotype.Component; + +import java.util.Map; + +@Component +public class TaskSessionHolder { + + /** + * key: recordId + * value: ITaskProcessor + */ + private final Map holder = Maps.newCurrentHashMap(); + + /** + * 添加session + * + * @param id id + * @param session session + */ + public void addSession(Long id, ITaskProcessor session) { + holder.put(id, session); + } + + /** + * 获取session + * + * @param id id + * @return session + */ + public ITaskProcessor getSession(Long id) { + return holder.get(id); + } + + /** + * 删除session + * + * @param id id + * @return session + */ + public ITaskProcessor removeSession(Long id) { + return holder.remove(id); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/machine/ITaskMachineHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/machine/ITaskMachineHandler.java new file mode 100644 index 0000000..68f74f3 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/machine/ITaskMachineHandler.java @@ -0,0 +1,33 @@ + +package cn.orionsec.ops.handler.scheduler.machine; + +import cn.orionsec.kit.lang.able.SafeCloseable; +import cn.orionsec.ops.constant.scheduler.SchedulerTaskMachineStatus; + +public interface ITaskMachineHandler extends Runnable, SafeCloseable { + + /** + * 跳过 (未开始) + */ + void skip(); + + /** + * 停止 (进行中) + */ + void terminate(); + + /** + * 发送命令 + * + * @param command command + */ + void write(String command); + + /** + * 状态 + * + * @return 获取状态 + */ + SchedulerTaskMachineStatus getStatus(); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/machine/TaskMachineHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/machine/TaskMachineHandler.java new file mode 100644 index 0000000..1716834 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/scheduler/machine/TaskMachineHandler.java @@ -0,0 +1,309 @@ + +package cn.orionsec.ops.handler.scheduler.machine; + +import cn.orionsec.kit.lang.constant.Letters; +import cn.orionsec.kit.lang.exception.DisabledException; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.lang.utils.io.Streams; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.kit.net.host.SessionStore; +import cn.orionsec.kit.net.host.ssh.ExitCode; +import cn.orionsec.kit.net.host.ssh.command.CommandExecutor; +import cn.orionsec.kit.net.host.ssh.command.CommandExecutors; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.common.StainCode; +import cn.orionsec.ops.constant.scheduler.SchedulerTaskMachineStatus; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.dao.SchedulerTaskMachineRecordDAO; +import cn.orionsec.ops.entity.domain.SchedulerTaskMachineRecordDO; +import cn.orionsec.ops.handler.tail.TailSessionHolder; +import cn.orionsec.ops.service.api.MachineInfoService; +import cn.orionsec.ops.utils.Utils; +import lombok.Getter; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.OutputStream; +import java.util.Date; + +@Slf4j +public class TaskMachineHandler implements ITaskMachineHandler { + + private static final SchedulerTaskMachineRecordDAO schedulerTaskMachineRecordDAO = SpringHolder.getBean(SchedulerTaskMachineRecordDAO.class); + + private static final MachineInfoService machineInfoService = SpringHolder.getBean(MachineInfoService.class); + + private static final TailSessionHolder tailSessionHolder = SpringHolder.getBean(TailSessionHolder.class); + + private final Long machineRecordId; + + private String logPath; + + private OutputStream logOutputStream; + + private SessionStore sessionStore; + + private CommandExecutor executor; + + private SchedulerTaskMachineRecordDO machineRecord; + + private Date startTime, endTime; + + private Integer exitCode; + + private volatile boolean terminated; + + @Getter + private volatile SchedulerTaskMachineStatus status; + + public TaskMachineHandler(Long machineRecordId) { + this.machineRecordId = machineRecordId; + this.status = SchedulerTaskMachineStatus.WAIT; + } + + @Override + public void run() { + // 检查状态 + log.info("调度任务-机器操作-开始 machineRecordId: {}", machineRecordId); + this.machineRecord = schedulerTaskMachineRecordDAO.selectById(machineRecordId); + this.status = SchedulerTaskMachineStatus.of(machineRecord.getExecStatus()); + if (!SchedulerTaskMachineStatus.WAIT.equals(status)) { + return; + } + // 执行 + Exception ex = null; + try { + this.updateStatus(SchedulerTaskMachineStatus.RUNNABLE); + // 打开日志 + this.openLogger(); + // 打开机器 + this.sessionStore = machineInfoService.openSessionStore(machineRecord.getMachineId()); + // 获取执行器 + this.executor = sessionStore.getCommandExecutor(Strings.replaceCRLF(machineRecord.getExecCommand())); + // 开始执行 + CommandExecutors.execCommand(executor, logOutputStream); + this.exitCode = executor.getExitCode(); + } catch (Exception e) { + ex = e; + } + // 回调 + try { + if (terminated) { + // 停止回调 + this.terminatedCallback(); + } else if (ex == null) { + // 完成回调 + this.completeCallback(); + } else if (ex instanceof DisabledException) { + // 机器未启用回调 + this.machineDisableCallback(); + } else { + // 执行异常回调 + this.exceptionCallback(ex); + throw Exceptions.runtime(ex); + } + } finally { + // 释放资源 + this.close(); + } + } + + /** + * 停止回调 + */ + private void terminatedCallback() { + log.error("调度任务-机器操作-停止 machineRecordId: {}", machineRecordId); + // 更新状态 + this.updateStatus(SchedulerTaskMachineStatus.TERMINATED); + // 拼接日志 + StringBuilder log = new StringBuilder(Const.LF_2) + .append(Utils.getStainKeyWords("# 调度任务执行停止", StainCode.GLOSS_YELLOW)) + .append(Letters.TAB) + .append(Utils.getStainKeyWords(Dates.format(endTime), StainCode.GLOSS_BLUE)) + .append(Const.LF); + this.appendLog(log.toString()); + } + + /** + * 完成回调 + */ + private void completeCallback() { + log.info("调度任务-机器操作-完成 machineRecordId: {}, exitCode: {}", machineRecordId, exitCode); + final boolean execSuccess = ExitCode.isSuccess(exitCode); + // 更新状态 + if (execSuccess) { + this.updateStatus(SchedulerTaskMachineStatus.SUCCESS); + } else { + this.updateStatus(SchedulerTaskMachineStatus.FAILURE); + } + // 拼接日志 + long used = endTime.getTime() - startTime.getTime(); + StringBuilder log = new StringBuilder() + .append(Letters.LF) + .append(Utils.getStainKeyWords("# 调度任务执行完成", StainCode.GLOSS_GREEN)) + .append(Letters.LF); + log.append("exitcode: ") + .append(execSuccess + ? Utils.getStainKeyWords(exitCode, StainCode.GLOSS_BLUE) + : Utils.getStainKeyWords(exitCode, StainCode.GLOSS_RED)) + .append(Letters.LF); + log.append("结束时间: ") + .append(Utils.getStainKeyWords(Dates.format(endTime), StainCode.GLOSS_BLUE)) + .append(" used ") + .append(Utils.getStainKeyWords(Utils.interval(used), StainCode.GLOSS_BLUE)) + .append(" (") + .append(StainCode.prefix(StainCode.GLOSS_BLUE)) + .append(used) + .append("ms") + .append(StainCode.SUFFIX) + .append(")\n"); + this.appendLog(log.toString()); + } + + /** + * 机器未启用回调 + */ + private void machineDisableCallback() { + log.error("调度任务-机器操作-机器停用停止 machineRecordId: {}", machineRecordId); + // 更新状态 + this.updateStatus(SchedulerTaskMachineStatus.TERMINATED); + // 拼接日志 + StringBuilder log = new StringBuilder() + .append(Const.LF) + .append(Utils.getStainKeyWords("# 调度任务执行机器未启用", StainCode.GLOSS_YELLOW)) + .append(Letters.TAB) + .append(Utils.getStainKeyWords(Dates.format(endTime), StainCode.GLOSS_BLUE)) + .append(Const.LF); + this.appendLog(log.toString()); + } + + /** + * 异常回调 + */ + private void exceptionCallback(Exception e) { + log.error("调度任务-机器操作-失败 machineRecordId: {}", machineRecordId, e); + // 更新状态 + this.updateStatus(SchedulerTaskMachineStatus.FAILURE); + // 拼接日志 + StringBuilder log = new StringBuilder() + .append(Const.LF) + .append(Utils.getStainKeyWords("# 调度任务执行失败", StainCode.GLOSS_RED)) + .append(Letters.TAB) + .append(Utils.getStainKeyWords(Dates.format(endTime), StainCode.GLOSS_BLUE)) + .append(Letters.LF) + .append(Exceptions.getStackTraceAsString(e)) + .append(Const.LF); + this.appendLog(log.toString()); + } + + @Override + public void skip() { + log.info("调度任务-机器操作-跳过 machineRecordId: {}, status: {}", machineRecordId, status); + if (SchedulerTaskMachineStatus.WAIT.equals(status)) { + // 只能跳过等待中的任务 + this.updateStatus(SchedulerTaskMachineStatus.SKIPPED); + } + } + + @Override + public void terminate() { + log.info("调度任务-机器操作-停止 machineRecordId: {}", machineRecordId); + // 只能停止进行中的任务 + if (SchedulerTaskMachineStatus.RUNNABLE.equals(status)) { + this.terminated = true; + Streams.close(this.executor); + } + } + + @Override + public void write(String command) { + executor.write(command); + } + + /** + * 打开日志 + */ + private void openLogger() { + File logFile = new File(Files1.getPath(SystemEnvAttr.LOG_PATH.getValue(), machineRecord.getLogPath())); + Files1.touch(logFile); + this.logPath = logFile.getAbsolutePath(); + // 打开日志流 + log.info("TaskMachineHandler-打开日志流 {} {}", machineRecordId, logPath); + this.logOutputStream = Files1.openOutputStreamFastSafe(logFile); + // 拼接开始日志 + StringBuilder log = new StringBuilder() + .append(Utils.getStainKeyWords("# 开始执行调度任务 ", StainCode.GLOSS_GREEN)) + .append(Const.LF); + log.append("执行机器: ") + .append(Utils.getStainKeyWords(machineRecord.getMachineName(), StainCode.GLOSS_BLUE)) + .append(Const.LF); + log.append("开始时间: ") + .append(Utils.getStainKeyWords(Dates.format(startTime), StainCode.GLOSS_BLUE)) + .append(Const.LF_2); + log.append(Utils.getStainKeyWords("# 执行命令", StainCode.GLOSS_GREEN)) + .append(Const.LF) + .append(StainCode.prefix(StainCode.GLOSS_CYAN)) + .append(Utils.getEndLfWithEof(machineRecord.getExecCommand())) + .append(StainCode.SUFFIX) + .append(Utils.getStainKeyWords("# 开始执行", StainCode.GLOSS_GREEN)) + .append(Const.LF); + this.appendLog(log.toString()); + } + + /** + * 拼接日志 + * + * @param log log + */ + @SneakyThrows + private void appendLog(String log) { + logOutputStream.write(Strings.bytes(log)); + logOutputStream.flush(); + } + + /** + * 更新状态 + * + * @param status status + */ + private void updateStatus(SchedulerTaskMachineStatus status) { + Date now = new Date(); + this.status = status; + SchedulerTaskMachineRecordDO update = new SchedulerTaskMachineRecordDO(); + update.setId(machineRecordId); + update.setExecStatus(status.getStatus()); + update.setUpdateTime(now); + switch (status) { + case RUNNABLE: + this.startTime = now; + update.setStartTime(now); + break; + case SUCCESS: + case FAILURE: + case TERMINATED: + if (startTime != null) { + this.endTime = now; + update.setEndTime(now); + update.setExitCode(exitCode); + } + break; + default: + } + schedulerTaskMachineRecordDAO.updateById(update); + } + + @Override + public void close() { + // 释放资源 + Streams.close(executor); + Streams.close(sessionStore); + Streams.close(logOutputStream); + // 异步关闭正在tail的日志 + tailSessionHolder.asyncCloseTailFile(Const.HOST_MACHINE_ID, logPath); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/FileTransferProcessor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/FileTransferProcessor.java new file mode 100644 index 0000000..449cfea --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/FileTransferProcessor.java @@ -0,0 +1,222 @@ + +package cn.orionsec.ops.handler.sftp; + +import cn.orionsec.kit.lang.support.progress.ByteTransferRateProgress; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.lang.utils.math.Numbers; +import cn.orionsec.kit.net.host.SessionStore; +import cn.orionsec.kit.net.host.sftp.SftpExecutor; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.SchedulerPools; +import cn.orionsec.ops.constant.sftp.SftpTransferStatus; +import cn.orionsec.ops.dao.FileTransferLogDAO; +import cn.orionsec.ops.entity.domain.FileTransferLogDO; +import cn.orionsec.ops.entity.dto.sftp.FileTransferNotifyProgressDTO; +import cn.orionsec.ops.service.api.MachineEnvService; +import cn.orionsec.ops.service.api.MachineInfoService; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public abstract class FileTransferProcessor implements IFileTransferProcessor { + + protected static MachineInfoService machineInfoService = SpringHolder.getBean(MachineInfoService.class); + + protected static MachineEnvService machineEnvService = SpringHolder.getBean(MachineEnvService.class); + + protected static FileTransferLogDAO fileTransferLogDAO = SpringHolder.getBean(FileTransferLogDAO.class); + + protected static TransferProcessorManager transferProcessorManager = SpringHolder.getBean(TransferProcessorManager.class); + + protected SessionStore sessionStore; + + protected SftpExecutor executor; + + protected FileTransferLogDO record; + + protected Long userId; + + protected Long machineId; + + protected String fileToken; + + protected ByteTransferRateProgress progress; + + protected volatile boolean userCancel; + + public FileTransferProcessor(FileTransferLogDO record) { + this.record = record; + this.fileToken = record.getFileToken(); + this.userId = record.getUserId(); + this.machineId = record.getMachineId(); + } + + @Override + public void run() { + // 判断是否可以传输 + this.record = fileTransferLogDAO.selectById(record.getId()); + if (record == null || !SftpTransferStatus.WAIT.getStatus().equals(record.getTransferStatus())) { + return; + } + transferProcessorManager.addProcessor(fileToken, this); + try { + // 开始传输 + this.updateStatusAndNotify(SftpTransferStatus.RUNNABLE.getStatus()); + // 打开连接 + this.sessionStore = machineInfoService.openSessionStore(machineId); + String charset = machineEnvService.getSftpCharset(machineId); + this.executor = sessionStore.getSftpExecutor(charset); + executor.connect(); + log.info("sftp传输文件-初始化完毕, 准备处理传输 fileToken: {}", fileToken); + // 检查是否可以用文件系统传输 + if (SftpSupport.checkUseFileSystem(executor)) { + // 直接拷贝 + SftpSupport.usingFsCopy(this); + } else { + // 处理 + this.handler(); + } + log.info("sftp传输文件-传输完毕 fileToken: {}", fileToken); + } catch (Exception e) { + log.error("sftp传输文件-出现异常 fileToken: {}, e: {}, message: {}", fileToken, e.getClass().getName(), e.getMessage()); + // 程序错误并非传输错误修改状态 + if (!userCancel) { + log.error("sftp传输文件-运行异常 fileToken: {}", fileToken, e); + this.updateStatusAndNotify(SftpTransferStatus.ERROR.getStatus()); + } + e.printStackTrace(); + } finally { + transferProcessorManager.removeProcessor(fileToken); + this.disconnected(); + } + } + + @Override + public void stop() { + log.info("sftp传输文件-用户暂停 fileToken: {}", fileToken); + this.userCancel = true; + this.updateStatusAndNotify(SftpTransferStatus.PAUSE.getStatus()); + this.disconnected(); + } + + /** + * 处理操作 + */ + protected abstract void handler(); + + /** + * 初始化进度条 + */ + protected void initProgress(ByteTransferRateProgress progress) { + this.progress = progress; + progress.computeRate(); + progress.rateExecutor(SchedulerPools.SFTP_TRANSFER_RATE_SCHEDULER); + progress.rateAcceptor(this::transferAccept); + progress.callback(this::transferDoneCallback); + } + + /** + * 传输回调 + * + * @param progress progress + */ + protected void transferAccept(ByteTransferRateProgress progress) { + try { + if (progress.isDone()) { + return; + } + String progressRate = Numbers.setScale(progress.getProgress() * 100, 2); + String transferRate = Files1.getSize(progress.getNowRate()); + String transferCurrent = Files1.getSize(progress.getCurrent()); + // debug + // log.info(transferCurrent + " " + progressRate + "% " + transferRate + "/s"); + // notify progress + this.notifyProgress(transferRate, transferCurrent, progressRate); + } catch (Exception e) { + log.error("sftp-传输信息回调异常 fileToken: {}, digest: {}", fileToken, Exceptions.getDigest(e), e); + } + } + + /** + * 传输完成回调 + * + * @param pro progress + */ + protected void transferDoneCallback() { + try { + FileTransferLogDO update = new FileTransferLogDO(); + update.setId(record.getId()); + if (progress.isError()) { + // 非用户取消更新状态 + if (!userCancel) { + this.updateStatusAndNotify(SftpTransferStatus.ERROR.getStatus()); + } + } else { + String transferCurrent = Files1.getSize(progress.getCurrent()); + String transferRate = Files1.getSize(progress.getNowRate()); + // notify progress + this.notifyProgress(transferRate, transferCurrent, "100"); + // notify status + this.updateStatusAndNotify(SftpTransferStatus.FINISH.getStatus(), 100D, progress.getEnd()); + } + } catch (Exception e) { + log.error("sftp-传输完成回调异常 fileToken: {}, digest: {}", fileToken, Exceptions.getDigest(e)); + e.printStackTrace(); + } + } + + protected void updateStatusAndNotify(Integer status) { + if (progress == null) { + this.updateStatusAndNotify(status, null, null); + } else { + this.updateStatusAndNotify(status, progress.getProgress() * 100, progress.getCurrent()); + } + } + + /** + * 更新状态并且通知 + * + * @param status status + * @param progress progress + * @param currentSize currentSize + */ + protected void updateStatusAndNotify(Integer status, Double progress, Long currentSize) { + Long id = record.getId(); + if (id == null) { + return; + } + record.setTransferStatus(status); + // 更新 + FileTransferLogDO update = new FileTransferLogDO(); + update.setId(id); + update.setTransferStatus(status); + update.setNowProgress(progress); + update.setCurrentSize(currentSize); + int effect = fileTransferLogDAO.updateById(update); + log.info("sftp传输文件-更新状态 fileToken: {}, status: {}, progress: {}, currentSize: {}, effect: {}", fileToken, status, progress, currentSize, effect); + // notify status + transferProcessorManager.notifySessionStatusEvent(userId, machineId, fileToken, status); + } + + /** + * 通知进度 + * + * @param rate 速度 + * @param current 当前位置 + * @param progress 进度 + */ + protected void notifyProgress(String rate, String current, String progress) { + FileTransferNotifyProgressDTO notifyProgress = new FileTransferNotifyProgressDTO(rate, current, progress); + transferProcessorManager.notifySessionProgressEvent(userId, machineId, fileToken, notifyProgress); + } + + /** + * 断开连接 + */ + protected void disconnected() { + if (executor != null) { + executor.disconnect(); + } + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/IFileTransferProcessor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/IFileTransferProcessor.java new file mode 100644 index 0000000..c41ee1d --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/IFileTransferProcessor.java @@ -0,0 +1,36 @@ + +package cn.orionsec.ops.handler.sftp; + +import cn.orionsec.kit.lang.able.Executable; +import cn.orionsec.kit.lang.able.Stoppable; +import cn.orionsec.kit.lang.function.select.Branches; +import cn.orionsec.kit.lang.function.select.Selector; +import cn.orionsec.ops.constant.sftp.SftpTransferType; +import cn.orionsec.ops.entity.domain.FileTransferLogDO; +import cn.orionsec.ops.handler.sftp.hint.FilePackageHint; +import cn.orionsec.ops.handler.sftp.hint.FileTransferHint; +import cn.orionsec.ops.handler.sftp.impl.DownloadFileProcessor; +import cn.orionsec.ops.handler.sftp.impl.PackageFileProcessor; +import cn.orionsec.ops.handler.sftp.impl.UploadFileProcessor; + +public interface IFileTransferProcessor extends Runnable, Stoppable, Executable { + + /** + * 获取执行processor + * + * @param hint hint + * @return IFileTransferProcessor + */ + static IFileTransferProcessor of(FileTransferHint hint) { + FileTransferLogDO record = hint.getRecord(); + return Selector.of(hint.getType()) + .test(Branches.eq(SftpTransferType.UPLOAD) + .then(() -> new UploadFileProcessor(record))) + .test(Branches.eq(SftpTransferType.DOWNLOAD) + .then(() -> new DownloadFileProcessor(record))) + .test(Branches.eq(SftpTransferType.PACKAGE) + .then(() -> new PackageFileProcessor(record, ((FilePackageHint) hint).getFileList()))) + .get(); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/SftpBasicExecutorHolder.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/SftpBasicExecutorHolder.java new file mode 100644 index 0000000..0e6dd68 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/SftpBasicExecutorHolder.java @@ -0,0 +1,129 @@ + +package cn.orionsec.ops.handler.sftp; + +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.net.host.SessionStore; +import cn.orionsec.kit.net.host.sftp.SftpExecutor; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.service.api.MachineEnvService; +import cn.orionsec.ops.service.api.MachineInfoService; +import cn.orionsec.ops.service.api.SftpService; +import cn.orionsec.ops.utils.EventParamsHolder; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Slf4j +@Component +public class SftpBasicExecutorHolder { + + @Resource + private MachineInfoService machineInfoService; + + @Resource + private MachineEnvService machineEnvService; + + @Resource + private SftpService sftpService; + + /** + * 基本操作的executor 不包含(upload, download) + * machineId: executor + */ + private final Map basicExecutorHolder = Maps.newCurrentHashMap(); + + /** + * 基本操作executor 最后使用时间 + */ + private final Map executorUsing = Maps.newCurrentHashMap(); + + /** + * 获取 sftp 基本操作 executor + * + * @param sessionToken sessionToken + * @return SftpExecutor + */ + public SftpExecutor getBasicExecutor(String sessionToken) { + // 获取executor + Long machineId = sftpService.getMachineId(sessionToken); + EventParamsHolder.addParam(EventKeys.MACHINE_ID, machineId); + return this.getBasicExecutor(machineId); + } + + + /** + * 获取 sftp 基本操作 executor + * + * @param machineId machineId + * @return SftpExecutor + */ + public SftpExecutor getBasicExecutor(Long machineId) { + return this.getBasicExecutor(machineId, null); + } + + /** + * 获取 sftp 基本操作 executor + * + * @param machineId machineId + * @param machine machine + * @return SftpExecutor + */ + public SftpExecutor getBasicExecutor(Long machineId, MachineInfoDO machine) { + SftpExecutor executor = basicExecutorHolder.get(machineId); + if (executor != null) { + if (!executor.isConnected()) { + try { + executor.connect(); + } catch (Exception e) { + // 无法连接则重新创建实例 + executor = null; + } + } + } + // 如果没有重新建立连接 + if (executor == null) { + if (machine == null) { + machine = machineInfoService.selectById(machineId); + } + // 获取charset + String charset = machineEnvService.getSftpCharset(machineId); + // 打开sftp连接 + SessionStore sessionStore = machineInfoService.openSessionStore(machine); + executor = sessionStore.getSftpExecutor(charset); + executor.connect(); + basicExecutorHolder.put(machineId, executor); + } + executorUsing.put(machineId, System.currentTimeMillis()); + return executor; + } + + /** + * 无效化一段时间(1分钟)未使用的执行器 + */ + @Scheduled(cron = "0 */1 * * * ?") + private void invalidationUnusedExecutor() { + long curr = System.currentTimeMillis(); + // 查询需要淘汰的executor的key + List expireKeys = basicExecutorHolder.keySet().stream() + .filter(key -> curr > executorUsing.get(key) + Const.MS_S_60 * 5) + .collect(Collectors.toList()); + // 移除 + expireKeys.forEach(key -> { + SftpExecutor sftpExecutor = basicExecutorHolder.get(key); + if (sftpExecutor == null) { + return; + } + log.info("淘汰sftp执行器: {}", key); + basicExecutorHolder.remove(key); + sftpExecutor.disconnect(); + }); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/SftpSupport.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/SftpSupport.java new file mode 100644 index 0000000..e3772db --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/SftpSupport.java @@ -0,0 +1,73 @@ + +package cn.orionsec.ops.handler.sftp; + +import cn.orionsec.kit.lang.id.UUIds; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.net.host.sftp.SftpExecutor; +import cn.orionsec.ops.constant.sftp.SftpTransferStatus; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.handler.sftp.impl.UploadFileProcessor; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; + +@Slf4j +public class SftpSupport { + + private SftpSupport() { + } + + /** + * 检查远程机器和本机是否是同一台机器 + * + * @param executor executor + * @return 是否为本机 + */ + public static boolean checkUseFileSystem(SftpExecutor executor) { + try { + // 创建一个临时文件 + String checkPath = Files1.getPath(SystemEnvAttr.TEMP_PATH.getValue(), UUIds.random32() + ".ck"); + File checkFile = new File(checkPath); + Files1.touch(checkFile); + checkFile.deleteOnExit(); + // 查询远程机器是否有此文件 如果有则证明传输机器和宿主机是同一台 + boolean exist = executor.getFile(checkFile.getAbsolutePath()) != null; + Files1.delete(checkFile); + return exist; + } catch (Exception e) { + log.error("无法使用FSC {}", Exceptions.getDigest(e)); + return false; + } + } + + /** + * 使用 file system copy + * + * @param processor processor + */ + public static void usingFsCopy(FileTransferProcessor processor) { + String remoteFile = processor.record.getRemoteFile(); + String localFile = processor.record.getLocalFile(); + String localAbsolutePath = Files1.getPath(SystemEnvAttr.SWAP_PATH.getValue(), localFile); + log.info("sftp文件传输-使用FSC fileToken: {}, machineId: {}, local: {}, remote: {}", + processor.fileToken, processor.machineId, localAbsolutePath, remoteFile); + // 复制 + File sourceFile; + File targetFile; + if (processor instanceof UploadFileProcessor) { + sourceFile = new File(localAbsolutePath); + targetFile = new File(remoteFile); + } else { + sourceFile = new File(remoteFile); + targetFile = new File(localAbsolutePath); + } + Files1.copy(sourceFile, targetFile); + // 通知进度 + long fileSize = sourceFile.length(); + processor.notifyProgress(Files1.getSize(fileSize), Files1.getSize(fileSize), "100"); + // 通知状态 + processor.updateStatusAndNotify(SftpTransferStatus.FINISH.getStatus(), 100D, fileSize); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/TransferProcessorManager.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/TransferProcessorManager.java new file mode 100644 index 0000000..3701339 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/TransferProcessorManager.java @@ -0,0 +1,232 @@ + +package cn.orionsec.ops.handler.sftp; + +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Threads; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.lang.utils.json.Jsons; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.sftp.SftpNotifyType; +import cn.orionsec.ops.entity.domain.FileTransferLogDO; +import cn.orionsec.ops.entity.dto.sftp.FileTransferNotifyDTO; +import cn.orionsec.ops.entity.dto.sftp.FileTransferNotifyProgressDTO; +import cn.orionsec.ops.entity.vo.sftp.FileTransferLogVO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +import java.util.List; +import java.util.Map; + +@Slf4j +@Component +public class TransferProcessorManager { + + /** + * key: token + * value: processor + */ + private final Map transferProcessor = Maps.newCurrentHashMap(); + + /** + * sessionId 和 session 映射 + *

+ * key: sessionId + * value: webSocketSession + */ + private final Map idMapping = Maps.newCurrentHashMap(); + + /** + * 机器 和 session 映射 + *

+ * key: userId_machineId + * value: sessionIdList + */ + private final Map> userMachineSessionMapping = Maps.newCurrentHashMap(); + + /** + * 文件传输进度 + *

+ * key: token + * value: progress + */ + private final Map TRANSFER_PROGRESS = Maps.newCurrentHashMap(); + + /** + * 添加processor + * + * @param token token + * @param processor processor + */ + public void addProcessor(String token, IFileTransferProcessor processor) { + transferProcessor.put(token, processor); + } + + /** + * 删除processor + * + * @param token token + */ + public void removeProcessor(String token) { + transferProcessor.remove(token); + } + + /** + * 获取processor + * + * @param token token + * @return processor + */ + public IFileTransferProcessor getProcessor(String token) { + return transferProcessor.get(token); + } + + /** + * 注册session 通知 + * + * @param id id + * @param session session + * @param userId userId + * @param machineId machineId + */ + public void registerSessionNotify(String id, WebSocketSession session, Long userId, Long machineId) { + idMapping.put(id, session); + userMachineSessionMapping.computeIfAbsent(this.getUserMachine(userId, machineId), s -> Lists.newList()).add(id); + } + + /** + * 关闭session 通知 + * + * @param id id + */ + public void closeSessionNotify(String id) { + idMapping.remove(id); + // 删除机器与会话的关联 + userMachineSessionMapping.forEach((k, v) -> { + if (Lists.isEmpty(v)) { + return; + } + v.removeIf(s -> s.equals(id)); + }); + } + + /** + * 通知session 添加事件 + * + * @param userId userId + * @param machineId machineId + * @param record record + */ + public void notifySessionAddEvent(Long userId, Long machineId, FileTransferLogDO record) { + FileTransferNotifyDTO notify = new FileTransferNotifyDTO(); + notify.setType(SftpNotifyType.ADD.getType()); + notify.setFileToken(record.getFileToken()); + notify.setBody(Jsons.toJsonWriteNull(Converts.to(record, FileTransferLogVO.class))); + this.notifySession(userId, machineId, notify); + } + + /** + * 通知session 进度事件 + * + * @param userId userId + * @param machineId machineId + * @param fileToken fileToken + * @param progress progress + */ + public void notifySessionProgressEvent(Long userId, Long machineId, String fileToken, FileTransferNotifyProgressDTO progress) { + // 设置进度 + TRANSFER_PROGRESS.put(fileToken, progress.getProgress()); + // 通知 + FileTransferNotifyDTO notify = new FileTransferNotifyDTO(); + notify.setType(SftpNotifyType.PROGRESS.getType()); + notify.setFileToken(fileToken); + notify.setBody(Jsons.toJsonWriteNull(progress)); + this.notifySession(userId, machineId, notify); + } + + /** + * 通知session 状态事件 + * + * @param userId userId + * @param machineId machineId + * @param fileToken fileToken + * @param status status + */ + public void notifySessionStatusEvent(Long userId, Long machineId, String fileToken, Integer status) { + // 清除进度 + TRANSFER_PROGRESS.remove(fileToken); + // 通知 + FileTransferNotifyDTO notify = new FileTransferNotifyDTO(); + notify.setType(SftpNotifyType.CHANGE_STATUS.getType()); + notify.setFileToken(fileToken); + notify.setBody(status); + this.notifySession(userId, machineId, notify); + } + + /** + * 通知session + * + * @param userId userId + * @param machineId machineId + * @param notify notifyInfo + */ + public void notifySession(Long userId, Long machineId, FileTransferNotifyDTO notify) { + List sessionIds = userMachineSessionMapping.get(this.getUserMachine(userId, machineId)); + if (Lists.isEmpty(sessionIds)) { + return; + } + for (String sessionId : sessionIds) { + if (sessionId == null) { + continue; + } + WebSocketSession session = idMapping.get(sessionId); + if (session == null || !session.isOpen()) { + continue; + } + // 通知 + Exception ex = null; + for (int i = 0; i < 3; i++) { + try { + session.sendMessage(new TextMessage(Jsons.toJsonWriteNull(notify))); + break; + } catch (Exception e) { + ex = e; + log.error("通知session失败 userId: {}, machineId: {} sessionId: {}, try: {}, e: {}", userId, machineId, session, (i + 1), Exceptions.getDigest(e)); + Threads.sleep(Const.N_100); + } + } + if (ex != null) { + ex.printStackTrace(); + } + } + } + + /** + * 获取用户机器key + * + * @param userId userId + * @param machineId machineId + * @return key + */ + private String getUserMachine(Long userId, Long machineId) { + return userId + "_" + machineId; + } + + /** + * 获取传输进度 + * + * @param token token + * @return progress + */ + public Double getProgress(String token) { + String progress = TRANSFER_PROGRESS.get(token); + if (progress == null) { + return null; + } + return Double.valueOf(progress); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/direct/DirectDownloader.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/direct/DirectDownloader.java new file mode 100644 index 0000000..e2e6083 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/direct/DirectDownloader.java @@ -0,0 +1,105 @@ + +package cn.orionsec.ops.handler.sftp.direct; + +import cn.orionsec.kit.lang.able.SafeCloseable; +import cn.orionsec.kit.lang.utils.Valid; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.lang.utils.io.Streams; +import cn.orionsec.kit.net.host.SessionStore; +import cn.orionsec.kit.net.host.sftp.SftpExecutor; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.handler.sftp.SftpSupport; +import cn.orionsec.ops.service.api.MachineEnvService; +import cn.orionsec.ops.service.api.MachineInfoService; +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.io.InputStream; + +@Slf4j +public class DirectDownloader implements SafeCloseable { + + private static final MachineInfoService machineInfoService = SpringHolder.getBean(MachineInfoService.class); + + private static final MachineEnvService machineEnvService = SpringHolder.getBean(MachineEnvService.class); + + /** + * 机器id + */ + private final Long machineId; + + /** + * session + */ + private SessionStore session; + + /** + * 执行器 + */ + private SftpExecutor executor; + + public DirectDownloader(Long machineId) { + this.machineId = machineId; + } + + /** + * 打开连接 + * + * @return this + */ + public DirectDownloader open() { + log.info("直接下载远程文件-建立连接-开始 machineId: {}", machineId); + try { + this.session = machineInfoService.openSessionStore(machineId); + log.info("直接下载远程文件-建立连接-成功 machineId: {}", machineId); + return this; + } catch (Exception e) { + log.error("直接下载远程文件-建立连接-失败 machineId: {}, e: {}", machineId, e); + throw e; + } + } + + /** + * 获取文件 + * + * @param path path + * @return 文件流 + * @throws IOException IOException + */ + public InputStream getFile(String path) throws IOException { + log.info("直接下载远程文件-开始执行 machineId: {}, path: {}", machineId, path); + Valid.notNull(session, MessageConst.UNCONNECTED); + try { + // 打开执行器 + String charset = machineEnvService.getSftpCharset(machineId); + this.executor = session.getSftpExecutor(charset); + executor.connect(); + // 检查是否为本机 + if (SftpSupport.checkUseFileSystem(executor)) { + // 是本机则返回文件流 + return Files1.openInputStreamFast(path); + } else { + // 不是本机获取sftp文件 + return executor.openInputStream(path); + } + } catch (IOException e) { + log.error("直接下载远程文件-执行失败 machineId: {}, path: {}, e: {}", machineId, path, e); + throw e; + } + } + + /** + * 关闭执行器 + */ + public void closeExecutor() { + Streams.close(executor); + } + + @Override + public void close() { + Streams.close(executor); + Streams.close(session); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/hint/FilePackageHint.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/hint/FilePackageHint.java new file mode 100644 index 0000000..60017bb --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/hint/FilePackageHint.java @@ -0,0 +1,26 @@ + +package cn.orionsec.ops.handler.sftp.hint; + +import cn.orionsec.ops.entity.domain.FileTransferLogDO; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +@EqualsAndHashCode(callSuper = true) +public class FilePackageHint extends FileTransferHint { + + /** + * 压缩文件列表 + */ + private List fileList; + + public FilePackageHint(FileTransferLogDO record, List fileList) { + super(record); + this.fileList = fileList; + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/hint/FileTransferHint.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/hint/FileTransferHint.java new file mode 100644 index 0000000..c82f474 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/hint/FileTransferHint.java @@ -0,0 +1,49 @@ + +package cn.orionsec.ops.handler.sftp.hint; + +import cn.orionsec.ops.constant.sftp.SftpTransferType; +import cn.orionsec.ops.entity.domain.FileTransferLogDO; +import lombok.Data; + +import java.util.List; + +@Data +public class FileTransferHint { + + /** + * record + */ + private FileTransferLogDO record; + + /** + * 类型 + */ + private SftpTransferType type; + + public FileTransferHint(FileTransferLogDO record) { + this.record = record; + this.type = SftpTransferType.of(record.getTransferType()); + } + + /** + * 获取上传配置 + * + * @param record record + * @return 上传配置 + */ + public static FileTransferHint transfer(FileTransferLogDO record) { + return new FileTransferHint(record); + } + + /** + * 获取打包配置 + * + * @param record record + * @param fileList fileList + * @return 上传配置 + */ + public static FilePackageHint packaged(FileTransferLogDO record, List fileList) { + return new FilePackageHint(record, fileList); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/impl/DownloadFileProcessor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/impl/DownloadFileProcessor.java new file mode 100644 index 0000000..6eb9f76 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/impl/DownloadFileProcessor.java @@ -0,0 +1,42 @@ + +package cn.orionsec.ops.handler.sftp.impl; + +import cn.orionsec.kit.lang.utils.Threads; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.net.host.sftp.transfer.SftpDownloader; +import cn.orionsec.ops.constant.SchedulerPools; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.entity.domain.FileTransferLogDO; +import cn.orionsec.ops.handler.sftp.FileTransferProcessor; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class DownloadFileProcessor extends FileTransferProcessor { + + public DownloadFileProcessor(FileTransferLogDO record) { + super(record); + } + + @Override + public void exec() { + String localFile = record.getLocalFile(); + String localAbsolutePath = Files1.getPath(SystemEnvAttr.SWAP_PATH.getValue(), localFile); + log.info("sftp文件下载-提交任务 fileToken: {}, machineId: {}, local: {}, remote: {}, record: {}", + fileToken, machineId, localAbsolutePath, record.getRemoteFile(), JSON.toJSONString(record)); + Threads.start(this, SchedulerPools.SFTP_DOWNLOAD_SCHEDULER); + } + + @Override + protected void handler() { + String remoteFile = record.getRemoteFile(); + String localFile = record.getLocalFile(); + String localAbsolutePath = Files1.getPath(SystemEnvAttr.SWAP_PATH.getValue(), localFile); + log.info("sftp文件下载-开始传输 fileToken: {}, machineId: {}, local: {}, remote: {}", + fileToken, machineId, localAbsolutePath, remoteFile); + SftpDownloader download = executor.download(remoteFile, localAbsolutePath); + this.initProgress(download.getProgress()); + download.run(); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/impl/PackageFileProcessor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/impl/PackageFileProcessor.java new file mode 100644 index 0000000..a09c963 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/impl/PackageFileProcessor.java @@ -0,0 +1,276 @@ + +package cn.orionsec.ops.handler.sftp.impl; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.Threads; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.lang.utils.io.Streams; +import cn.orionsec.kit.lang.utils.io.compress.CompressTypeEnum; +import cn.orionsec.kit.lang.utils.io.compress.FileCompressor; +import cn.orionsec.kit.lang.utils.math.Numbers; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.SchedulerPools; +import cn.orionsec.ops.constant.sftp.SftpTransferStatus; +import cn.orionsec.ops.constant.sftp.SftpTransferType; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.dao.FileTransferLogDAO; +import cn.orionsec.ops.entity.domain.FileTransferLogDO; +import cn.orionsec.ops.entity.dto.sftp.FileTransferNotifyProgressDTO; +import cn.orionsec.ops.handler.sftp.IFileTransferProcessor; +import cn.orionsec.ops.handler.sftp.TransferProcessorManager; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.InputStream; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +@Slf4j +public class PackageFileProcessor implements IFileTransferProcessor { + + protected static FileTransferLogDAO fileTransferLogDAO = SpringHolder.getBean(FileTransferLogDAO.class); + + protected static TransferProcessorManager transferProcessorManager = SpringHolder.getBean(TransferProcessorManager.class); + + private static final String FINISH_PROGRESS = "100"; + + /** + * 打包文件 + */ + private final FileTransferLogDO packageFile; + + /** + * 文件列表 + */ + private final List fileList; + + /** + * 文件名映射 + */ + private Map nameMapping; + + /** + * 当前大小 + */ + private final AtomicLong currentSize; + + /** + * 文件总大小 + */ + private final long totalSize; + + /** + * 文件压缩器 + */ + private FileCompressor compressor; + + /** + * 压缩文件路径 + */ + private String compressPath; + + private final Long userId; + + private final Long machineId; + + private final String fileToken; + + private volatile boolean userCancel; + + private volatile boolean done; + + public PackageFileProcessor(FileTransferLogDO packageFile, List fileList) { + this.packageFile = packageFile; + this.fileList = fileList; + this.fileToken = packageFile.getFileToken(); + this.currentSize = new AtomicLong(); + this.totalSize = packageFile.getFileSize(); + this.userId = packageFile.getUserId(); + this.machineId = packageFile.getMachineId(); + } + + @Override + public void exec() { + String localFile = packageFile.getLocalFile(); + this.compressPath = Files1.getPath(SystemEnvAttr.SWAP_PATH.getValue(), localFile); + log.info("sftp文件打包-提交任务 fileToken: {} machineId: {}, local: {}, remote: {}, record: {}, fileList: {}", + fileToken, machineId, compressPath, packageFile.getRemoteFile(), + JSON.toJSONString(packageFile), JSON.toJSONString(fileList)); + Threads.start(this, SchedulerPools.SFTP_PACKAGE_SCHEDULER); + } + + @Override + public void run() { + // 判断是否可以传输 + FileTransferLogDO fileTransferLog = fileTransferLogDAO.selectById(packageFile.getId()); + if (fileTransferLog == null || !SftpTransferStatus.WAIT.getStatus().equals(fileTransferLog.getTransferStatus())) { + return; + } + transferProcessorManager.addProcessor(fileToken, this); + try { + // 通知状态runnable + this.updateStatus(SftpTransferStatus.RUNNABLE); + // 初始化压缩器 + this.compressor = CompressTypeEnum.ZIP.compressor().get(); + compressor.setAbsoluteCompressPath(compressPath); + compressor.compressNotify(this::notifyProgress); + // 添加压缩文件 + this.initCompressFiles(); + // 添加压缩清单 + this.initCompressFileRaw(); + // 二次检查状态 防止在添加文件过程中取消或者删除 + fileTransferLog = fileTransferLogDAO.selectById(packageFile.getId()); + if (fileTransferLog == null || !SftpTransferStatus.RUNNABLE.getStatus().equals(fileTransferLog.getTransferStatus())) { + return; + } + // 开始压缩 + this.compressor.compress(); + // 传输完成通知 + this.updateStatus(SftpTransferStatus.FINISH); + } catch (Exception e) { + log.error("sftp压缩文件-出现异常 fileToken: {}, e: {}, message: {}", fileToken, e.getClass().getName(), e.getMessage()); + // 程序错误并非传输错误修改状态 + if (!userCancel) { + log.error("sftp传输文件-运行异常 fileToken: {}", fileToken, e); + this.updateStatus(SftpTransferStatus.ERROR); + } + e.printStackTrace(); + } finally { + this.done = true; + transferProcessorManager.removeProcessor(fileToken); + } + } + + @Override + public void stop() { + log.info("sftp传输打包-用户取消 fileToken: {}", fileToken); + this.userCancel = true; + // 修改状态为已取消 + this.updateStatus(SftpTransferStatus.CANCEL); + // 取消 + if (compressor != null) { + Streams.close(compressor.getCloseable()); + } + } + + /** + * 初始化压缩文件 + */ + private void initCompressFiles() { + this.nameMapping = Maps.newLinkedMap(); + for (int i = 0; i < fileList.size(); i++) { + FileTransferLogDO fileLog = fileList.get(i); + String remoteFile = fileLog.getRemoteFile(); + String localFilePath = Files1.getPath(SystemEnvAttr.SWAP_PATH.getValue(), fileLog.getLocalFile()); + if (!Files1.isFile(new File(localFilePath))) { + continue; + } + // 添加mapping + String remoteFileName; + if (nameMapping.containsKey(remoteFile)) { + remoteFileName = remoteFile + "_" + (i + 1); + } else { + remoteFileName = remoteFile; + } + nameMapping.put(remoteFileName, fileLog); + compressor.addFile(remoteFileName, localFilePath); + } + } + + /** + * 添加压缩文件清单到压缩列表 + */ + private void initCompressFileRaw() { + // 设置文件清单 + List compressFileRaw = Lists.newList(); + for (FileTransferLogDO fileLog : fileList) { + String remoteFile = fileLog.getRemoteFile(); + String label = SftpTransferType.of(fileLog.getTransferType()).getLabel(); + String localFilePath = Files1.getPath(SystemEnvAttr.SWAP_PATH.getValue(), fileLog.getLocalFile()); + String status = Files1.isFile(localFilePath) ? "成功" : "未找到文件"; + // 添加raw + compressFileRaw.add(label + Const.SPACE + status + Const.SPACE + remoteFile); + } + // 设置文件清单文件 + String compressRawListFile = String.join(Const.LF, compressFileRaw) + Const.LF; + InputStream compressRawListStream = Streams.toInputStream(compressRawListFile); + compressor.addFile(Const.COMPRESS_LIST_FILE, compressRawListStream); + } + + /** + * 通知状态 + * + * @param status status + */ + private void updateStatus(SftpTransferStatus status) { + FileTransferLogDO update = new FileTransferLogDO(); + update.setId(packageFile.getId()); + update.setTransferStatus(status.getStatus()); + if (SftpTransferStatus.FINISH.equals(status)) { + // 设置压缩文件实际大小 + File compressFile = new File(compressPath); + if (Files1.isFile(compressFile)) { + update.setFileSize(compressFile.length()); + update.setCurrentSize(compressFile.length()); + } else { + update.setCurrentSize(packageFile.getFileSize()); + } + update.setNowProgress(100D); + } + int effect = fileTransferLogDAO.updateById(update); + log.info("sftp传输压缩-更新状态 fileToken: {}, status: {}, effect: {}", fileToken, status, effect); + if (SftpTransferStatus.FINISH.equals(status)) { + // 通知进度 + FileTransferNotifyProgressDTO notifyProgress = new FileTransferNotifyProgressDTO(Strings.EMPTY, Files1.getSize(totalSize), FINISH_PROGRESS); + transferProcessorManager.notifySessionProgressEvent(userId, machineId, fileToken, notifyProgress); + } + // 通知状态 + transferProcessorManager.notifySessionStatusEvent(userId, machineId, fileToken, status.getStatus()); + } + + /** + * 通知进度 + * + * @param name name + */ + private void notifyProgress(String name) { + if (done) { + return; + } + FileTransferLogDO compressedFile = nameMapping.get(name); + if (compressedFile == null) { + return; + } + // 计算进度 + long curr = currentSize.addAndGet(compressedFile.getFileSize()); + double progress = this.getProgress(); + String progressRate = Numbers.setScale(progress, 2); + // 更新进度 + FileTransferLogDO update = new FileTransferLogDO(); + update.setId(packageFile.getId()); + update.setCurrentSize(curr); + update.setNowProgress(progress); + fileTransferLogDAO.updateById(update); + // 通知进度 + FileTransferNotifyProgressDTO notifyProgress = new FileTransferNotifyProgressDTO(Strings.EMPTY, Files1.getSize(curr), progressRate); + transferProcessorManager.notifySessionProgressEvent(userId, machineId, fileToken, notifyProgress); + } + + /** + * 获取当前进度 + * + * @return 当前进度 + */ + protected double getProgress() { + if (totalSize == 0) { + return 0; + } + return ((double) currentSize.get() / (double) totalSize) * 100; + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/impl/UploadFileProcessor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/impl/UploadFileProcessor.java new file mode 100644 index 0000000..2f740e2 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/impl/UploadFileProcessor.java @@ -0,0 +1,42 @@ + +package cn.orionsec.ops.handler.sftp.impl; + +import cn.orionsec.kit.lang.utils.Threads; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.net.host.sftp.transfer.SftpUploader; +import cn.orionsec.ops.constant.SchedulerPools; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.entity.domain.FileTransferLogDO; +import cn.orionsec.ops.handler.sftp.FileTransferProcessor; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class UploadFileProcessor extends FileTransferProcessor { + + public UploadFileProcessor(FileTransferLogDO record) { + super(record); + } + + @Override + public void exec() { + String localFile = record.getLocalFile(); + String localAbsolutePath = Files1.getPath(SystemEnvAttr.SWAP_PATH.getValue(), localFile); + log.info("sftp文件上传-提交任务 fileToken: {}, machineId: {}, local: {}, remote: {}, record: {}", + fileToken, machineId, localAbsolutePath, record.getRemoteFile(), JSON.toJSONString(record)); + Threads.start(this, SchedulerPools.SFTP_UPLOAD_SCHEDULER); + } + + @Override + protected void handler() { + String remoteFile = record.getRemoteFile(); + String localFile = record.getLocalFile(); + String localAbsolutePath = Files1.getPath(SystemEnvAttr.SWAP_PATH.getValue(), localFile); + log.info("sftp文件上传-开始传输 fileToken: {}, machineId: {}, local: {}, remote: {}", + fileToken, machineId, localAbsolutePath, remoteFile); + SftpUploader upload = executor.upload(remoteFile, localAbsolutePath); + this.initProgress(upload.getProgress()); + upload.run(); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/notify/FileTransferNotifyHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/notify/FileTransferNotifyHandler.java new file mode 100644 index 0000000..4c97871 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/sftp/notify/FileTransferNotifyHandler.java @@ -0,0 +1,119 @@ + +package cn.orionsec.ops.handler.sftp.notify; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.ws.WsCloseCode; +import cn.orionsec.ops.entity.dto.sftp.SftpSessionTokenDTO; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.handler.sftp.TransferProcessorManager; +import cn.orionsec.ops.service.api.PassportService; +import cn.orionsec.ops.service.api.SftpService; +import cn.orionsec.ops.utils.WebSockets; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.*; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; + +@Component +@Slf4j +public class FileTransferNotifyHandler implements WebSocketHandler { + + @Resource + private TransferProcessorManager transferProcessorManager; + + @Resource + private PassportService passportService; + + @Resource + private SftpService sftpService; + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + String id = session.getId(); + String token = WebSockets.getToken(session); + try { + SftpSessionTokenDTO tokenInfo = sftpService.getTokenInfo(token); + Long userId = tokenInfo.getUserId(); + Long machineId = tokenInfo.getMachineId(); + List machineIdList = tokenInfo.getMachineIdList(); + if (machineIdList == null) { + machineIdList = Lists.newList(); + } + if (machineId != null) { + machineIdList.add(machineId); + } + session.getAttributes().put(WebSockets.UID, userId); + session.getAttributes().put(WebSockets.MID, machineIdList); + log.info("sftp-Notify 建立连接成功 id: {}, token: {}, userId: {}, machineId: {}, machineIdList: {}", id, token, userId, machineId, machineIdList); + } catch (Exception e) { + log.error("sftp-Notify 建立连接失败-未查询到token信息 id: {}, token: {}", id, token, e); + WebSockets.close(session, WsCloseCode.FORGE_TOKEN); + } + } + + @Override + @SuppressWarnings("unchecked") + public void handleMessage(WebSocketSession session, WebSocketMessage message) { + String id = session.getId(); + Map attributes = session.getAttributes(); + if (attributes.get(WebSockets.AUTHED) != null) { + return; + } + if (!(message instanceof TextMessage)) { + return; + } + // 获取body + String authToken = ((TextMessage) message).getPayload(); + if (Strings.isEmpty(authToken)) { + log.info("sftp-Notify 认证失败-body为空 id: {}", id); + WebSockets.close(session, WsCloseCode.INCORRECT_TOKEN); + return; + } + // 获取认证用户 + UserDTO user = passportService.getUserByToken(authToken, null); + if (user == null) { + log.info("sftp-Notify 认证失败-未查询到用户 id: {}, authToken: {}", id, authToken); + WebSockets.close(session, WsCloseCode.INCORRECT_TOKEN); + return; + } + // 检查认证用户是否匹配 + Long userId = user.getId(); + Long tokenUserId = (Long) attributes.get(WebSockets.UID); + final boolean valid = userId.equals(tokenUserId); + if (!valid) { + log.info("sftp-Notify 认证失败-用户不匹配 id: {}, userId: {}, tokenUserId: {}", id, userId, tokenUserId); + WebSockets.close(session, WsCloseCode.VALID); + return; + } + attributes.put(WebSockets.AUTHED, Const.ENABLE); + // 注册会话 + List machineIdList = (List) attributes.get(WebSockets.MID); + Lists.forEach(machineIdList, i -> { + log.info("sftp-Notify 认证成功 id: {}, userId: {}, machineId: {}", id, userId, i); + transferProcessorManager.registerSessionNotify(id, session, userId, i); + }); + } + + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) { + log.error("sftp-Notify 操作异常拦截 id: {}", session.getId()); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { + String id = session.getId(); + transferProcessorManager.closeSessionNotify(id); + log.info("sftp-Notify 关闭连接 id: {}, code: {}, reason: {}", id, status.getCode(), status.getReason()); + } + + @Override + public boolean supportsPartialMessages() { + return false; + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/ITailHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/ITailHandler.java new file mode 100644 index 0000000..1fe93ed --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/ITailHandler.java @@ -0,0 +1,66 @@ + +package cn.orionsec.ops.handler.tail; + +import cn.orionsec.kit.lang.able.SafeCloseable; +import cn.orionsec.kit.lang.function.select.Branches; +import cn.orionsec.kit.lang.function.select.Selector; +import cn.orionsec.ops.constant.tail.FileTailMode; +import cn.orionsec.ops.handler.tail.impl.ExecTailFileHandler; +import cn.orionsec.ops.handler.tail.impl.TrackerTailFileHandler; +import org.springframework.web.socket.WebSocketSession; + +public interface ITailHandler extends SafeCloseable { + + /** + * 开始 + * + * @throws Exception Exception + */ + void start() throws Exception; + + /** + * 获取机器id + * + * @return 机器id + */ + Long getMachineId(); + + /** + * 获取文件路径 + * + * @return 文件路径 + */ + String getFilePath(); + + /** + * 设置最后修改时间 + */ + default void setLastModify() { + } + + /** + * 写入命令 + * + * @param command command + */ + default void write(String command) { + } + + /** + * 获取实际执行 handler + * + * @param mode mode + * @param hint hint + * @param session session + * @return handler + */ + static ITailHandler with(FileTailMode mode, TailFileHint hint, WebSocketSession session) { + return Selector.of(mode) + .test(Branches.eq(FileTailMode.TRACKER) + .then(() -> new TrackerTailFileHandler(hint, session))) + .test(Branches.eq(FileTailMode.TAIL) + .then(() -> new ExecTailFileHandler(hint, session))) + .get(); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/TailFileHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/TailFileHandler.java new file mode 100644 index 0000000..a3a06df --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/TailFileHandler.java @@ -0,0 +1,94 @@ + +package cn.orionsec.ops.handler.tail; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.constant.tail.FileTailMode; +import cn.orionsec.ops.entity.dto.file.FileTailDTO; +import cn.orionsec.ops.utils.WebSockets; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.WebSocketMessage; +import org.springframework.web.socket.WebSocketSession; + +import javax.annotation.Resource; + +@Component +@Slf4j +public class TailFileHandler implements WebSocketHandler { + + @Resource + private TailSessionHolder tailSessionHolder; + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + String id = session.getId(); + FileTailDTO config = (FileTailDTO) session.getAttributes().get(WebSockets.CONFIG); + String token = (String) session.getAttributes().get(WebSockets.TOKEN); + log.info("tail 建立ws连接 token: {}, id: {}, config: {}", token, id, JSON.toJSONString(config)); + try { + this.openTailHandler(session, config, token); + } catch (Exception e) { + log.error("tail 打开处理器-失败 id: {}", id, e); + } + } + + @Override + public void handleMessage(WebSocketSession session, WebSocketMessage message) { + } + + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) { + log.error("tail 操作异常拦截 token: {}, id: {}", WebSockets.getToken(session), session.getId(), exception); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { + String token = (String) session.getAttributes().get(WebSockets.TOKEN); + // 释放资源 + ITailHandler handler = tailSessionHolder.removeSession(token); + if (handler == null) { + return; + } + handler.close(); + } + + @Override + public boolean supportsPartialMessages() { + return false; + } + + /** + * 打开文件处理器 + * + * @param session session + * @param fileTail tailDTO + * @param token token + * @throws Exception Exception + */ + private void openTailHandler(WebSocketSession session, FileTailDTO fileTail, String token) throws Exception { + TailFileHint hint = new TailFileHint(); + hint.setToken(token); + hint.setMachineId(fileTail.getMachineId()); + hint.setPath(fileTail.getFilePath()); + hint.setOffset(fileTail.getOffset()); + hint.setCharset(fileTail.getCharset()); + hint.setCommand(fileTail.getCommand()); + FileTailMode mode = FileTailMode.of(fileTail.getMode()); + if (FileTailMode.TRACKER.equals(mode)) { + // 获取 delay + String delayValue = SystemEnvAttr.TRACKER_DELAY_TIME.getValue(); + int delay = Strings.isInteger(delayValue) ? Integer.parseInt(delayValue) : Const.TRACKER_DELAY_MS; + hint.setDelay(Math.max(delay, Const.MIN_TRACKER_DELAY_MS)); + } + log.info("tail 打开处理器-开始 token: {}, mode: {}, hint: {}", token, mode, JSON.toJSONString(hint)); + ITailHandler handler = ITailHandler.with(mode, hint, session); + tailSessionHolder.addSession(token, handler); + handler.start(); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/TailFileHint.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/TailFileHint.java new file mode 100644 index 0000000..efc832f --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/TailFileHint.java @@ -0,0 +1,44 @@ + +package cn.orionsec.ops.handler.tail; + +import lombok.Data; + +@Data +public class TailFileHint { + + /** + * token + */ + private String token; + + /** + * 文件 + */ + private String path; + + /** + * 机器id + */ + private Long machineId; + + /** + * 尾行偏移量 + */ + private Integer offset; + + /** + * 编码格式 + */ + private String charset; + + /** + * tail 命令 + */ + private String command; + + /** + * 延迟时间 + */ + private int delay; + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/TailSessionHolder.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/TailSessionHolder.java new file mode 100644 index 0000000..59d5df8 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/TailSessionHolder.java @@ -0,0 +1,105 @@ + +package cn.orionsec.ops.handler.tail; + +import cn.orionsec.kit.lang.utils.Threads; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.ops.constant.Const; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + + +@Slf4j +@Component +public class TailSessionHolder { + + /** + * key: token + * value: ITailHandler + */ + private final Map holder = Maps.newCurrentHashMap(); + + /** + * key: machineId:filePath + * value: token + */ + private final Map> fileTokenMapping = Maps.newCurrentHashMap(); + + /** + * 添加session + * + * @param token token + * @param session session + */ + public void addSession(String token, ITailHandler session) { + holder.put(token, session); + fileTokenMapping.computeIfAbsent(session.getMachineId() + ":" + session.getFilePath(), s -> Lists.newList()).add(token); + } + + /** + * 获取session + * + * @param token token + * @return session + */ + public ITailHandler getSession(String token) { + return holder.get(token); + } + + /** + * 获取session + * + * @param machineId machineId + * @param path path + * @return session + */ + public List getSession(Long machineId, String path) { + List tokenList = fileTokenMapping.get(machineId + ":" + path); + if (Lists.isEmpty(tokenList)) { + return Lists.empty(); + } + return tokenList.stream() + .map(holder::get) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + /** + * 删除session + * + * @param token token + * @return session + */ + public ITailHandler removeSession(String token) { + ITailHandler handler = holder.remove(token); + if (handler != null) { + fileTokenMapping.remove(handler.getMachineId() + ":" + handler.getFilePath()); + } + return handler; + } + + /** + * 异步关闭进行中的 tail + * + * @param machineId machineId + * @param path path + */ + public void asyncCloseTailFile(Long machineId, String path) { + Threads.start(() -> { + try { + Threads.sleep(Const.MS_S_1); + this.getSession(machineId, path).forEach(ITailHandler::setLastModify); + Threads.sleep(Const.MS_S_5); + this.getSession(machineId, path).forEach(ITailHandler::close); + } catch (Exception e) { + log.error("关闭tailingFile失败 machineId: {}, path: {}", machineId, path, e); + } + }); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/impl/ExecTailFileHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/impl/ExecTailFileHandler.java new file mode 100644 index 0000000..cfbfa49 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/impl/ExecTailFileHandler.java @@ -0,0 +1,166 @@ + +package cn.orionsec.ops.handler.tail.impl; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.io.Streams; +import cn.orionsec.kit.net.host.SessionStore; +import cn.orionsec.kit.net.host.ssh.command.CommandExecutor; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.SchedulerPools; +import cn.orionsec.ops.constant.ws.WsCloseCode; +import cn.orionsec.ops.handler.tail.ITailHandler; +import cn.orionsec.ops.handler.tail.TailFileHint; +import cn.orionsec.ops.service.api.MachineInfoService; +import cn.orionsec.ops.utils.WebSockets; +import com.alibaba.fastjson.JSON; +import lombok.Getter; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.socket.BinaryMessage; +import org.springframework.web.socket.WebSocketSession; + +import java.io.InputStream; + +@Slf4j +public class ExecTailFileHandler implements ITailHandler { + + protected static MachineInfoService machineInfoService = SpringHolder.getBean(MachineInfoService.class); + + @Getter + private final String token; + + /** + * session + */ + private final WebSocketSession session; + + /** + * hint + */ + private final TailFileHint hint; + + private SessionStore sessionStore; + + private CommandExecutor executor; + + private volatile boolean close; + + public ExecTailFileHandler(TailFileHint hint, WebSocketSession session) { + this.token = hint.getToken(); + this.hint = hint; + this.session = session; + log.info("tail EXEC_TAIL 监听文件初始化 token: {}, hint: {}", token, JSON.toJSONString(hint)); + } + + @Override + public void start() throws Exception { + try { + // 打开session + this.sessionStore = machineInfoService.openSessionStore(hint.getMachineId()); + log.info("tail 建立连接成功 machineId: {}", hint.getMachineId()); + } catch (Exception e) { + WebSockets.openSessionStoreThrowClose(session, e); + log.error("tail 建立连接失败-连接远程服务器失败 e: {}, machineId: {}", e, hint.getMachineId()); + return; + } + // 打开 command + this.executor = sessionStore.getCommandExecutor(Strings.replaceCRLF(hint.getCommand())); + executor.merge(); + executor.callback(this::callback); + executor.streamHandler(this::handler); + executor.connect(); + SchedulerPools.TAIL_SCHEDULER.execute(executor); + log.info("tail EXEC_TAIL 监听文件开始 token: {}", token); + } + + @Override + public void write(String command) { + executor.write(command); + } + + @Override + public Long getMachineId() { + return hint.getMachineId(); + } + + @Override + public String getFilePath() { + return hint.getPath(); + } + + /** + * 回调 + */ + private void callback() { + log.info("tail EXEC_TAIL 监听文件结束 token: {}", token); + WebSockets.close(session, WsCloseCode.EOF); + } + + /** + * 处理标准输入流 + * + * @param input 流 + */ + @SneakyThrows + private void handler(InputStream input) { + byte[] buffer = new byte[Const.BUFFER_KB_8]; + int read; + while ((read = input.read(buffer)) != -1) { + if (session.isOpen()) { + session.sendMessage(new BinaryMessage(buffer, 0, read, true)); + } + } + + // 2.0 tail 换行有问题 + // BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, hint.getCharset()), Const.BUFFER_KB_8); + // String line; + // while ((line = reader.readLine()) != null) { + // session.sendMessage(new TextMessage(line)); + // } + + // 1.0 tracker 换行有问题 + // char[] buffer = new char[Const.BUFFER_KB_4]; + // int read; + // StringBuilder sb = new StringBuilder(); + // // tail命令结合BufferedReader对CR处理有问题 所以不能使用readLine + // while ((read = reader.read(buffer)) != -1) { + // int mark = -1; + // for (int i = 0; i < read; i++) { + // // 读取到行结尾 + // if (buffer[i] == Letters.LF) { + // sb.append(buffer, mark + 1, i - mark - 1); + // if (session.isOpen()) { + // String payload = sb.toString().replaceAll(Const.CR, Const.EMPTY); + // session.sendMessage(new TextMessage(payload)); + // } + // sb = new StringBuilder(); + // mark = i; + // } + // } + // // 不是完整的一行 + // if (mark == -1) { + // sb.append(buffer, 0, read); + // } else if (mark != read - 1) { + // sb.append(buffer, mark + 1, read - mark - 1); + // } + // } + } + + @Override + @SneakyThrows + public void close() { + if (close) { + return; + } + this.close = true; + Streams.close(executor); + Streams.close(sessionStore); + } + + @Override + public String toString() { + return hint.getPath(); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/impl/TrackerTailFileHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/impl/TrackerTailFileHandler.java new file mode 100644 index 0000000..6a8de05 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/tail/impl/TrackerTailFileHandler.java @@ -0,0 +1,112 @@ + +package cn.orionsec.ops.handler.tail.impl; + +import cn.orionsec.kit.ext.tail.Tracker; +import cn.orionsec.kit.ext.tail.delay.DelayTrackerListener; +import cn.orionsec.kit.ext.tail.handler.DataHandler; +import cn.orionsec.kit.ext.tail.mode.FileNotFoundMode; +import cn.orionsec.kit.ext.tail.mode.FileOffsetMode; +import cn.orionsec.kit.lang.define.thread.HookRunnable; +import cn.orionsec.kit.lang.utils.Threads; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.SchedulerPools; +import cn.orionsec.ops.constant.ws.WsCloseCode; +import cn.orionsec.ops.handler.tail.ITailHandler; +import cn.orionsec.ops.handler.tail.TailFileHint; +import cn.orionsec.ops.utils.WebSockets; +import com.alibaba.fastjson.JSON; +import lombok.Getter; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.socket.BinaryMessage; +import org.springframework.web.socket.WebSocketSession; + +@Slf4j +public class TrackerTailFileHandler implements ITailHandler, DataHandler { + + @Getter + private final String token; + + /** + * session + */ + private final WebSocketSession session; + + /** + * hint + */ + private final TailFileHint hint; + + private DelayTrackerListener tracker; + + private volatile boolean close; + + @Getter + private final String filePath; + + public TrackerTailFileHandler(TailFileHint hint, WebSocketSession session) { + this.token = hint.getToken(); + this.hint = hint; + this.filePath = hint.getPath(); + this.session = session; + log.info("tail TRACKER 监听文件初始化 token: {}, hint: {}", token, JSON.toJSONString(hint)); + } + + @Override + public void start() { + this.tracker = new DelayTrackerListener(filePath, this); + tracker.delayMillis(hint.getDelay()); + tracker.charset(hint.getCharset()); + tracker.offset(FileOffsetMode.LINE, hint.getOffset()); + tracker.notFoundMode(FileNotFoundMode.WAIT_COUNT, 10); + Threads.start(new HookRunnable(() -> { + log.info("tail TRACKER 开始监听文件 token: {}", token); + tracker.tail(); + }, this::callback), SchedulerPools.TAIL_SCHEDULER); + } + + @Override + public void setLastModify() { + tracker.setFileLastModifyTime(); + } + + @Override + public Long getMachineId() { + return Const.HOST_MACHINE_ID; + } + + /** + * 回调 + */ + @SneakyThrows + private void callback() { + log.info("tail TRACKER 监听文件结束 token: {}", token); + WebSockets.close(session, WsCloseCode.EOF); + } + + @SneakyThrows + @Override + public void read(byte[] bytes, int len, Tracker tracker) { + if (session.isOpen()) { + session.sendMessage(new BinaryMessage(bytes, 0, len, true)); + } + } + + @Override + @SneakyThrows + public void close() { + if (close) { + return; + } + this.close = true; + if (tracker != null) { + tracker.stop(); + } + } + + @Override + public String toString() { + return filePath; + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/IOperateHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/IOperateHandler.java new file mode 100644 index 0000000..c2c4a13 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/IOperateHandler.java @@ -0,0 +1,59 @@ + +package cn.orionsec.ops.handler.terminal; + +import cn.orionsec.kit.lang.able.SafeCloseable; +import cn.orionsec.ops.constant.terminal.TerminalClientOperate; +import cn.orionsec.ops.entity.config.TerminalConnectConfig; +import cn.orionsec.ops.handler.terminal.manager.TerminalManagementHandler; +import cn.orionsec.ops.handler.terminal.watcher.ITerminalWatcherProcessor; + +public interface IOperateHandler extends TerminalManagementHandler, SafeCloseable { + + /** + * 建立连接 + */ + void connect(); + + /** + * 断开连接 + */ + void disconnect(); + + /** + * 处理消息 + * + * @param operate 操作 + * @param body body + * @throws Exception ex + */ + void handleMessage(TerminalClientOperate operate, String body) throws Exception; + + /** + * 心跳是否结束 + * + * @return true结束 + */ + boolean isDown(); + + /** + * 获取token + * + * @return token + */ + String getToken(); + + /** + * 获取终端配置 + * + * @return 终端配置 + */ + TerminalConnectConfig getHint(); + + /** + * 获取监视器 + * + * @return processor + */ + ITerminalWatcherProcessor getWatcher(); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/TerminalMessageHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/TerminalMessageHandler.java new file mode 100644 index 0000000..3f67e60 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/TerminalMessageHandler.java @@ -0,0 +1,192 @@ + +package cn.orionsec.ops.handler.terminal; + +import cn.orionsec.kit.lang.define.wrapper.Tuple; +import cn.orionsec.kit.net.host.SessionStore; +import cn.orionsec.ops.constant.terminal.TerminalClientOperate; +import cn.orionsec.ops.constant.ws.WsCloseCode; +import cn.orionsec.ops.constant.ws.WsProtocol; +import cn.orionsec.ops.entity.config.TerminalConnectConfig; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.domain.MachineTerminalLogDO; +import cn.orionsec.ops.entity.dto.terminal.TerminalConnectDTO; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.handler.terminal.manager.TerminalSessionManager; +import cn.orionsec.ops.service.api.MachineInfoService; +import cn.orionsec.ops.service.api.MachineTerminalService; +import cn.orionsec.ops.service.api.PassportService; +import cn.orionsec.ops.utils.WebSockets; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.*; + +import javax.annotation.Resource; +import java.util.Date; + +@Slf4j +@Component("terminalMessageHandler") +public class TerminalMessageHandler implements WebSocketHandler { + + @Resource + private TerminalSessionManager terminalSessionManager; + + @Resource + private PassportService passportService; + + @Resource + private MachineInfoService machineInfoService; + + @Resource + private MachineTerminalService machineTerminalService; + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + log.info("terminal 已建立连接 token: {}, id: {}, params: {}", WebSockets.getToken(session), session.getId(), session.getAttributes()); + } + + @Override + public void handleMessage(WebSocketSession session, WebSocketMessage message) { + if (!(message instanceof TextMessage)) { + return; + } + String token = session.getId(); + try { + // 解析请求 + Tuple tuple = WebSockets.parsePayload(((TextMessage) message).getPayload()); + if (tuple == null) { + WebSockets.sendText(session, WsProtocol.ERROR.get()); + return; + } + TerminalClientOperate operate = tuple.get(0); + String body = tuple.get(1); + + // 建立连接 + if (operate == TerminalClientOperate.CONNECT) { + // 建立连接 + if (session.getAttributes().get(WebSockets.CONNECTED) != null) { + return; + } + this.connect(session, token, body); + return; + } + // 检查连接 + if (session.getAttributes().get(WebSockets.CONNECTED) == null) { + WebSockets.close(session, WsCloseCode.VALID); + return; + } + // 获取连接 + IOperateHandler handler = terminalSessionManager.getSession(token); + if (handler == null) { + WebSockets.close(session, WsCloseCode.UNKNOWN_CONNECT); + return; + } + // 操作 + handler.handleMessage(operate, body); + } catch (Exception e) { + log.error("terminal 处理操作异常 token: {}", token, e); + WebSockets.close(session, WsCloseCode.RUNTIME_EXCEPTION); + } + } + + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) { + log.error("terminal 操作异常拦截 token: {}", session.getId(), exception); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { + String token = session.getId(); + int code = status.getCode(); + log.info("terminal 关闭连接 token: {}, code: {}, reason: {}", token, code, status.getReason()); + // 释放资源 + IOperateHandler handler = terminalSessionManager.removeSession(token); + if (handler == null) { + return; + } + handler.close(); + // 修改日志 + MachineTerminalLogDO updateLog = new MachineTerminalLogDO(); + updateLog.setCloseCode(code); + updateLog.setDisconnectedTime(new Date()); + Integer effect = machineTerminalService.updateAccessLog(token, updateLog); + log.info("terminal 连接关闭更新日志 token: {}, effect: {}", token, effect); + } + + @Override + public boolean supportsPartialMessages() { + return false; + } + + /** + * 建立连接 + * + * @param session session + * @param token token + * @param body body + */ + private void connect(WebSocketSession session, String token, String body) { + log.info("terminal 尝试建立连接 token: {}, body: {}", token, body); + // 检查参数 + TerminalConnectDTO connectInfo = TerminalUtils.parseConnectBody(body); + if (connectInfo == null) { + WebSockets.sendText(session, WsProtocol.ERROR.get()); + return; + } + Long userId = (Long) session.getAttributes().get(WebSockets.UID); + Long machineId = (Long) session.getAttributes().get(WebSockets.MID); + + // 获取登录用户 + UserDTO userDTO = passportService.getUserByToken(connectInfo.getLoginToken(), null); + if (userDTO == null || !userId.equals(userDTO.getId())) { + log.info("terminal 建立连接拒绝-用户认证失败 token: {}", token); + WebSockets.close(session, WsCloseCode.IDENTITY_MISMATCH); + return; + } + // 获取机器信息 + MachineInfoDO machine = machineInfoService.selectById(machineId); + if (machine == null) { + log.info("terminal 建立连接拒绝-未查询到机器信息 token: {}, machineId: {}", token, machineId); + WebSockets.close(session, WsCloseCode.INVALID_MACHINE); + return; + } + session.getAttributes().put(WebSockets.CONNECTED, 1); + // 建立连接 + SessionStore sessionStore; + try { + // 打开session + sessionStore = machineInfoService.openSessionStore(machine); + WebSockets.sendText(session, WsProtocol.CONNECTED.get()); + } catch (Exception e) { + WebSockets.openSessionStoreThrowClose(session, e); + log.error("terminal 建立连接失败-连接远程服务器失败 uid: {}, machineId: {}", userId, machineId, e); + return; + } + + // 配置 + TerminalConnectConfig hint = new TerminalConnectConfig(); + String terminalType = machineTerminalService.getMachineConfig(machineId).getTerminalType(); + hint.setUserId(userId); + hint.setUsername(userDTO.getUsername()); + hint.setMachineId(machineId); + hint.setMachineName(machine.getMachineName()); + hint.setMachineHost(machine.getMachineHost()); + hint.setMachineTag(machine.getMachineTag()); + hint.setCols(connectInfo.getCols()); + hint.setRows(connectInfo.getRows()); + hint.setTerminalType(terminalType); + TerminalOperateHandler terminalHandler = new TerminalOperateHandler(token, hint, session, sessionStore); + try { + // 打开shell + log.info("terminal 尝试建立连接-尝试打开shell token: {}", token); + terminalHandler.connect(); + log.info("terminal 建立连接成功-打开shell成功 token: {}", token); + } catch (Exception e) { + WebSockets.close(session, WsCloseCode.OPEN_SHELL_EXCEPTION); + log.error("terminal 建立连接失败-打开shell失败 machineId: {}, uid: {}", machineId, userId, e); + return; + } + terminalSessionManager.addSession(token, terminalHandler); + log.info("terminal 建立连接成功 uid: {}, machineId: {}", userId, machineId); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/TerminalOperateHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/TerminalOperateHandler.java new file mode 100644 index 0000000..403c80f --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/TerminalOperateHandler.java @@ -0,0 +1,290 @@ + +package cn.orionsec.ops.handler.terminal; + +import cn.orionsec.kit.lang.constant.Letters; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.lang.utils.io.Streams; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.kit.net.host.SessionStore; +import cn.orionsec.kit.net.host.ssh.shell.ShellExecutor; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.SchedulerPools; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.constant.terminal.TerminalClientOperate; +import cn.orionsec.ops.constant.terminal.TerminalConst; +import cn.orionsec.ops.constant.ws.WsCloseCode; +import cn.orionsec.ops.constant.ws.WsProtocol; +import cn.orionsec.ops.entity.config.TerminalConnectConfig; +import cn.orionsec.ops.entity.domain.MachineTerminalLogDO; +import cn.orionsec.ops.entity.dto.terminal.TerminalSizeDTO; +import cn.orionsec.ops.handler.terminal.screen.TerminalScreenEnv; +import cn.orionsec.ops.handler.terminal.screen.TerminalScreenHeader; +import cn.orionsec.ops.handler.terminal.watcher.ITerminalWatcherProcessor; +import cn.orionsec.ops.handler.terminal.watcher.TerminalWatcherProcessor; +import cn.orionsec.ops.service.api.MachineTerminalService; +import cn.orionsec.ops.utils.PathBuilders; +import cn.orionsec.ops.utils.Utils; +import cn.orionsec.ops.utils.WebSockets; +import com.alibaba.fastjson.JSON; +import lombok.Getter; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.socket.WebSocketSession; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Date; + +@Slf4j +public class TerminalOperateHandler implements IOperateHandler { + + private static final MachineTerminalService machineTerminalService = SpringHolder.getBean(MachineTerminalService.class); + + private static final String SCREEN_BODY_TEMPLATE = "[{}, \"o\", \"{}\"]"; + + @Getter + private final String token; + + @Getter + private final TerminalConnectConfig hint; + + @Getter + private final ITerminalWatcherProcessor watcher; + + private final WebSocketSession session; + + private final SessionStore sessionStore; + + private ShellExecutor executor; + + private long connectedTime; + + /** + * 录屏流 + */ + private OutputStream screenStream; + + /** + * 最后一次心跳通讯时间 + */ + private volatile long lastHeartbeat; + + protected volatile boolean close; + + public TerminalOperateHandler(String token, TerminalConnectConfig hint, WebSocketSession session, SessionStore sessionStore) { + this.token = token; + this.hint = hint; + this.watcher = new TerminalWatcherProcessor(); + this.session = session; + this.sessionStore = sessionStore; + this.lastHeartbeat = System.currentTimeMillis(); + this.initShell(); + } + + @Override + public void connect() { + executor.connect(); + executor.streamHandler(this::streamHandler); + // 连接成功后初始化日志信息 + this.initLog(); + // 开始监听输出 + SchedulerPools.TERMINAL_SCHEDULER.execute(executor); + watcher.watch(); + } + + /** + * 初始化 shell + */ + private void initShell() { + // 初始化 shell 执行器 + this.executor = sessionStore.getShellExecutor(); + executor.terminalType(hint.getTerminalType()); + executor.size(hint.getCols(), hint.getRows()); + } + + /** + * 初始化日志 + */ + private void initLog() { + this.connectedTime = System.currentTimeMillis(); + hint.setConnectedTime(new Date(connectedTime)); + // 初始化录屏 + String screenPath = this.initScreenStream(); + log.info("terminal 开始记录用户操作录屏: {} {}", token, screenPath); + // 记录日志 + MachineTerminalLogDO logEntity = new MachineTerminalLogDO(); + logEntity.setAccessToken(token); + logEntity.setUserId(hint.getUserId()); + logEntity.setUsername(hint.getUsername()); + logEntity.setMachineId(hint.getMachineId()); + logEntity.setMachineName(hint.getMachineName()); + logEntity.setMachineTag(hint.getMachineTag()); + logEntity.setMachineHost(hint.getMachineHost()); + logEntity.setConnectedTime(hint.getConnectedTime()); + logEntity.setScreenPath(screenPath); + Long logId = machineTerminalService.addTerminalLog(logEntity); + hint.setLogId(logId); + log.info("terminal 保存用户操作日志: {} logId: {}", token, logId); + } + + /** + * 初始化录屏流 + * + * @return path + */ + @SneakyThrows + private String initScreenStream() { + // 初始化流 + String screenPath = PathBuilders.getTerminalScreenPath(hint.getUserId(), hint.getMachineId()); + String realScreenPath = Files1.getPath(SystemEnvAttr.SCREEN_PATH.getValue(), screenPath); + this.screenStream = Files1.openOutputStreamFastSafe(realScreenPath); + // 设置头 + TerminalScreenHeader header = new TerminalScreenHeader(); + String title = Strings.format("{}({}) {} {}", hint.getMachineName(), hint.getMachineHost(), + hint.getUsername(), Dates.format(hint.getConnectedTime())); + header.setTitle(title); + header.setCols(hint.getCols()); + header.setRows(hint.getRows()); + header.setTimestamp(connectedTime / Dates.SECOND_STAMP); + header.setEnv(new TerminalScreenEnv(hint.getTerminalType())); + // 拼接头 + screenStream.write(JSON.toJSONBytes(header)); + screenStream.write(Letters.LF); + screenStream.flush(); + return screenPath; + } + + /** + * 标准输出处理 + * + * @param inputStream stream + */ + private void streamHandler(InputStream inputStream) { + byte[] bs = new byte[Const.BUFFER_KB_4]; + BufferedInputStream in = new BufferedInputStream(inputStream, Const.BUFFER_KB_4); + int read; + try { + while (session.isOpen() && (read = in.read(bs)) != -1) { + // 响应 + byte[] msg = WsProtocol.OK.msg(bs, 0, read); + WebSockets.sendText(session, msg); + // 响应监视 + watcher.sendMessage(msg); + // 记录录屏 + String row = Strings.format(SCREEN_BODY_TEMPLATE, + ((double) (System.currentTimeMillis() - connectedTime)) / Dates.SECOND_STAMP, + Utils.convertControlUnicode(new String(bs, 0, read))); + screenStream.write(Strings.bytes(row)); + screenStream.write(Letters.LF); + screenStream.flush(); + } + } catch (IOException ex) { + log.error("terminal 读取流失败", ex); + WebSockets.close(session, WsCloseCode.READ_EXCEPTION); + } + // eof + if (close) { + return; + } + WebSockets.close(session, WsCloseCode.EOF); + log.info("terminal eof回调 {}", token); + } + + @Override + public void disconnect() { + if (close) { + return; + } + this.close = true; + try { + Streams.close(screenStream); + Streams.close(executor); + Streams.close(sessionStore); + } catch (Exception e) { + log.error("terminal 断开连接 失败 token: {}", token, e); + } + } + + @Override + public void forcedOffline() { + WebSockets.close(session, WsCloseCode.FORCED_OFFLINE); + log.info("terminal 管理员强制断连 {}", token); + } + + @Override + public void heartbeatDownClose() { + WebSockets.close(session, WsCloseCode.HEART_DOWN); + log.info("terminal 心跳结束断连 {}", token); + } + + @Override + public void sendHeartbeat() { + WebSockets.sendText(session, WsProtocol.PING.get()); + } + + @Override + public boolean isDown() { + return (System.currentTimeMillis() - lastHeartbeat) > TerminalConst.TERMINAL_CONNECT_DOWN; + } + + @Override + public void handleMessage(TerminalClientOperate operate, String body) { + if (close) { + return; + } + switch (operate) { + case KEY: + executor.write(Strings.bytes(body)); + return; + case PING: + // client 主动发送 ping + this.lastHeartbeat = System.currentTimeMillis(); + WebSockets.sendText(session, WsProtocol.PONG.get()); + return; + case PONG: + // server 主动发送 ping, client 响应 pong + this.lastHeartbeat = System.currentTimeMillis(); + return; + case RESIZE: + this.resize(body); + return; + case COMMAND: + executor.write(Strings.bytes(body)); + executor.write(new byte[]{Letters.LF}); + case CLEAR: + executor.write(new byte[]{12}); + return; + default: + } + } + + @Override + public void close() { + this.disconnect(); + Streams.close(watcher); + } + + /** + * 重置大小 + */ + private void resize(String body) { + // 检查参数 + TerminalSizeDTO window = TerminalUtils.parseResizeBody(body); + if (window == null) { + WebSockets.sendText(session, WsProtocol.ERROR.get()); + return; + } + hint.setCols(window.getCols()); + hint.setRows(window.getRows()); + if (!executor.isConnected()) { + executor.connect(); + } + executor.size(window.getCols(), window.getRows()); + executor.resize(); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/TerminalUtils.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/TerminalUtils.java new file mode 100644 index 0000000..0b89caf --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/TerminalUtils.java @@ -0,0 +1,60 @@ + +package cn.orionsec.ops.handler.terminal; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.ops.entity.dto.terminal.TerminalConnectDTO; +import cn.orionsec.ops.entity.dto.terminal.TerminalSizeDTO; + +public class TerminalUtils { + + private TerminalUtils() { + } + + /** + * 解析连接参数 + *

+ * .e.g cols|rows|loginToken + * + * @param body body + * @return connect + */ + public static TerminalConnectDTO parseConnectBody(String body) { + String[] arr = body.split("\\|"); + if (arr.length != 3) { + return null; + } + // 解析 size + if (!Strings.isInteger(arr[0]) || !Strings.isInteger(arr[1])) { + return null; + } + TerminalConnectDTO connect = new TerminalConnectDTO(); + connect.setCols(Integer.parseInt(arr[0])); + connect.setRows(Integer.parseInt(arr[1])); + connect.setLoginToken(arr[2]); + return connect; + } + + /** + * 解析修改大小参数 + *

+ * .e.g cols|rows + * + * @param body body + * @return size + */ + public static TerminalSizeDTO parseResizeBody(String body) { + String[] arr = body.split("\\|"); + if (arr.length != 2) { + return null; + } + // 解析 size + if (!Strings.isInteger(arr[0]) || !Strings.isInteger(arr[1])) { + return null; + } + TerminalSizeDTO size = new TerminalSizeDTO(); + size.setCols(Integer.parseInt(arr[0])); + size.setRows(Integer.parseInt(arr[1])); + return size; + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/manager/TerminalManagementHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/manager/TerminalManagementHandler.java new file mode 100644 index 0000000..2ab589a --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/manager/TerminalManagementHandler.java @@ -0,0 +1,25 @@ + +package cn.orionsec.ops.handler.terminal.manager; + +public interface TerminalManagementHandler { + + /** + * 管理员强制下线 + * + * @throws Exception Exception + */ + void forcedOffline() throws Exception; + + /** + * 心跳结束下线 + * + * @throws Exception Exception + */ + void heartbeatDownClose() throws Exception; + + /** + * 主动发送心跳 + */ + void sendHeartbeat(); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/manager/TerminalSessionManager.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/manager/TerminalSessionManager.java new file mode 100644 index 0000000..e6cc20c --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/manager/TerminalSessionManager.java @@ -0,0 +1,167 @@ + +package cn.orionsec.ops.handler.terminal.manager; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.id.UUIds; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.Valid; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.lang.utils.time.DateRanges; +import cn.orionsec.ops.constant.KeyConst; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.entity.config.TerminalConnectConfig; +import cn.orionsec.ops.entity.dto.terminal.TerminalWatcherDTO; +import cn.orionsec.ops.entity.request.machine.MachineTerminalManagerRequest; +import cn.orionsec.ops.entity.vo.machine.MachineTerminalManagerVO; +import cn.orionsec.ops.entity.vo.machine.TerminalWatcherVO; +import cn.orionsec.ops.handler.terminal.IOperateHandler; +import cn.orionsec.ops.utils.Currents; +import cn.orionsec.ops.utils.EventParamsHolder; +import com.alibaba.fastjson.JSON; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +@Component +public class TerminalSessionManager { + + @Resource + private RedisTemplate redisTemplate; + + /** + * 会话 sessionId:handler + */ + private final Map sessionHolder = Maps.newCurrentHashMap(); + + /** + * session 列表 + * + * @param request request + * @return dataGrid + */ + public DataGrid getOnlineTerminal(MachineTerminalManagerRequest request) { + List sessionList = sessionHolder.values() + .stream() + .filter(s -> Optional.ofNullable(request.getToken()) + .filter(Strings::isNotBlank) + .map(t -> s.getToken().contains(t)) + .orElse(true)) + .filter(s -> Optional.ofNullable(request.getMachineName()) + .filter(Strings::isNotBlank) + .map(t -> s.getHint().getMachineName().toLowerCase().contains(t.toLowerCase())) + .orElse(true)) + .filter(s -> Optional.ofNullable(request.getMachineTag()) + .filter(Strings::isNotBlank) + .map(t -> s.getHint().getMachineTag().contains(t)) + .orElse(true)) + .filter(s -> Optional.ofNullable(request.getMachineHost()) + .filter(Strings::isNotBlank) + .map(t -> s.getHint().getMachineHost().contains(t)) + .orElse(true)) + .filter(s -> Optional.ofNullable(request.getUserId()) + .map(t -> s.getHint().getUserId().equals(t)) + .orElse(true)) + .filter(s -> Optional.ofNullable(request.getUsername()) + .map(t -> s.getHint().getUsername().toLowerCase().contains(t.toLowerCase())) + .orElse(true)) + .filter(s -> Optional.ofNullable(request.getMachineId()) + .map(t -> s.getHint().getMachineId().equals(t)) + .orElse(true)) + .filter(s -> { + if (request.getConnectedTimeStart() == null || request.getConnectedTimeEnd() == null) { + return true; + } + return DateRanges.inRange(request.getConnectedTimeStart(), request.getConnectedTimeEnd(), s.getHint().getConnectedTime()); + }) + .map(s -> { + MachineTerminalManagerVO vo = Converts.to(s.getHint(), MachineTerminalManagerVO.class); + vo.setToken(s.getToken()); + return vo; + }) + .sorted(Comparator.comparing(MachineTerminalManagerVO::getConnectedTime).reversed()) + .collect(Collectors.toList()); + List page = Lists.newLimitList(sessionList) + .limit(request.getLimit()) + .page(request.getPage()); + return DataGrid.of(page, sessionList.size()); + } + + /** + * 强制下线 + * + * @param token token + */ + public void forceOffline(String token) { + IOperateHandler handler = sessionHolder.get(token); + Valid.notNull(handler, MessageConst.SESSION_PRESENT); + try { + // 下线 + handler.forcedOffline(); + // 设置日志参数 + TerminalConnectConfig hint = handler.getHint(); + EventParamsHolder.addParam(EventKeys.TOKEN, token); + EventParamsHolder.addParam(EventKeys.USERNAME, hint.getUsername()); + EventParamsHolder.addParam(EventKeys.NAME, hint.getMachineName()); + } catch (Exception e) { + throw Exceptions.app(MessageConst.OPERATOR_ERROR, e); + } + } + + /** + * 获取终端监视 token + * + * @param token token + * @param readonly readonly + * @return watcher + */ + public TerminalWatcherVO getWatcherToken(String token, Integer readonly) { + IOperateHandler handler = sessionHolder.get(token); + Valid.notNull(handler, MessageConst.SESSION_PRESENT); + // 设置缓存 + String watcherToken = UUIds.random32(); + TerminalWatcherDTO cache = TerminalWatcherDTO.builder() + .userId(Currents.getUserId()) + .token(token) + .readonly(readonly) + .build(); + String key = Strings.format(KeyConst.TERMINAL_WATCHER_TOKEN, watcherToken); + redisTemplate.opsForValue().set(key, JSON.toJSONString(cache), + KeyConst.TERMINAL_WATCHER_TOKEN_EXPIRE, TimeUnit.SECONDS); + // 设置返回 + TerminalConnectConfig hint = handler.getHint(); + return TerminalWatcherVO.builder() + .token(watcherToken) + .readonly(readonly) + .cols(hint.getCols()) + .rows(hint.getRows()) + .build(); + } + + public Map getSessionHolder() { + return sessionHolder; + } + + public IOperateHandler getSession(String key) { + return sessionHolder.get(key); + } + + public IOperateHandler removeSession(String key) { + return sessionHolder.remove(key); + } + + public void addSession(String key, IOperateHandler handler) { + sessionHolder.put(key, handler); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/screen/TerminalScreenEnv.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/screen/TerminalScreenEnv.java new file mode 100644 index 0000000..5af23de --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/screen/TerminalScreenEnv.java @@ -0,0 +1,37 @@ + +package cn.orionsec.ops.handler.terminal.screen; + +import cn.orionsec.kit.net.host.ssh.TerminalType; +import cn.orionsec.ops.constant.Const; +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; + +@Data +public class TerminalScreenEnv { + + /** + * 终端类型 + */ + @JSONField(name = "TERM") + private String term; + + /** + * shell 类型 + */ + @JSONField(name = "SHELL") + private String shell; + + public TerminalScreenEnv() { + this(TerminalType.XTERM.getType(), Const.DEFAULT_SHELL); + } + + public TerminalScreenEnv(String term) { + this(term, Const.DEFAULT_SHELL); + } + + public TerminalScreenEnv(String term, String shell) { + this.term = term; + this.shell = shell; + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/screen/TerminalScreenHeader.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/screen/TerminalScreenHeader.java new file mode 100644 index 0000000..914b22a --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/screen/TerminalScreenHeader.java @@ -0,0 +1,48 @@ + +package cn.orionsec.ops.handler.terminal.screen; + +import com.alibaba.fastjson.annotation.JSONField; +import lombok.Data; + +@Data +public class TerminalScreenHeader { + + private static final Integer CAST_VERSION = 2; + + /** + * 版本 + */ + private Integer version; + + /** + * 标题 + */ + private String title; + + /** + * cols + */ + @JSONField(name = "width") + private Integer cols; + + /** + * rows + */ + @JSONField(name = "height") + private Integer rows; + + /** + * 开始时间戳 + */ + private Long timestamp; + + /** + * 环境变量 + */ + private TerminalScreenEnv env; + + public TerminalScreenHeader() { + this.version = CAST_VERSION; + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/watcher/ITerminalWatcherProcessor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/watcher/ITerminalWatcherProcessor.java new file mode 100644 index 0000000..8c164b1 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/watcher/ITerminalWatcherProcessor.java @@ -0,0 +1,31 @@ + +package cn.orionsec.ops.handler.terminal.watcher; + +import cn.orionsec.kit.lang.able.SafeCloseable; +import cn.orionsec.kit.lang.able.Watchable; +import org.springframework.web.socket.WebSocketSession; + +public interface ITerminalWatcherProcessor extends Watchable, SafeCloseable { + + /** + * 发送消息 + * + * @param message message + */ + void sendMessage(byte[] message); + + /** + * 添加 watcher + * + * @param session session + */ + void addWatcher(WebSocketSession session); + + /** + * 移除 watcher + * + * @param id id + */ + void removeWatcher(String id); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/watcher/TerminalWatcherHandler.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/watcher/TerminalWatcherHandler.java new file mode 100644 index 0000000..444cd06 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/watcher/TerminalWatcherHandler.java @@ -0,0 +1,133 @@ + +package cn.orionsec.ops.handler.terminal.watcher; + +import cn.orionsec.kit.lang.define.wrapper.Tuple; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.terminal.TerminalClientOperate; +import cn.orionsec.ops.constant.ws.WsCloseCode; +import cn.orionsec.ops.constant.ws.WsProtocol; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.handler.terminal.IOperateHandler; +import cn.orionsec.ops.handler.terminal.manager.TerminalSessionManager; +import cn.orionsec.ops.service.api.PassportService; +import cn.orionsec.ops.utils.WebSockets; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.*; + +import javax.annotation.Resource; + +@Slf4j +@Component("terminalWatcherHandler") +public class TerminalWatcherHandler implements WebSocketHandler { + + @Resource + private TerminalSessionManager terminalSessionManager; + + @Resource + private PassportService passportService; + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + log.info("terminal-watcher 已建立连接 token: {}, id: {}, params: {}", WebSockets.getToken(session), session.getId(), session.getAttributes()); + } + + @Override + public void handleMessage(WebSocketSession session, WebSocketMessage message) { + if (!(message instanceof TextMessage)) { + return; + } + String token = session.getId(); + try { + // 解析请求 + Tuple tuple = WebSockets.parsePayload(((TextMessage) message).getPayload()); + if (tuple == null) { + WebSockets.sendText(session, WsProtocol.ERROR.get()); + return; + } + TerminalClientOperate operate = tuple.get(0); + String body = tuple.get(1); + + // 建立连接 + if (operate == TerminalClientOperate.CONNECT) { + // 建立连接 + if (session.getAttributes().get(WebSockets.AUTHED) != null) { + return; + } + this.auth(session, body); + return; + } + if (operate != TerminalClientOperate.KEY && operate != TerminalClientOperate.CLEAR) { + return; + } + // 检查连接 + if (session.getAttributes().get(WebSockets.AUTHED) == null) { + WebSockets.close(session, WsCloseCode.VALID); + return; + } + // 检查是否只读 + final boolean readonly = Const.ENABLE.equals(session.getAttributes().get(WebSockets.READONLY)); + if (operate == TerminalClientOperate.KEY && readonly) { + return; + } + // 获取连接 + String terminalToken = session.getAttributes().get(WebSockets.TOKEN).toString(); + IOperateHandler handler = terminalSessionManager.getSession(terminalToken); + if (handler == null) { + WebSockets.close(session, WsCloseCode.UNKNOWN_CONNECT); + return; + } + // 操作 + handler.handleMessage(operate, body); + } catch (Exception e) { + log.error("terminal 处理操作异常 token: {}", token, e); + WebSockets.close(session, WsCloseCode.RUNTIME_EXCEPTION); + } + } + + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) { + log.error("terminal-watcher 操作异常拦截 token: {}", session.getId(), exception); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { + log.info("terminal-watcher 关闭连接 token: {}, code: {}, reason: {}", session.getId(), status.getCode(), status.getReason()); + // 这时候主连接可能已经关了 + IOperateHandler handler = terminalSessionManager.getSession((String) session.getAttributes().get(WebSockets.TOKEN)); + if (handler == null) { + return; + } + handler.getWatcher().removeWatcher(session.getId()); + } + + @Override + public boolean supportsPartialMessages() { + return false; + } + + /** + * 认证 + * + * @param session session + * @param loginToken loginToken + */ + private void auth(WebSocketSession session, String loginToken) { + // 检查参数 + Long userId = (Long) session.getAttributes().get(WebSockets.UID); + // 获取登录用户 + UserDTO userDTO = passportService.getUserByToken(loginToken, null); + if (userDTO == null || !userId.equals(userDTO.getId())) { + WebSockets.close(session, WsCloseCode.IDENTITY_MISMATCH); + return; + } + session.getAttributes().put(WebSockets.AUTHED, 1); + // 获取连接 + String terminalToken = session.getAttributes().get(WebSockets.TOKEN).toString(); + IOperateHandler handler = terminalSessionManager.getSession(terminalToken); + // 设置 watcher + handler.getWatcher().addWatcher(session); + WebSockets.sendText(session, WsProtocol.CONNECTED.get()); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/watcher/TerminalWatcherProcessor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/watcher/TerminalWatcherProcessor.java new file mode 100644 index 0000000..4f70098 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/terminal/watcher/TerminalWatcherProcessor.java @@ -0,0 +1,73 @@ + +package cn.orionsec.ops.handler.terminal.watcher; + +import cn.orionsec.kit.lang.utils.Threads; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.SchedulerPools; +import cn.orionsec.ops.constant.ws.WsCloseCode; +import cn.orionsec.ops.utils.WebSockets; +import lombok.SneakyThrows; +import org.springframework.web.socket.WebSocketSession; + +import java.util.Map; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +public class TerminalWatcherProcessor implements ITerminalWatcherProcessor, Runnable { + + private final Map sessions; + + private final LinkedBlockingQueue queue; + + private volatile boolean run; + + public TerminalWatcherProcessor() { + this.sessions = Maps.newCurrentHashMap(); + this.queue = new LinkedBlockingQueue<>(); + } + + @Override + public void watch() { + this.run = true; + Threads.start(this, SchedulerPools.TERMINAL_WATCHER_SCHEDULER); + } + + @SneakyThrows + @Override + public void run() { + while (run) { + byte[] message = queue.poll(Const.MS_S_10, TimeUnit.MILLISECONDS); + if (message == null || !run) { + continue; + } + for (WebSocketSession session : sessions.values()) { + WebSockets.sendText(session, message); + } + } + } + + @Override + public void sendMessage(byte[] message) { + queue.add(message); + } + + @Override + public void addWatcher(WebSocketSession session) { + sessions.put(session.getId(), session); + } + + @Override + public void removeWatcher(String id) { + sessions.remove(id); + } + + @Override + public void close() { + sessions.forEach((k, s) -> WebSockets.close(s, WsCloseCode.EOF)); + sessions.clear(); + queue.clear(); + this.run = false; + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/webhook/DingRobotPusher.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/webhook/DingRobotPusher.java new file mode 100644 index 0000000..d42a3a7 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/webhook/DingRobotPusher.java @@ -0,0 +1,69 @@ + +package cn.orionsec.ops.handler.webhook; + +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.collect.Lists; +import com.dingtalk.api.DefaultDingTalkClient; +import com.dingtalk.api.DingTalkClient; +import com.dingtalk.api.request.OapiRobotSendRequest; +import com.dingtalk.api.response.OapiRobotSendResponse; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +@Slf4j +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class DingRobotPusher implements IWebhookPusher { + + /** + * 推送 url + */ + private String url; + + /** + * 推送标题 + */ + private String title; + + /** + * 推送内容 + */ + private String text; + + /** + * @ 用户的手机号 + */ + private List atMobiles; + + @Override + public void push() { + OapiRobotSendRequest.Markdown content = new OapiRobotSendRequest.Markdown(); + content.setTitle(title); + content.setText(text); + // 执行推送请求 + DingTalkClient client = new DefaultDingTalkClient(url); + OapiRobotSendRequest request = new OapiRobotSendRequest(); + request.setMsgtype("markdown"); + request.setMarkdown(content); + if (!Lists.isEmpty(atMobiles)) { + OapiRobotSendRequest.At at = new OapiRobotSendRequest.At(); + at.setAtMobiles(atMobiles); + request.setAt(at); + } + try { + OapiRobotSendResponse response = client.execute(request); + if (!response.isSuccess()) { + log.error("钉钉机器人推送失败 url: {}", url); + } + } catch (Exception e) { + log.error("钉钉机器人推送异常 url: {}", url, e); + throw Exceptions.httpRequest(url, "ding push error", e); + } + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/webhook/IWebhookPusher.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/webhook/IWebhookPusher.java new file mode 100644 index 0000000..3a49fca --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/handler/webhook/IWebhookPusher.java @@ -0,0 +1,11 @@ + +package cn.orionsec.ops.handler.webhook; + +public interface IWebhookPusher { + + /** + * 执行推送 + */ + void push(); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/AuthenticateInterceptor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/AuthenticateInterceptor.java new file mode 100644 index 0000000..077414d --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/AuthenticateInterceptor.java @@ -0,0 +1,79 @@ + +package cn.orionsec.ops.interceptor; + +import cn.orionsec.kit.lang.constant.StandardContentType; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.web.servlet.web.Servlets; +import cn.orionsec.ops.annotation.IgnoreAuth; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.ResultCode; +import cn.orionsec.ops.constant.common.EnableType; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.service.api.PassportService; +import cn.orionsec.ops.utils.Currents; +import cn.orionsec.ops.utils.UserHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +public class AuthenticateInterceptor implements HandlerInterceptor { + + @Resource + private PassportService passportService; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { + if (!(handler instanceof HandlerMethod)) { + return true; + } + // 是否跳过 + final boolean ignore = ((HandlerMethod) handler).hasMethodAnnotation(IgnoreAuth.class); + HttpWrapper rejectWrapper = null; + String loginToken = Currents.getLoginToken(request); + if (!Strings.isEmpty(loginToken)) { + String ip = null; + // 如果开启用户 ip 绑定 则获取 ip + if (EnableType.of(SystemEnvAttr.LOGIN_IP_BIND.getValue()).getValue()) { + ip = Servlets.getRemoteAddr(request); + } + // 获取用户登录信息 + UserDTO user = passportService.getUserByToken(loginToken, ip); + if (user != null) { + if (Const.DISABLE.equals(user.getUserStatus())) { + rejectWrapper = HttpWrapper.of(ResultCode.USER_DISABLED); + } else { + UserHolder.set(user); + } + } else { + rejectWrapper = HttpWrapper.of(ResultCode.UNAUTHORIZED); + } + } else if (!ignore) { + rejectWrapper = HttpWrapper.of(ResultCode.UNAUTHORIZED); + } + // 匿名接口直接返回 + if (ignore) { + return true; + } + // 驳回接口设置返回 + if (rejectWrapper != null) { + response.setContentType(StandardContentType.APPLICATION_JSON); + Servlets.transfer(response, rejectWrapper.toJsonString().getBytes()); + return false; + } + return true; + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { + UserHolder.remove(); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/DemoDisableApiAspect.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/DemoDisableApiAspect.java new file mode 100644 index 0000000..c7d249e --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/DemoDisableApiAspect.java @@ -0,0 +1,28 @@ + +package cn.orionsec.ops.interceptor; + +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.ops.annotation.DemoDisableApi; +import cn.orionsec.ops.constant.ResultCode; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.core.annotation.Order; + +@Aspect +@Slf4j +@Order(20) +public class DemoDisableApiAspect { + + @Pointcut("@annotation(e)") + public void disableApi(DemoDisableApi e) { + } + + @Before(value = "disableApi(e)", argNames = "e") + public void beforeDisableApi(DemoDisableApi e) { + throw Exceptions.httpWrapper(HttpWrapper.of(ResultCode.DEMO_DISABLE_API)); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/ExposeApiHeaderInterceptor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/ExposeApiHeaderInterceptor.java new file mode 100644 index 0000000..d89daae --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/ExposeApiHeaderInterceptor.java @@ -0,0 +1,45 @@ + +package cn.orionsec.ops.interceptor; + +import cn.orionsec.kit.lang.constant.StandardContentType; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.kit.web.servlet.web.Servlets; +import cn.orionsec.ops.annotation.IgnoreCheck; +import cn.orionsec.ops.constant.ResultCode; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Component +public class ExposeApiHeaderInterceptor implements HandlerInterceptor { + + @Value("${expose.api.access.header}") + private String accessHeader; + + @Value("${expose.api.access.secret}") + private String accessSecret; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { + if (!(handler instanceof HandlerMethod)) { + return true; + } + // 是否跳过 + final boolean ignore = ((HandlerMethod) handler).hasMethodAnnotation(IgnoreCheck.class); + if (ignore) { + return true; + } + final boolean access = accessSecret.equals(request.getHeader(accessHeader)); + if (!access) { + response.setContentType(StandardContentType.APPLICATION_JSON); + Servlets.transfer(response, HttpWrapper.of(ResultCode.ILLEGAL_ACCESS).toJsonString().getBytes()); + } + return access; + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/FileTransferNotifyInterceptor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/FileTransferNotifyInterceptor.java new file mode 100644 index 0000000..1ff9990 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/FileTransferNotifyInterceptor.java @@ -0,0 +1,39 @@ + +package cn.orionsec.ops.interceptor; + +import cn.orionsec.kit.lang.utils.Booleans; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.ops.constant.KeyConst; +import cn.orionsec.ops.utils.WebSockets; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.HandshakeInterceptor; + +import javax.annotation.Resource; +import java.util.Map; + +@Slf4j +@Component +public class FileTransferNotifyInterceptor implements HandshakeInterceptor { + + @Resource + private RedisTemplate redisTemplate; + + @Override + public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception { + String token = WebSockets.getToken(request); + String tokenKey = Strings.format(KeyConst.SFTP_SESSION_TOKEN, token); + boolean access = Booleans.isTrue(redisTemplate.hasKey(tokenKey)); + log.info("sftp通知 尝试建立ws连接开始 token: {}, 结果: {}", token, access); + return access; + } + + @Override + public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/IpFilterInterceptor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/IpFilterInterceptor.java new file mode 100644 index 0000000..2aa7464 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/IpFilterInterceptor.java @@ -0,0 +1,107 @@ + +package cn.orionsec.ops.interceptor; + +import cn.orionsec.kit.lang.constant.StandardContentType; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.web.servlet.web.Servlets; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.ResultCode; +import cn.orionsec.ops.utils.Utils; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +@Component +public class IpFilterInterceptor implements HandlerInterceptor { + + /** + * 是否启用 + */ + private boolean enable; + + /** + * 是否为白名单 + */ + private boolean isWhiteList; + + /** + * 过滤器 + */ + private List filters; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { + // 停用 + if (!enable) { + return true; + } + if (!(handler instanceof HandlerMethod)) { + return true; + } + String ip = Servlets.getRemoteAddr(request); + // 本机不过滤 + if (Const.LOCALHOST_IP_V4.equals(ip)) { + return true; + } + // 过滤 + boolean contains = false; + for (String filter : filters) { + if (Strings.isBlank(filter)) { + continue; + } + // 检测 + contains = Utils.checkIpIn(ip, filter); + if (contains) { + break; + } + } + // 结果 + boolean pass; + if (isWhiteList) { + pass = contains; + } else { + pass = !contains; + } + // 返回 + if (!pass) { + response.setContentType(StandardContentType.APPLICATION_JSON); + Servlets.transfer(response, HttpWrapper.of(ResultCode.IP_BAN).toJsonString().getBytes()); + } + return pass; + } + + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { + } + + /** + * 配置启用类型 + * + * @param enable 是否启用 + * @param isWhiteList 是否为白名单 + * @param filter 规则 + */ + public void set(boolean enable, boolean isWhiteList, String filter) { + this.enable = enable; + this.isWhiteList = isWhiteList; + if (Strings.isBlank(filter)) { + this.enable = false; + } else { + this.filters = Arrays.stream(filter.split(Const.LF)) + .filter(Strings::isNotBlank) + .collect(Collectors.toList()); + if (filters.isEmpty()) { + this.enable = false; + } + } + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/LogPrintInterceptor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/LogPrintInterceptor.java new file mode 100644 index 0000000..fc93c46 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/LogPrintInterceptor.java @@ -0,0 +1,149 @@ + +package cn.orionsec.ops.interceptor; + +import cn.orionsec.kit.lang.id.UUIds; +import cn.orionsec.kit.lang.utils.Arrays1; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.kit.web.servlet.web.Servlets; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.utils.UserHolder; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.PropertyFilter; +import lombok.extern.slf4j.Slf4j; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.lang.reflect.Method; +import java.util.Date; +import java.util.Optional; + +@Slf4j +@Order(10) +@Component +public class LogPrintInterceptor implements MethodInterceptor { + + @Value("#{'${log.interceptor.ignore.fields:}'.split(',')}") + private String[] ignoreFields; + + /** + * 请求序列 + */ + public static final ThreadLocal SEQ_HOLDER = ThreadLocal.withInitial(UUIds::random32); + + /** + * 开始时间 + */ + private static final ThreadLocal START_HOLDER = ThreadLocal.withInitial(Date::new); + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable { + // 打印开始日志 + this.beforeLogPrint(invocation); + try { + // 执行方法 + Object ret = invocation.proceed(); + // 返回打印 + this.afterReturnLogPrint(ret); + return ret; + } catch (Throwable t) { + // 异常打印 + this.afterThrowingLogPrint(t); + throw t; + } finally { + // 删除threadLocal + SEQ_HOLDER.remove(); + START_HOLDER.remove(); + } + } + + /** + * 方法进入打印 + * + * @param invocation invocation + */ + public void beforeLogPrint(MethodInvocation invocation) { + StringBuilder requestLog = new StringBuilder("\napi请求-开始-seq: ").append(SEQ_HOLDER.get()).append('\n'); + // 登录用户 + requestLog.append("\t当前用户: ").append(JSON.toJSONString(UserHolder.get())).append('\n'); + // http请求信息 + Optional.ofNullable(RequestContextHolder.getRequestAttributes()) + .map(s -> (ServletRequestAttributes) s) + .map(ServletRequestAttributes::getRequest) + .ifPresent(request -> { + // url + requestLog.append("\t").append(Servlets.getMethod(request)).append(" ") + .append(Servlets.getRequestUrl(request)).append('\n'); + // query + requestLog.append("\tip: ").append(Servlets.getRemoteAddr(request)).append('\n') + .append("\tquery: ").append(Servlets.getQueryString(request)).append('\n'); + // header + Servlets.getHeaderMap(request).forEach((hk, hv) -> requestLog.append('\t') + .append(hk).append(": ") + .append(hv).append('\n')); + }); + // 方法信息 + Method method = invocation.getMethod(); + requestLog.append("\t开始时间: ").append(Dates.format(START_HOLDER.get(), Dates.YMD_HMSS)).append('\n') + .append("\t方法签名: ").append(method.getDeclaringClass().getName()).append('#') + .append(method.getName()).append("\n") + .append("\t请求参数: ").append(this.argsToString(invocation.getArguments())); + log.info(requestLog.toString()); + } + + /** + * 返回打印 + * + * @param ret return + */ + private void afterReturnLogPrint(Object ret) { + Date endTime = new Date(); + // 响应日志 + StringBuilder responseLog = new StringBuilder("\napi请求-结束-seq: ").append(SEQ_HOLDER.get()).append('\n'); + responseLog.append("\t结束时间: ").append(Dates.format(endTime, Dates.YMD_HMSS)) + .append(" used: ").append(endTime.getTime() - START_HOLDER.get().getTime()).append("ms \n") + .append("\t响应结果: ").append(this.argsToString(ret)); + log.info(responseLog.toString()); + } + + /** + * 异常打印 + * + * @param throwable ex + */ + private void afterThrowingLogPrint(Throwable throwable) { + Date endTime = new Date(); + // 响应日志 + StringBuilder responseLog = new StringBuilder("\napi请求-异常-seq: ").append(SEQ_HOLDER.get()).append('\n'); + responseLog.append("\t结束时间: ").append(Dates.format(endTime, Dates.YMD_HMSS)) + .append(" used: ").append(endTime.getTime() - START_HOLDER.get().getTime()).append("ms \n") + .append("\t异常摘要: ").append(Exceptions.getDigest(throwable)); + log.error(responseLog.toString()); + } + + /** + * 参数转json + * + * @param o object + * @return json + */ + private String argsToString(Object o) { + try { + if (ignoreFields.length == 1 && Const.EMPTY.equals(ignoreFields[0])) { + // 不过滤 + return JSON.toJSONString(o); + } else { + return JSON.toJSONString(o, (PropertyFilter) (object, name, value) -> !Arrays1.contains(ignoreFields, name)); + + } + } catch (Exception e) { + return String.valueOf(o); + } + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/RoleInterceptor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/RoleInterceptor.java new file mode 100644 index 0000000..89433ee --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/RoleInterceptor.java @@ -0,0 +1,52 @@ + +package cn.orionsec.ops.interceptor; + +import cn.orionsec.kit.lang.constant.StandardContentType; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.kit.lang.utils.Arrays1; +import cn.orionsec.kit.web.servlet.web.Servlets; +import cn.orionsec.ops.annotation.RequireRole; +import cn.orionsec.ops.constant.ResultCode; +import cn.orionsec.ops.constant.user.RoleType; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.utils.UserHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@Component +public class RoleInterceptor implements HandlerInterceptor { + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + if (!(handler instanceof HandlerMethod)) { + return true; + } + RequireRole role = ((HandlerMethod) handler).getMethodAnnotation(RequireRole.class); + if (role == null) { + return true; + } + UserDTO user = UserHolder.get(); + if (user == null) { + response.setContentType(StandardContentType.APPLICATION_JSON); + Servlets.transfer(response, HttpWrapper.of(ResultCode.UNAUTHORIZED).toJsonString().getBytes()); + return false; + } + RoleType[] hasRoles = role.value(); + if (Arrays1.isEmpty(hasRoles)) { + return true; + } + for (RoleType roleType : hasRoles) { + if (roleType.getType().equals(user.getRoleType())) { + return true; + } + } + response.setContentType(StandardContentType.APPLICATION_JSON); + Servlets.transfer(response, HttpWrapper.of(ResultCode.NO_PERMISSION).toJsonString().getBytes()); + return false; + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/TailFileInterceptor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/TailFileInterceptor.java new file mode 100644 index 0000000..c9871a3 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/TailFileInterceptor.java @@ -0,0 +1,49 @@ + +package cn.orionsec.ops.interceptor; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.ops.constant.KeyConst; +import cn.orionsec.ops.entity.dto.file.FileTailDTO; +import cn.orionsec.ops.utils.WebSockets; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.HandshakeInterceptor; + +import javax.annotation.Resource; +import java.util.Map; + +@Component +@Slf4j +public class TailFileInterceptor implements HandshakeInterceptor { + + @Resource + private RedisTemplate redisTemplate; + + @Override + public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) { + String token = WebSockets.getToken(request); + String tokenKey = Strings.format(KeyConst.FILE_TAIL_ACCESS_TOKEN, token); + String tokenValue = redisTemplate.opsForValue().get(tokenKey); + boolean access = false; + if (!Strings.isBlank(tokenValue)) { + // 设置信息 + access = true; + attributes.put(WebSockets.CONFIG, JSON.parseObject(tokenValue, FileTailDTO.class)); + attributes.put(WebSockets.TOKEN, token); + // 删除 token + redisTemplate.delete(tokenKey); + } + log.info("tail 尝试建立ws连接开始 token: {}, 结果: {}", token, access); + return access; + } + + @Override + public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/TerminalAccessInterceptor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/TerminalAccessInterceptor.java new file mode 100644 index 0000000..1046463 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/TerminalAccessInterceptor.java @@ -0,0 +1,49 @@ + +package cn.orionsec.ops.interceptor; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.ops.constant.KeyConst; +import cn.orionsec.ops.utils.WebSockets; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.HandshakeInterceptor; + +import javax.annotation.Resource; +import java.util.Map; + +@Component +@Slf4j +public class TerminalAccessInterceptor implements HandshakeInterceptor { + + @Resource + private RedisTemplate redisTemplate; + + @Override + public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) { + // 获取 token + String token = WebSockets.getToken(request); + String tokenKey = Strings.format(KeyConst.TERMINAL_ACCESS_TOKEN, token); + String tokenValue = redisTemplate.opsForValue().get(tokenKey); + boolean access = false; + if (!Strings.isBlank(tokenValue)) { + // 设置用户机器信息 + access = true; + String[] pair = tokenValue.split("_"); + attributes.put(WebSockets.UID, Long.valueOf(pair[0])); + attributes.put(WebSockets.MID, Long.valueOf(pair[1])); + // 删除 token + redisTemplate.delete(tokenKey); + } + log.info("terminal尝试打开ws连接开始 token: {}, 结果: {}", token, access); + return access; + } + + @Override + public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/TerminalWatcherInterceptor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/TerminalWatcherInterceptor.java new file mode 100644 index 0000000..06042ac --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/TerminalWatcherInterceptor.java @@ -0,0 +1,51 @@ + +package cn.orionsec.ops.interceptor; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.ops.constant.KeyConst; +import cn.orionsec.ops.entity.dto.terminal.TerminalWatcherDTO; +import cn.orionsec.ops.utils.WebSockets; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.HandshakeInterceptor; + +import javax.annotation.Resource; +import java.util.Map; + +@Component +@Slf4j +public class TerminalWatcherInterceptor implements HandshakeInterceptor { + + @Resource + private RedisTemplate redisTemplate; + + @Override + public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) { + // 获取 token + String token = WebSockets.getToken(request); + String tokenKey = Strings.format(KeyConst.TERMINAL_WATCHER_TOKEN, token); + String tokenValue = redisTemplate.opsForValue().get(tokenKey); + boolean access = false; + if (!Strings.isBlank(tokenValue)) { + access = true; + TerminalWatcherDTO watcher = JSON.parseObject(tokenValue, TerminalWatcherDTO.class); + attributes.put(WebSockets.UID, watcher.getUserId()); + attributes.put(WebSockets.TOKEN, watcher.getToken()); + attributes.put(WebSockets.READONLY, watcher.getReadonly()); + // 删除 token + redisTemplate.delete(tokenKey); + } + log.info("terminal尝试监视ws连接开始 token: {}, 结果: {}", token, access); + return access; + } + + @Override + public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/UserActiveInterceptor.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/UserActiveInterceptor.java new file mode 100644 index 0000000..a1d60a7 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/UserActiveInterceptor.java @@ -0,0 +1,118 @@ + +package cn.orionsec.ops.interceptor; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.KeyConst; +import cn.orionsec.ops.constant.common.EnableType; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.dao.UserInfoDAO; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.service.api.UserEventLogService; +import cn.orionsec.ops.utils.Currents; +import cn.orionsec.ops.utils.EventParamsHolder; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@Component +public class UserActiveInterceptor implements HandlerInterceptor { + + /** + * key: 用户id + * value: 活跃时间戳 + */ + private final Map activeUsers = Maps.newCurrentHashMap(); + + @Resource + private UserInfoDAO userInfoDAO; + + @Resource + private UserEventLogService userEventLogService; + + @Resource + private RedisTemplate redisTemplate; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + // 处理 + this.handleActive(); + return true; + } + + /** + * 处理用户活跃信息 + */ + private void handleActive() { + UserDTO user = Currents.getUser(); + if (user == null) { + return; + } + long now = System.currentTimeMillis(); + Long before = activeUsers.put(user.getId(), now); + if (before == null) { + // 应用启动/多端登录(单端登出) token有效 + this.refreshActive(user); + return; + } + // 获取活跃域值 + long activeThresholdHour = Long.parseLong(SystemEnvAttr.LOGIN_TOKEN_AUTO_RENEW_THRESHOLD.getValue()); + long activeThreshold = TimeUnit.HOURS.toMillis(activeThresholdHour); + if (before + activeThreshold < now) { + // 超过活跃域值 + this.refreshActive(user); + } + } + + /** + * 刷新活跃 + * + * @param user user + */ + private void refreshActive(UserDTO user) { + Long userId = user.getId(); + // 刷新登录时间 + userInfoDAO.updateLastLoginTime(userId); + // 记录日志 + EventParamsHolder.addParam(EventKeys.REFRESH_LOGIN, Const.ENABLE); + EventParamsHolder.setDefaultEventParams(); + userEventLogService.recordLog(EventType.LOGIN, true); + // 如果开启自动续签 刷新登录token 绑定token + if (EnableType.of(SystemEnvAttr.LOGIN_TOKEN_AUTO_RENEW.getValue()).getValue()) { + long expire = Long.parseLong(SystemEnvAttr.LOGIN_TOKEN_EXPIRE.getValue()); + String loginKey = Strings.format(KeyConst.LOGIN_TOKEN_KEY, userId); + String bindKey = Strings.format(KeyConst.LOGIN_TOKEN_BIND_KEY, userId, user.getCurrentBindTimestamp()); + redisTemplate.expire(loginKey, expire, TimeUnit.HOURS); + redisTemplate.expire(bindKey, expire, TimeUnit.HOURS); + } + } + + /** + * 设置活跃时间 + * + * @param id id + * @param time 登录时间 + */ + public void setActiveTime(Long id, Long time) { + activeUsers.put(id, time); + } + + /** + * 删除活跃时间 + * + * @param id id + */ + public void deleteActiveTime(Long id) { + activeUsers.remove(id); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/UserEventLogAspect.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/UserEventLogAspect.java new file mode 100644 index 0000000..aeb04be --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/interceptor/UserEventLogAspect.java @@ -0,0 +1,44 @@ + +package cn.orionsec.ops.interceptor; + +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.service.api.UserEventLogService; +import cn.orionsec.ops.utils.EventParamsHolder; +import lombok.extern.slf4j.Slf4j; +import org.aspectj.lang.annotation.*; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +@Component +@Aspect +@Slf4j +@Order(30) +public class UserEventLogAspect { + + @Resource + private UserEventLogService userEventLogService; + + @Pointcut("@annotation(e)") + public void eventLogPoint(EventLog e) { + } + + @Before(value = "eventLogPoint(e)", argNames = "e") + public void beforeLogRecord(EventLog e) { + EventParamsHolder.remove(); + // 设置默认参数 + EventParamsHolder.setDefaultEventParams(); + } + + @AfterReturning(pointcut = "eventLogPoint(e)", argNames = "e") + public void afterLogRecord(EventLog e) { + userEventLogService.recordLog(e.value(), true); + } + + @AfterThrowing(pointcut = "eventLogPoint(e)", argNames = "e") + public void afterLogRecordThrowing(EventLog e) { + userEventLogService.recordLog(e.value(), false); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/AlarmGroupNotifyService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/AlarmGroupNotifyService.java new file mode 100644 index 0000000..6501ebd --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/AlarmGroupNotifyService.java @@ -0,0 +1,42 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.entity.domain.AlarmGroupNotifyDO; + +import java.util.List; + +public interface AlarmGroupNotifyService { + + /** + * 通过 groupId 查询 + * + * @param groupId groupId + * @return rows + */ + List selectByGroupId(Long groupId); + + /** + * 通过 groupId 查询 + * + * @param groupIdList groupIdList + * @return rows + */ + List selectByGroupIdList(List groupIdList); + + /** + * 通过 groupId 删除 + * + * @param groupId groupId + * @return effect + */ + Integer deleteByGroupId(Long groupId); + + /** + * 通过 webhookId 删除 + * + * @param webhookId webhookId + * @return effect + */ + Integer deleteByWebhookId(Long webhookId); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/AlarmGroupService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/AlarmGroupService.java new file mode 100644 index 0000000..5895499 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/AlarmGroupService.java @@ -0,0 +1,50 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.entity.request.alarm.AlarmGroupRequest; +import cn.orionsec.ops.entity.vo.alarm.AlarmGroupVO; + +public interface AlarmGroupService { + + /** + * 添加报警组 + * + * @param request request + * @return id + */ + Long addAlarmGroup(AlarmGroupRequest request); + + /** + * 更新报警组 + * + * @param request request + * @return effect + */ + Integer updateAlarmGroup(AlarmGroupRequest request); + + /** + * 删除报警组 + * + * @param id id + * @return effect + */ + Integer deleteAlarmGroup(Long id); + + /** + * 查询列表 + * + * @param request request + * @return rows + */ + DataGrid getAlarmGroupList(AlarmGroupRequest request); + + /** + * 查询详情 + * + * @param id id + * @return row + */ + AlarmGroupVO getAlarmGroupDetail(Long id); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/AlarmGroupUserService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/AlarmGroupUserService.java new file mode 100644 index 0000000..490a670 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/AlarmGroupUserService.java @@ -0,0 +1,42 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.entity.domain.AlarmGroupUserDO; + +import java.util.List; + +public interface AlarmGroupUserService { + + /** + * 通过 groupId 查询 + * + * @param groupId groupId + * @return rows + */ + List selectByGroupId(Long groupId); + + /** + * 通过 groupId 查询 + * + * @param groupIdList groupIdList + * @return rows + */ + List selectByGroupIdList(List groupIdList); + + /** + * 通过 groupId 删除 + * + * @param groupId groupId + * @return effect + */ + Integer deleteByGroupId(Long groupId); + + /** + * 通过 userId 删除 + * + * @param userId userId + * @return effect + */ + Integer deleteByUserId(Long userId); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationActionLogService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationActionLogService.java new file mode 100644 index 0000000..28c8287 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationActionLogService.java @@ -0,0 +1,96 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.constant.app.StageType; +import cn.orionsec.ops.entity.domain.ApplicationActionLogDO; +import cn.orionsec.ops.entity.vo.app.ApplicationActionLogVO; + +import java.util.List; + +public interface ApplicationActionLogService { + + /** + * 通过 id 查询详情 + * + * @param id id + * @return detail + */ + ApplicationActionLogVO getDetailById(Long id); + + /** + * 通过 id 查询状态 + * + * @param id id + * @return detail + */ + ApplicationActionLogVO getStatusById(Long id); + + /** + * 获取操作执行日志 + * + * @param relId relId + * @param stageType stageType + * @return rows + */ + List getActionLogsByRelId(Long relId, StageType stageType); + + /** + * 删除 + * + * @param relId relId + * @param stageType stageType + * @return effect + */ + Integer deleteByRelId(Long relId, StageType stageType); + + /** + * 删除 + * + * @param relIdList relIdList + * @param stageType stageType + * @return effect + */ + Integer deleteByRelIdList(List relIdList, StageType stageType); + + /** + * 通过 relId 查询 action + * + * @param relId relId + * @param stageType stageType + * @return action + */ + List selectActionByRelId(Long relId, StageType stageType); + + /** + * 通过 relIdList 查询 action + * + * @param relIdList relIdList + * @param stageType stageType + * @return action + */ + List selectActionByRelIdList(List relIdList, StageType stageType); + + /** + * 更新 action + * + * @param record record + */ + void updateActionById(ApplicationActionLogDO record); + + /** + * 获取操作执行日志路径 + * + * @param id id + * @return path + */ + String getActionLogPath(Long id); + + /** + * 设置操作状态 + * + * @param relId relId + * @param stageType stageType + */ + void resetActionStatus(Long relId, StageType stageType); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationActionService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationActionService.java new file mode 100644 index 0000000..c2c52ae --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationActionService.java @@ -0,0 +1,75 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.entity.domain.ApplicationActionDO; +import cn.orionsec.ops.entity.request.app.ApplicationConfigRequest; + +import java.util.List; +import java.util.Map; + + +public interface ApplicationActionService { + + /** + * 删除发布流程 + * + * @param appId appId + * @param profileId profileId + * @return effect + */ + Integer deleteAppActionByAppProfileId(Long appId, Long profileId); + + /** + * 获取发布流程 + * + * @param appId appId + * @param profileId profileId + * @param stageType stageType + * @return actions + */ + List getAppProfileActions(Long appId, Long profileId, Integer stageType); + + /** + * 通过appId profileId查询操作流程数量数量 + * + * @param appId appId + * @param profileId profileId + * @param stageType stageType + * @return count + */ + Integer getAppProfileActionCount(Long appId, Long profileId, Integer stageType); + + /** + * 配置app发布流程 + * + * @param request request + */ + void configAppAction(ApplicationConfigRequest request); + + /** + * 同步app操作流程 + * + * @param appId appId + * @param profileId profileId + * @param syncProfileId 需要同步的profileId + */ + void syncAppProfileAction(Long appId, Long profileId, Long syncProfileId); + + /** + * 复制发布流程 + * + * @param appId appId + * @param targetAppId targetAppId + */ + void copyAppAction(Long appId, Long targetAppId); + + /** + * 获取app是否已配置 + * + * @param profileId profileId + * @param appIdList appIdList + * @return appId, isConfig + */ + Map getAppIsConfig(Long profileId, List appIdList); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationBuildService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationBuildService.java new file mode 100644 index 0000000..c438afe --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationBuildService.java @@ -0,0 +1,128 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.entity.domain.ApplicationBuildDO; +import cn.orionsec.ops.entity.request.app.ApplicationBuildRequest; +import cn.orionsec.ops.entity.vo.app.ApplicationBuildReleaseListVO; +import cn.orionsec.ops.entity.vo.app.ApplicationBuildStatusVO; +import cn.orionsec.ops.entity.vo.app.ApplicationBuildVO; + +import java.util.List; + +public interface ApplicationBuildService { + + /** + * 提交执行 + * + * @param request request + * @param execute 是否提交任务 + * @return id + */ + Long submitBuildTask(ApplicationBuildRequest request, boolean execute); + + /** + * 获取构建列表 + * + * @param request request + * @return rows + */ + DataGrid getBuildList(ApplicationBuildRequest request); + + /** + * 获取构建详情 + * + * @param id id + * @return row + */ + ApplicationBuildVO getBuildDetail(Long id); + + /** + * 查询构建状态 + * + * @param id id + * @return status + */ + ApplicationBuildStatusVO getBuildStatus(Long id); + + /** + * 查询构建状态列表 + * + * @param buildIdList id + * @return key: id value: status + */ + List getBuildStatusList(List buildIdList); + + /** + * 停止构建 + * + * @param id id + */ + void terminateBuildTask(Long id); + + /** + * 输入命令 + * + * @param id id + * @param command command + */ + void writeBuildTask(Long id, String command); + + /** + * 删除构建 + * + * @param idList idList + * @return effect + */ + Integer deleteBuildTask(List idList); + + /** + * 重新构建 + * + * @param id id + * @return id + */ + Long rebuild(Long id); + + /** + * 通过id查询 + * + * @param id id + * @return row + */ + ApplicationBuildDO selectById(Long id); + + /** + * 获取构建日志路径 + * + * @param id id + * @return path + */ + String getBuildLogPath(Long id); + + /** + * 获取构建产物路径 + * + * @param id id + * @return path + */ + String getBuildBundlePath(Long id); + + /** + * 检查并且获取构建目录 + * + * @param build build + * @return 构建产物路径 + */ + String checkBuildBundlePath(ApplicationBuildDO build); + + /** + * 获取构建发布序列 + * + * @param appId appId + * @param profileId profileId + * @return rows + */ + List getBuildReleaseList(Long appId, Long profileId); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationEnvService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationEnvService.java new file mode 100644 index 0000000..480f0b6 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationEnvService.java @@ -0,0 +1,153 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.collect.MutableLinkedHashMap; +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.entity.domain.ApplicationEnvDO; +import cn.orionsec.ops.entity.request.app.ApplicationConfigRequest; +import cn.orionsec.ops.entity.request.app.ApplicationEnvRequest; +import cn.orionsec.ops.entity.vo.app.ApplicationEnvVO; + +import java.util.List; +import java.util.Map; + +public interface ApplicationEnvService { + + /** + * 添加应用变量 + * + * @param request request + * @return id + */ + Long addAppEnv(ApplicationEnvRequest request); + + /** + * 更新应用变量 + * + * @param request request + * @return effect + */ + Integer updateAppEnv(ApplicationEnvRequest request); + + /** + * 更新应用变量 + * + * @param before 数据 + * @param update update + * @return effect + */ + Integer updateAppEnv(ApplicationEnvDO before, ApplicationEnvRequest update); + + /** + * 删除应用变量 + * + * @param idList idList + * @return effect + */ + Integer deleteAppEnv(List idList); + + /** + * 保存应用变量 + * + * @param appId appId + * @param profileId profileId + * @param env env + */ + void saveEnv(Long appId, Long profileId, Map env); + + /** + * 应用环境变量列表 + * + * @param request request + * @return rows + */ + DataGrid listAppEnv(ApplicationEnvRequest request); + + /** + * 应用环境变量详情 + * + * @param id id + * @return row + */ + ApplicationEnvVO getAppEnvDetail(Long id); + + /** + * 同步应用环境变量到其他环境 + * + * @param id id + * @param appId appId + * @param profileId profileId + * @param targetProfileIdList 同步环境id + */ + void syncAppEnv(Long id, Long appId, Long profileId, List targetProfileIdList); + + /** + * 获取环境变量值 + * + * @param appId appId + * @param profileId profileId + * @param key key + * @return value + */ + String getAppEnvValue(Long appId, Long profileId, String key); + + /** + * 获取应用环境变量 + * + * @param appId appId + * @param profileId profileId + * @return map + */ + MutableLinkedHashMap getAppProfileEnv(Long appId, Long profileId); + + /** + * 获取应用环境变量 包含应用 环境 + * + * @param appId appId + * @param profileId profileId + * @return env + */ + MutableLinkedHashMap getAppProfileFullEnv(Long appId, Long profileId); + + /** + * 通过appId profileId 删除env + * + * @param appId appId + * @param profileId profileId + * @return effect + */ + Integer deleteAppProfileEnvByAppProfileId(Long appId, Long profileId); + + /** + * 配置app环境 + * + * @param request request + */ + void configAppEnv(ApplicationConfigRequest request); + + /** + * 复制环境变量 + * + * @param appId appId + * @param targetAppId targetAppId + */ + void copyAppEnv(Long appId, Long targetAppId); + + /** + * 获取构建seq + * + * @param appId appId + * @param profileId profileId + * @return seq + */ + Integer getBuildSeqAndIncrement(Long appId, Long profileId); + + /** + * 检查并初始化系统变量 + * + * @param appId appId + * @param profileId profileId + */ + void checkInitSystemEnv(Long appId, Long profileId); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationInfoService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationInfoService.java new file mode 100644 index 0000000..879ddf4 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationInfoService.java @@ -0,0 +1,113 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.entity.request.app.ApplicationConfigRequest; +import cn.orionsec.ops.entity.request.app.ApplicationInfoRequest; +import cn.orionsec.ops.entity.vo.app.ApplicationDetailVO; +import cn.orionsec.ops.entity.vo.app.ApplicationInfoVO; +import cn.orionsec.ops.entity.vo.app.ApplicationMachineVO; + +import java.util.List; + +public interface ApplicationInfoService { + + /** + * 添加应用 + * + * @param request request + * @return id + */ + Long insertApp(ApplicationInfoRequest request); + + /** + * 更新应用 + * + * @param request request + * @return effect + */ + Integer updateApp(ApplicationInfoRequest request); + + /** + * 更新排序 + * + * @param id id + * @param increment 是否为上调 + * @return effect + */ + Integer updateAppSort(Long id, boolean increment); + + /** + * 删除应用 + * + * @param id id + * @return effect + */ + Integer deleteApp(Long id); + + /** + * 应用列表 + * + * @param request request + * @return rows + */ + DataGrid listApp(ApplicationInfoRequest request); + + /** + * 获取应用机器 + * + * @param appId appId + * @param profileId profileId + * @return machines + */ + List getAppMachines(Long appId, Long profileId); + + /** + * 获取应用详情 + * + * @param appId appId + * @param profileId profileId + * @return detail + */ + ApplicationDetailVO getAppDetail(Long appId, Long profileId); + + /** + * 配置应用环境配置 + * + * @param request request + */ + void configAppProfile(ApplicationConfigRequest request); + + /** + * 同步应用环境配置 + * + * @param appId appId + * @param profileId profileId + * @param targetProfileList targetProfileList + */ + void syncAppProfileConfig(Long appId, Long profileId, List targetProfileList); + + /** + * 复制应用 + * + * @param appId appId + */ + void copyApplication(Long appId); + + /** + * 检测应用是否已经配置 + * + * @param appId appId + * @param profileId profileId + * @return 检测应用是否已经配置 + */ + boolean checkAppConfig(Long appId, Long profileId); + + /** + * 获取下一个排序 + * + * @return sort + */ + Integer getNextSort(); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationMachineService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationMachineService.java new file mode 100644 index 0000000..7165128 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationMachineService.java @@ -0,0 +1,137 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.entity.domain.ApplicationMachineDO; +import cn.orionsec.ops.entity.request.app.ApplicationConfigRequest; +import cn.orionsec.ops.entity.vo.app.ApplicationMachineVO; + +import java.util.List; + +public interface ApplicationMachineService { + + /** + * 获取应用环境的机器 + * + * @param machineId machineId + * @param appId appId + * @param profileId profileId + * @return machineId + */ + Long getAppProfileMachineId(Long machineId, Long appId, Long profileId); + + /** + * 获取应用环境的机器id + * + * @param appId appId + * @param profileId profileId + * @param filterMachineStatus 是否过滤已禁用的机器状态 + * @return machineIdList + */ + List getAppProfileMachineIdList(Long appId, Long profileId, boolean filterMachineStatus); + + /** + * 获取应用环境的机器数量 + * + * @param appId appId + * @param profileId profileId + * @return count + */ + Integer getAppProfileMachineCount(Long appId, Long profileId); + + /** + * 获取应用环境的机器 + * + * @param appId appId + * @param profileId profileId + * @return machineList + */ + List getAppProfileMachineDetail(Long appId, Long profileId); + + /** + * 获取应用环境的机器 + * + * @param appId appId + * @param profileId profileId + * @return machineList + */ + List getAppProfileMachineList(Long appId, Long profileId); + + /** + * 通过机器 id 删除应用机器 + * + * @param machineIdList machineIdList + * @return effect + */ + Integer deleteAppMachineByMachineIdList(List machineIdList); + + /** + * 通过 appId profileId 删除应用机器 + * + * @param appId appId + * @param profileId profileId + * @return effect + */ + Integer deleteAppMachineByAppProfileId(Long appId, Long profileId); + + /** + * 通过 appId profileId machineId 删除应用机器 + * + * @param appId appId + * @param profileId profileId + * @param machineId machineId + * @return effect + */ + Integer deleteAppMachineByAppProfileMachineId(Long appId, Long profileId, Long machineId); + + /** + * 通过 id 删除 + * + * @param id id + * @return effect + */ + Integer deleteById(Long id); + + /** + * 通过 appId profileId 查询应用机器id列表 + * + * @param appId appId + * @param profileId profileId + * @return 机器id列表 + */ + List selectAppProfileMachineIdList(Long appId, Long profileId); + + /** + * 配置app机器 + * + * @param request request + */ + void configAppMachines(ApplicationConfigRequest request); + + /** + * 同步 app 机器 + * + * @param appId appId + * @param profileId profileId + * @param syncProfileId 需要同步的profileId + */ + void syncAppProfileMachine(Long appId, Long profileId, Long syncProfileId); + + /** + * 复制 app 机器 + * + * @param appId appId + * @param targetAppId targetAppId + */ + void copyAppMachine(Long appId, Long targetAppId); + + /** + * 更新 app releaseId + * + * @param appId appId + * @param profileId profileId + * @param releaseId releaseId + * @param machineIdList machineIdList + */ + void updateAppMachineReleaseId(Long appId, Long profileId, Long releaseId, List machineIdList); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationPipelineDetailService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationPipelineDetailService.java new file mode 100644 index 0000000..0346876 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationPipelineDetailService.java @@ -0,0 +1,67 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.entity.domain.ApplicationPipelineDetailDO; +import cn.orionsec.ops.entity.vo.app.ApplicationPipelineDetailVO; + +import java.util.List; + +public interface ApplicationPipelineDetailService { + + /** + * 通过 pipelineId 查询 + * + * @param pipelineId pipelineId + * @return rows + */ + List getByPipelineId(Long pipelineId); + + /** + * 通过 pipelineId 查询 + * + * @param pipelineId pipelineId + * @return rows + */ + List selectByPipelineId(Long pipelineId); + + /** + * 通过 pipelineId 查询 + * + * @param pipelineIdList pipelineIdList + * @return rows + */ + List selectByPipelineIdList(List pipelineIdList); + + /** + * 通过 pipelineId 删除 + * + * @param pipelineId pipelineId + * @return effect + */ + Integer deleteByPipelineId(Long pipelineId); + + /** + * 通过 pipelineId 删除 + * + * @param pipelineIdList pipelineIdList + * @return effect + */ + Integer deleteByPipelineIdList(List pipelineIdList); + + /** + * 通过 profileId 删除 + * + * @param profileId profileId + * @return effect + */ + Integer deleteByProfileId(Long profileId); + + /** + * 通过 appId 删除 + * + * @param appId appId + * @return effect + */ + Integer deleteByAppId(Long appId); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationPipelineService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationPipelineService.java new file mode 100644 index 0000000..149a4c5 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationPipelineService.java @@ -0,0 +1,60 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.entity.request.app.ApplicationPipelineRequest; +import cn.orionsec.ops.entity.vo.app.ApplicationPipelineVO; + +import java.util.List; + +public interface ApplicationPipelineService { + + /** + * 添加流水线 + * + * @param request request + * @return request + */ + Long addPipeline(ApplicationPipelineRequest request); + + /** + * 更新流水线 + * + * @param request request + * @return effect + */ + Integer updatePipeline(ApplicationPipelineRequest request); + + /** + * 流水线列表 + * + * @param request request + * @return rows + */ + DataGrid listPipeline(ApplicationPipelineRequest request); + + /** + * 获取流水线详情 + * + * @param id id + * @return row + */ + ApplicationPipelineVO getPipeline(Long id); + + /** + * 删除流水线 + * + * @param idList idList + * @return effect + */ + Integer deletePipeline(List idList); + + /** + * 通过 profileId 删除 + * + * @param profileId profileId + * @return effect + */ + Integer deleteByProfileId(Long profileId); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationPipelineTaskDetailService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationPipelineTaskDetailService.java new file mode 100644 index 0000000..30bd9d8 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationPipelineTaskDetailService.java @@ -0,0 +1,51 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.entity.domain.ApplicationPipelineTaskDetailDO; +import cn.orionsec.ops.entity.vo.app.ApplicationPipelineTaskDetailVO; + +import java.util.List; + +public interface ApplicationPipelineTaskDetailService { + + /** + * 获取流水线详情明细 + * + * @param taskId taskId + * @return rows + */ + List getTaskDetails(Long taskId); + + /** + * 获取流水线详情明细 + * + * @param taskId taskId + * @return rows + */ + List selectTaskDetails(Long taskId); + + /** + * 获取流水线详情明细 + * + * @param taskIdList taskIdList + * @return rows + */ + List selectTaskDetails(List taskIdList); + + /** + * 通过 taskId 删除详情明细 + * + * @param taskId taskId + * @return effect + */ + Integer deleteByTaskId(Long taskId); + + /** + * 通过 taskId 删除详情明细 + * + * @param taskIdList taskIdList + * @return effect + */ + Integer deleteByTaskIdList(List taskIdList); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationPipelineTaskLogService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationPipelineTaskLogService.java new file mode 100644 index 0000000..b8a9b53 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationPipelineTaskLogService.java @@ -0,0 +1,26 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.entity.vo.app.ApplicationPipelineTaskLogVO; + +import java.util.List; + +public interface ApplicationPipelineTaskLogService { + + /** + * 获取日志列表 + * + * @param taskId 任务id + * @return rows + */ + List getLogList(Long taskId); + + /** + * 通过 taskIdList 删除 + * + * @param taskIdList taskIdList + * @return effect + */ + Integer deleteByTaskIdList(List taskIdList); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationPipelineTaskService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationPipelineTaskService.java new file mode 100644 index 0000000..dd3c1fc --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationPipelineTaskService.java @@ -0,0 +1,126 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.entity.request.app.ApplicationPipelineTaskRequest; +import cn.orionsec.ops.entity.vo.app.ApplicationPipelineTaskListVO; +import cn.orionsec.ops.entity.vo.app.ApplicationPipelineTaskStatusVO; +import cn.orionsec.ops.entity.vo.app.ApplicationPipelineTaskVO; + +import java.util.Date; +import java.util.List; + +public interface ApplicationPipelineTaskService { + + /** + * 获取列表 + * + * @param request request + * @return rows + */ + DataGrid getPipelineTaskList(ApplicationPipelineTaskRequest request); + + /** + * 获取详情 + * + * @param id id + * @return task + */ + ApplicationPipelineTaskVO getPipelineTaskDetail(Long id); + + /** + * 提交流水线执行 + * + * @param request request + * @return id + */ + Long submitPipelineTask(ApplicationPipelineTaskRequest request); + + /** + * 审核流水线 + * + * @param request request + * @return effect + */ + Integer auditPipelineTask(ApplicationPipelineTaskRequest request); + + /** + * 复制流水线 + * + * @param id id + * @return effect + */ + Long copyPipeline(Long id); + + /** + * 执行流水线 + * + * @param id id + * @param isSystem 是否为系统执行 + */ + void execPipeline(Long id, boolean isSystem); + + /** + * 删除流水线 + * + * @param idList idList + * @return effect + */ + Integer deletePipeline(List idList); + + /** + * 设置定时执行流水线 + * + * @param id id + * @param timedExecDate timedExecDate + */ + void setPipelineTimedExec(Long id, Date timedExecDate); + + /** + * 取消定时执行流水线 + * + * @param id id + */ + void cancelPipelineTimedExec(Long id); + + /** + * 停止执行流水线 + * + * @param id id + */ + void terminateExec(Long id); + + /** + * 停止执行流水线 + * + * @param id id + * @param detailId detailId + */ + void terminateExecDetail(Long id, Long detailId); + + /** + * 停止执行流水线 + * + * @param id id + * @param detailId detailId + */ + void skipExecDetail(Long id, Long detailId); + + /** + * 任务状态 + * + * @param id id + * @return status + */ + ApplicationPipelineTaskStatusVO getTaskStatus(Long id); + + /** + * 任务状态列表 + * + * @param idList idList + * @param detailIdList detailIdList + * @return status + */ + List getTaskStatusList(List idList, List detailIdList); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationProfileService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationProfileService.java new file mode 100644 index 0000000..90148fd --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationProfileService.java @@ -0,0 +1,65 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.entity.request.app.ApplicationProfileRequest; +import cn.orionsec.ops.entity.vo.app.ApplicationProfileFastVO; +import cn.orionsec.ops.entity.vo.app.ApplicationProfileVO; + +import java.util.List; + +public interface ApplicationProfileService { + + /** + * 添加环境 + * + * @param request request + * @return id + */ + Long addProfile(ApplicationProfileRequest request); + + /** + * 更新环境 + * + * @param request request + * @return effect + */ + Integer updateProfile(ApplicationProfileRequest request); + + /** + * 删除环境 + * + * @param id id + * @return effect + */ + Integer deleteProfile(Long id); + + /** + * 环境列表 + * + * @param request request + * @return rows + */ + DataGrid listProfiles(ApplicationProfileRequest request); + + /** + * 环境列表 (缓存) + * + * @return rows + */ + List fastListProfiles(); + + /** + * 环境详情 + * + * @param id id + * @return rows + */ + ApplicationProfileVO getProfile(Long id); + + /** + * 清空环境缓存 + */ + void clearProfileCache(); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationReleaseMachineService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationReleaseMachineService.java new file mode 100644 index 0000000..2e2326a --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationReleaseMachineService.java @@ -0,0 +1,50 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.entity.domain.ApplicationReleaseMachineDO; + +import java.util.List; + +public interface ApplicationReleaseMachineService { + + /** + * 查询发布机器 + * + * @param releaseId releaseId + * @return rows + */ + List getReleaseMachines(Long releaseId); + + /** + * 查询发布机器 + * + * @param releaseIdList releaseIdList + * @return rows + */ + List getReleaseMachines(List releaseIdList); + + /** + * 查询发布机器id + * + * @param releaseIdList releaseIdList + * @return rows + */ + List getReleaseMachineIdList(List releaseIdList); + + /** + * 通过 releaseId 删除 + * + * @param releaseId releaseId + * @return effect + */ + Integer deleteByReleaseId(Long releaseId); + + /** + * 获取发布机器日志 + * + * @param id id + * @return 日志路径 + */ + String getReleaseMachineLogPath(Long id); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationReleaseService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationReleaseService.java new file mode 100644 index 0000000..b7e25da --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationReleaseService.java @@ -0,0 +1,172 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.entity.request.app.ApplicationReleaseAuditRequest; +import cn.orionsec.ops.entity.request.app.ApplicationReleaseRequest; +import cn.orionsec.ops.entity.vo.app.*; + +import java.util.Date; +import java.util.List; + +public interface ApplicationReleaseService { + + /** + * 发布列表 + * + * @param request request + * @return rows + */ + DataGrid getReleaseList(ApplicationReleaseRequest request); + + /** + * 查询发布机器列表 + * + * @param id id + * @return rows + */ + List getReleaseMachineList(Long id); + + /** + * 发布详情 + * + * @param request request + * @return row + */ + ApplicationReleaseDetailVO getReleaseDetail(ApplicationReleaseRequest request); + + /** + * 获取发布机器详情 + * + * @param releaseMachineId id + * @return row + */ + ApplicationReleaseMachineVO getReleaseMachineDetail(Long releaseMachineId); + + /** + * 提交 + * + * @param request request + * @return id + */ + Long submitAppRelease(ApplicationReleaseRequest request); + + /** + * 复制 + * + * @param id id + * @return id + */ + Long copyAppRelease(Long id); + + /** + * 审核 + * + * @param request request + * @return effect + */ + Integer auditAppRelease(ApplicationReleaseAuditRequest request); + + /** + * 执行发布 + * + * @param id id + * @param systemSchedule 是否为系统调度 + * @param execute 是否立即执行 + */ + void runnableAppRelease(Long id, boolean systemSchedule, boolean execute); + + /** + * 取消执行 + * + * @param id id + */ + void cancelAppTimedRelease(Long id); + + /** + * 设置定时时间 + * + * @param id id + * @param releaseTime 定时时间 + */ + void setTimedRelease(Long id, Date releaseTime); + + /** + * 回滚 + * + * @param id id + * @return id + */ + Long rollbackAppRelease(Long id); + + /** + * 终止 + * + * @param id id + */ + void terminateRelease(Long id); + + /** + * 终止 + * + * @param releaseMachineId releaseMachineId + */ + void terminateMachine(Long releaseMachineId); + + /** + * 跳过 + * + * @param releaseMachineId releaseMachineId + */ + void skipMachine(Long releaseMachineId); + + /** + * 输入命令 + * + * @param releaseMachineId releaseMachineId + * @param command 命令 + */ + void writeMachine(Long releaseMachineId, String command); + + /** + * 删除 + * + * @param idList idList + * @return effect + */ + Integer deleteRelease(List idList); + + /** + * 获取发布状态列表 + * + * @param idList idList + * @param machineIdList machineIdList + * @return list + */ + List getReleaseStatusList(List idList, List machineIdList); + + /** + * 获取发布状态 + * + * @param id id + * @return status + */ + ApplicationReleaseStatusVO getReleaseStatus(Long id); + + /** + * 获取发布状态机器列表 + * + * @param releaseMachineIdList releaseMachineIdList + * @return list + */ + List getReleaseMachineStatusList(List releaseMachineIdList); + + /** + * 获取发布机器状态 + * + * @param releaseMachineId releaseMachineId + * @return status + */ + ApplicationReleaseMachineStatusVO getReleaseMachineStatus(Long releaseMachineId); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationRepositoryService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationRepositoryService.java new file mode 100644 index 0000000..1acb97a --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/ApplicationRepositoryService.java @@ -0,0 +1,129 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.ext.vcs.git.Gits; +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.entity.domain.ApplicationRepositoryDO; +import cn.orionsec.ops.entity.request.app.ApplicationRepositoryRequest; +import cn.orionsec.ops.entity.vo.app.ApplicationRepositoryBranchVO; +import cn.orionsec.ops.entity.vo.app.ApplicationRepositoryCommitVO; +import cn.orionsec.ops.entity.vo.app.ApplicationRepositoryInfoVO; +import cn.orionsec.ops.entity.vo.app.ApplicationRepositoryVO; + +import java.util.List; + +public interface ApplicationRepositoryService { + + /** + * 添加仓库 + * + * @param request request + * @return id + */ + Long addRepository(ApplicationRepositoryRequest request); + + /** + * 更新仓库 + * + * @param request request + * @return effect + */ + Integer updateRepository(ApplicationRepositoryRequest request); + + /** + * 通过 id 删除 + * + * @param id id + * @return effect + */ + Integer deleteRepository(Long id); + + /** + * 获取列表 + * + * @param request request + * @return rows + */ + DataGrid listRepository(ApplicationRepositoryRequest request); + + /** + * 获取详情 + * + * @param id id + * @return row + */ + ApplicationRepositoryVO getRepositoryDetail(Long id); + + /** + * 初始化 event 仓库 + * + * @param id id + * @param isReInit 是否是重新初始化 + * @see #getRepositoryInfo + * @see #getRepositoryBranchList + * @see #getRepositoryCommitList + */ + void initEventRepository(Long id, boolean isReInit); + + /** + * 获取版本信息列表 + * + * @param request request + * @return 分支信息 + */ + ApplicationRepositoryInfoVO getRepositoryInfo(ApplicationRepositoryRequest request); + + /** + * 获取分支列表 + * + * @param id id + * @return 分支信息 + */ + List getRepositoryBranchList(Long id); + + /** + * 获取提交列表 + * + * @param id id + * @param branchName 分支名称 + * @return log + */ + List getRepositoryCommitList(Long id, String branchName); + + /** + * 打开事件git + * + * @param id id + * @return gits + */ + Gits openEventGit(Long id); + + /** + * 清空仓库 + * + * @param id id + */ + void cleanBuildRepository(Long id); + + /** + * 同步仓库状态 + */ + void syncRepositoryStatus(); + + /** + * 查询仓库 + * + * @param id id + * @return 仓库 + */ + ApplicationRepositoryDO selectById(Long id); + + /** + * 获取仓库账号密码 + * + * @param repository repository + * @return [username, password] + */ + String[] getRepositoryUsernamePassword(ApplicationRepositoryDO repository); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/BatchUploadService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/BatchUploadService.java new file mode 100644 index 0000000..98c9e8f --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/BatchUploadService.java @@ -0,0 +1,37 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.entity.request.sftp.FileUploadRequest; +import cn.orionsec.ops.entity.request.upload.BatchUploadRequest; +import cn.orionsec.ops.entity.vo.upload.BatchUploadCheckVO; +import cn.orionsec.ops.entity.vo.upload.BatchUploadTokenVO; + +import java.util.List; + +public interface BatchUploadService { + + /** + * 批量上传检查机器以及文件 + * + * @param request request + * @return 检查信息 + */ + BatchUploadCheckVO checkMachineFiles(BatchUploadRequest request); + + /** + * 获取上传 token + * + * @param request request + * @return token + */ + BatchUploadTokenVO getUploadAccessToken(BatchUploadRequest request); + + /** + * 批量上传文件 + * + * @param requestFiles requestFiles + * @return fileToken + */ + List batchUpload(List requestFiles); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/CommandExecService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/CommandExecService.java new file mode 100644 index 0000000..00a2588 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/CommandExecService.java @@ -0,0 +1,87 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.entity.domain.CommandExecDO; +import cn.orionsec.ops.entity.request.exec.CommandExecRequest; +import cn.orionsec.ops.entity.vo.exec.CommandExecStatusVO; +import cn.orionsec.ops.entity.vo.exec.CommandExecVO; +import cn.orionsec.ops.entity.vo.exec.CommandTaskSubmitVO; +import org.springframework.web.bind.annotation.RequestBody; + +import java.util.List; + +public interface CommandExecService { + + /** + * 提交命令 + * + * @param request request + * @return tasks + */ + List batchSubmitTask(CommandExecRequest request); + + /** + * 命令执行列表 + * + * @param request request + * @return rows + */ + DataGrid execList(@RequestBody CommandExecRequest request); + + /** + * 命令执行详情 + * + * @param id id + * @return detail + */ + CommandExecVO execDetail(Long id); + + /** + * 写入命令 + * + * @param id id + * @param command command + */ + void writeCommand(Long id, String command); + + /** + * 终止任务 + * + * @param id id + */ + void terminateExec(Long id); + + /** + * 删除任务 + * + * @param idList idList + * @return effect + */ + Integer deleteTask(List idList); + + /** + * 查询状态 + * + * @param execIdList execIdList + * @return status + */ + List getExecStatusList(List execIdList); + + /** + * 通过id查询 execDO + * + * @param id id + * @return rows + */ + CommandExecDO selectById(Long id); + + /** + * 获取 exec command 日志 + * + * @param id id + * @return logPath + */ + String getExecLogFilePath(Long id); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/CommandTemplateService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/CommandTemplateService.java new file mode 100644 index 0000000..023e21a --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/CommandTemplateService.java @@ -0,0 +1,52 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.entity.request.template.CommandTemplateRequest; +import cn.orionsec.ops.entity.vo.template.CommandTemplateVO; + +import java.util.List; + +public interface CommandTemplateService { + + /** + * 添加模板 + * + * @param request request + * @return id + */ + Long addTemplate(CommandTemplateRequest request); + + /** + * 更新模板 + * + * @param request request + * @return effect + */ + Integer updateTemplate(CommandTemplateRequest request); + + /** + * 模板列表 + * + * @param request request + * @return rows + */ + DataGrid listTemplate(CommandTemplateRequest request); + + /** + * 模板详情 + * + * @param id id + * @return vo + */ + CommandTemplateVO templateDetail(Long id); + + /** + * 删除模板 + * + * @param idList idList + * @return effect + */ + Integer deleteTemplate(List idList); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/CommonService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/CommonService.java new file mode 100644 index 0000000..645bd4c --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/CommonService.java @@ -0,0 +1,7 @@ + +package cn.orionsec.ops.service.api; + +public interface CommonService { + + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/FileDownloadService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/FileDownloadService.java new file mode 100644 index 0000000..211f7f5 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/FileDownloadService.java @@ -0,0 +1,38 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.constant.download.FileDownloadType; +import cn.orionsec.ops.entity.dto.file.FileDownloadDTO; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public interface FileDownloadService { + + /** + * 下载文件 检查文件是否存在 + * + * @param id id + * @param type type + * @return token + */ + String getDownloadToken(Long id, FileDownloadType type); + + /** + * 通过token获取下载文件路径 + * + * @param token token + * @return path + */ + FileDownloadDTO getPathByDownloadToken(String token); + + /** + * 执行下载 + * + * @param token token + * @param response response + * @throws IOException IOException + */ + void execDownload(String token, HttpServletResponse response) throws IOException; + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/FileTailService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/FileTailService.java new file mode 100644 index 0000000..1fa2fbb --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/FileTailService.java @@ -0,0 +1,102 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.constant.tail.FileTailType; +import cn.orionsec.ops.entity.request.file.FileTailRequest; +import cn.orionsec.ops.entity.vo.tail.FileTailConfigVO; +import cn.orionsec.ops.entity.vo.tail.FileTailVO; + +import java.util.List; + +public interface FileTailService { + + /** + * 获取 tail 文件 token 检查文件是否存在 + * + * @param type type + * @param relId relId + * @return FileTailVO + */ + FileTailVO getTailToken(FileTailType type, Long relId); + + /** + * 添加 tail 文件 + * + * @param request request + * @return id + */ + Long insertTailFile(FileTailRequest request); + + /** + * 修改 tail 文件 + * + * @param request request + * @return effect + */ + Integer updateTailFile(FileTailRequest request); + + /** + * 上传文件 + * + * @param files files + */ + void uploadTailFiles(List files); + + /** + * 删除 tail 文件 + * + * @param idList idList + * @return effect + */ + Integer deleteTailFile(List idList); + + /** + * 删除 tail 文件 + * + * @param machineIdList machineIdList + * @return effect + */ + Integer deleteByMachineIdList(List machineIdList); + + /** + * tail 列表 + * + * @param request request + * @return dataGrid + */ + DataGrid tailFileList(FileTailRequest request); + + /** + * tail 详情 + * + * @param id id + * @return row + */ + FileTailVO tailFileDetail(Long id); + + /** + * 更新 更新时间 + * + * @param id id + * @return effect + */ + Integer updateFileUpdateTime(Long id); + + /** + * 获取机器配置 + * + * @param machineId machineId + * @return config + */ + FileTailConfigVO getMachineConfig(Long machineId); + + /** + * 写入命令 + * + * @param token token + * @param command command + */ + void writeCommand(String token, String command); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/HistoryValueService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/HistoryValueService.java new file mode 100644 index 0000000..031e466 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/HistoryValueService.java @@ -0,0 +1,38 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.constant.history.HistoryOperator; +import cn.orionsec.ops.constant.history.HistoryValueType; +import cn.orionsec.ops.entity.request.history.HistoryValueRequest; +import cn.orionsec.ops.entity.vo.history.HistoryValueVO; + +public interface HistoryValueService { + + /** + * 添加历史值快照 + * + * @param valueId valueId + * @param valueType valueType + * @param operatorType operatorType + * @param beforeValue beforeValue + * @param afterValue afterValue + */ + void addHistory(Long valueId, HistoryValueType valueType, HistoryOperator operatorType, String beforeValue, String afterValue); + + /** + * 值列表 + * + * @param request request + * @return rows + */ + DataGrid list(HistoryValueRequest request); + + /** + * 回滚 + * + * @param id id + */ + void rollback(Long id); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineAlarmConfigService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineAlarmConfigService.java new file mode 100644 index 0000000..a64ae86 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineAlarmConfigService.java @@ -0,0 +1,76 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.entity.domain.MachineAlarmConfigDO; +import cn.orionsec.ops.entity.request.machine.MachineAlarmConfigRequest; +import cn.orionsec.ops.entity.vo.machine.MachineAlarmConfigVO; +import cn.orionsec.ops.entity.vo.machine.MachineAlarmConfigWrapperVO; + +import java.util.List; + +public interface MachineAlarmConfigService { + + /** + * 获取报警配置 + * + * @param machineId machineId + * @return config + */ + MachineAlarmConfigWrapperVO getAlarmConfigInfo(Long machineId); + + /** + * 获取报警配置 + * + * @param machineId machineId + * @return config + */ + List getAlarmConfig(Long machineId); + + /** + * 设置报警配置 + * + * @param request request + */ + void setAlarmConfig(MachineAlarmConfigRequest request); + + /** + * 设置报警联系组 + * + * @param machineId machineId + * @param groupIdList groupIdList + */ + void setAlarmGroup(Long machineId, List groupIdList); + + /** + * 通过机器id查询 + * + * @param machineId machineId + * @return rows + */ + List selectByMachineId(Long machineId); + + /** + * 通过机器id查询数量 + * + * @param machineId machineId + * @return count + */ + Integer selectCountByMachineId(Long machineId); + + /** + * 通过机器id删除 + * + * @param machineId machineId + * @return effect + */ + Integer deleteByMachineId(Long machineId); + + /** + * 通过机器id删除 + * + * @param machineIdList machineIdList + * @return effect + */ + Integer deleteByMachineIdList(List machineIdList); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineAlarmGroupService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineAlarmGroupService.java new file mode 100644 index 0000000..c2e0cae --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineAlarmGroupService.java @@ -0,0 +1,42 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.entity.domain.MachineAlarmGroupDO; + +import java.util.List; + +public interface MachineAlarmGroupService { + + /** + * 通过机器id查询 + * + * @param machineId machineId + * @return rows + */ + List selectByMachineId(Long machineId); + + /** + * 通过机器id删除 + * + * @param machineId machineId + * @return effect + */ + Integer deleteByMachineId(Long machineId); + + /** + * 通过机器id删除 + * + * @param machineIdList machineIdList + * @return effect + */ + Integer deleteByMachineIdList(List machineIdList); + + /** + * 通过报警组id删除 + * + * @param groupId groupId + * @return effect + */ + Integer deleteByGroupId(Long groupId); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineAlarmHistoryService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineAlarmHistoryService.java new file mode 100644 index 0000000..b2cd8bd --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineAlarmHistoryService.java @@ -0,0 +1,18 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.entity.request.machine.MachineAlarmHistoryRequest; +import cn.orionsec.ops.entity.vo.machine.MachineAlarmHistoryVO; + +public interface MachineAlarmHistoryService { + + /** + * 获取机器报警历史 + * + * @param request request + * @return rows + */ + DataGrid getAlarmHistory(MachineAlarmHistoryRequest request); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineAlarmService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineAlarmService.java new file mode 100644 index 0000000..93e1cd8 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineAlarmService.java @@ -0,0 +1,22 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.entity.request.machine.MachineAlarmRequest; + +public interface MachineAlarmService { + + /** + * 触发机器报警 + * + * @param alarmHistoryId alarmHistoryId + */ + void triggerMachineAlarm(Long alarmHistoryId); + + /** + * 触发机器报警 + * + * @param request request + */ + void triggerMachineAlarm(MachineAlarmRequest request); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineEnvService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineEnvService.java new file mode 100644 index 0000000..c1686d7 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineEnvService.java @@ -0,0 +1,176 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.collect.MutableLinkedHashMap; +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.entity.domain.MachineEnvDO; +import cn.orionsec.ops.entity.request.machine.MachineEnvRequest; +import cn.orionsec.ops.entity.vo.machine.MachineEnvVO; + +import java.util.List; +import java.util.Map; + +public interface MachineEnvService { + + /** + * 添加变量 + * + * @param request request + * @return id + */ + Long addEnv(MachineEnvRequest request); + + /** + * 修改变量 + * + * @param request request + * @return effect + */ + Integer updateEnv(MachineEnvRequest request); + + /** + * 修改变量 + * + * @param before before + * @param request request + * @return effect + */ + Integer updateEnv(MachineEnvDO before, MachineEnvRequest request); + + /** + * 通过id删除 + * + * @param idList idList + * @return effect + */ + Integer deleteEnv(List idList); + + /** + * 批量添加 + * + * @param machineId machineId + * @param env env + */ + void saveEnv(Long machineId, Map env); + + /** + * 合并属性 + * + * @param sourceMachineId 原始机器id + * @param targetMachineId 目标机器id + * @return effect + */ + Integer mergeEnv(Long sourceMachineId, Long targetMachineId); + + /** + * 列表 + * + * @param request request + * @return rows + */ + DataGrid listEnv(MachineEnvRequest request); + + /** + * 详情 + * + * @param id id + * @return row + */ + MachineEnvVO getEnvDetail(Long id); + + /** + * 同步机器属性 + * + * @param request request + */ + void syncMachineEnv(MachineEnvRequest request); + + /** + * 获取机器变量 + * + * @param machineId machineId + * @param env envKey + * @return env + */ + String getMachineEnv(Long machineId, String env); + + /** + * 获取机器环境变量 + * + * @param machineId machineId + * @return map + */ + MutableLinkedHashMap getMachineEnv(Long machineId); + + /** + * 获取机器环境变量 包含机器信息 + * + * @param machineId machineId + * @return map + */ + MutableLinkedHashMap getFullMachineEnv(Long machineId); + + /** + * 初始化机器环境 + * + * @param machineId 机器id + */ + void initEnv(Long machineId); + + /** + * 删除机器环境变量 + * + * @param machineIdList 机器id + * @return effect + */ + Integer deleteEnvByMachineIdList(List machineIdList); + + /** + * 获取 sftp 编码格式 + * + * @param machineId 机器id + * @return 编码格式 + */ + String getSftpCharset(Long machineId); + + /** + * 获取文件 tail 尾行偏移量 + * + * @param machineId 机器id + * @return offset line + */ + Integer getTailOffset(Long machineId); + + /** + * 获取文件 tail 编码集 + * + * @param machineId 机器id + * @return 编码集 + */ + String getTailCharset(Long machineId); + + /** + * 获取文件 tail 默认命令 + * + * @param machineId 机器id + * @return 默认命令 + */ + String getTailDefaultCommand(Long machineId); + + /** + * 获取连接超时时间 + * + * @param machineId machineId + * @return 连接超时时间 + */ + Integer getConnectTimeout(Long machineId); + + /** + * 获取连接失败重试次数 + * + * @param machineId machineId + * @return 连接失败重试次数 + */ + Integer getConnectRetryTimes(Long machineId); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineGroupRelService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineGroupRelService.java new file mode 100644 index 0000000..6147155 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineGroupRelService.java @@ -0,0 +1,62 @@ + +package cn.orionsec.ops.service.api; + +import java.util.List; +import java.util.Map; + +public interface MachineGroupRelService { + + /** + * 组内添加机器 一组多机器 (分组视图添加/修改机器时) + * + * @param groupId groupId + * @param machineIdList machineIdList + */ + void addMachineRelByGroup(Long groupId, List machineIdList); + + /** + * 修改机器分组 一机器多组 (新增/修改机器时) + * + * @param machineId machineId + * @param groupIdList groupIdList + */ + void updateMachineGroup(Long machineId, List groupIdList); + + /** + * 组内删除机器 + * + * @param groupIdList groupIdList + * @param machineIdList machineIdList + * @return effect + */ + Integer deleteByGroupMachineId(List groupIdList, List machineIdList); + + /** + * 通过机器id删除 + * + * @param machineIdList machineIdList + * @return effect + */ + Integer deleteByMachineIdList(List machineIdList); + + /** + * 通过分组id删除 + * + * @param groupIdList groupIdList + * @return effect + */ + Integer deleteByGroupIdList(List groupIdList); + + /** + * 获取机器分组引用缓存 + * + * @return cache + */ + Map> getMachineRelByCache(); + + /** + * 清理分组机器引用缓存 + */ + void clearGroupRelCache(); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineGroupService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineGroupService.java new file mode 100644 index 0000000..3b9112c --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineGroupService.java @@ -0,0 +1,50 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.entity.request.machine.MachineGroupRequest; +import cn.orionsec.ops.entity.vo.machine.MachineGroupTreeVO; + +import java.util.List; + +public interface MachineGroupService { + + /** + * 添加组 + * + * @param request request + * @return id + */ + Long addGroup(MachineGroupRequest request); + + /** + * 删除组 + * + * @param idList idList + * @return effect + */ + Integer deleteGroup(List idList); + + /** + * 移动组 + * + * @param request request + */ + void moveGroup(MachineGroupRequest request); + + /** + * 重命名 + * + * @param id id + * @param name name + * @return effect + */ + Integer renameGroup(Long id, String name); + + /** + * 获取机器分组树 + * + * @return tree + */ + List getRootTree(); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineInfoService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineInfoService.java new file mode 100644 index 0000000..2ed0a1f --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineInfoService.java @@ -0,0 +1,140 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.net.host.SessionStore; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.request.machine.MachineInfoRequest; +import cn.orionsec.ops.entity.vo.machine.MachineInfoVO; + +import java.util.List; + +public interface MachineInfoService { + + /** + * 添加机器 + * + * @param request request + * @return id + */ + Long addMachine(MachineInfoRequest request); + + /** + * 修改机器 + * + * @param request request + * @return effect + */ + Integer updateMachine(MachineInfoRequest request); + + /** + * 删除机器 + * + * @param idList idList + * @return effect + */ + Integer deleteMachine(List idList); + + /** + * 更新机器状态 + * + * @param idList idList + * @param status status + * @return effect + */ + Integer updateStatus(List idList, Integer status); + + /** + * 机器列表 + * + * @param request request + * @return dataGrid + */ + DataGrid listMachine(MachineInfoRequest request); + + /** + * 机器详情 + * + * @param id id + * @return dataGrid + */ + MachineInfoVO machineDetail(Long id); + + /** + * 复制机器机器 + * + * @param id id + * @return id + */ + Long copyMachine(Long id); + + /** + * 通过id查询机器 + * + * @param id id + * @return row + */ + MachineInfoDO selectById(Long id); + + /** + * 尝试ping 主机 + * + * @param id id + */ + void testPing(Long id); + + /** + * 尝试ping 主机 + * + * @param host host + */ + void testPing(String host); + + /** + * 尝试连接 主机 + * + * @param id id + */ + void testConnect(Long id); + + /** + * 尝试连接 主机 + * + * @param request request + */ + void testConnect(MachineInfoRequest request); + + /** + * 建立远程连接 + * + * @param id id + * @return session + */ + SessionStore openSessionStore(Long id); + + /** + * 建立远程连接 + * + * @param machine machine + * @return session + */ + SessionStore openSessionStore(MachineInfoDO machine); + + /** + * 同步执行命令获取输出结果 + * + * @param id 机器id + * @param command 命令 + * @return result + */ + String getCommandResultSync(Long id, String command); + + /** + * 获取机器名称 + * + * @param id id + * @return name + */ + String getMachineName(Long id); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineKeyService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineKeyService.java new file mode 100644 index 0000000..f58a504 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineKeyService.java @@ -0,0 +1,81 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.entity.domain.MachineSecretKeyDO; +import cn.orionsec.ops.entity.request.machine.MachineKeyRequest; +import cn.orionsec.ops.entity.vo.machine.MachineSecretKeyVO; + +import java.util.List; + +public interface MachineKeyService { + + /** + * 添加 key + * + * @param request request + * @return id + */ + Long addSecretKey(MachineKeyRequest request); + + /** + * 修改 key + * + * @param request request + * @return effect + */ + Integer updateSecretKey(MachineKeyRequest request); + + /** + * 删除 key + * + * @param idList idList + * @return effect + */ + Integer removeSecretKey(List idList); + + /** + * 通过 id 查询 key + * + * @param id id + * @return key + */ + MachineSecretKeyDO getKeyById(Long id); + + /** + * 查询密钥列表 + * + * @param request request + * @return dataGrid + */ + DataGrid listKeys(MachineKeyRequest request); + + /** + * 查询密钥详情 + * + * @param id id + * @return row + */ + MachineSecretKeyVO getKeyDetail(Long id); + + /** + * 绑定机器密钥 + * + * @param id id + * @param machineIdList 机器id + */ + void bindMachineKey(Long id, List machineIdList); + + /** + * 获取key的实际路径 + * + * @param path path + * @return 实际路径 + */ + static String getKeyPath(String path) { + return Files1.getPath(SystemEnvAttr.KEY_PATH.getValue(), path); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineMonitorEndpointService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineMonitorEndpointService.java new file mode 100644 index 0000000..9dcf974 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineMonitorEndpointService.java @@ -0,0 +1,82 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.entity.request.machine.MachineMonitorEndpointRequest; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; + +public interface MachineMonitorEndpointService { + + /** + * 发送 ping + * + * @param machineId machineId + * @return pong:1 + */ + Integer ping(Long machineId); + + /** + * 获取基本指标信息 + * + * @param machineId machineId + * @return metrics + */ + JSONObject getBaseMetrics(Long machineId); + + /** + * 获取系统负载 + * + * @param machineId machineId + * @return load + */ + JSONObject getSystemLoad(Long machineId); + + /** + * 获取 top 进程 + * + * @param machineId machineId + * @param name 名称 + * @return 进程 + */ + JSONArray getTopProcesses(Long machineId, String name); + + /** + * 查询磁盘名称 + * + * @param machineId machineId + * @return 名称 + */ + JSONArray getDiskName(Long machineId); + + /** + * 获取cpu图标 + * + * @param request request + * @return 图表 + */ + JSONObject getCpuChart(MachineMonitorEndpointRequest request); + + /** + * 获取内存图标 + * + * @param request request + * @return 图表 + */ + JSONObject getMemoryChart(MachineMonitorEndpointRequest request); + + /** + * 获取网络图标 + * + * @param request request + * @return 图表 + */ + JSONObject getNetChart(MachineMonitorEndpointRequest request); + + /** + * 获取磁盘图标 + * + * @param request request + * @return 图表 + */ + JSONObject getDiskChart(MachineMonitorEndpointRequest request); +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineMonitorService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineMonitorService.java new file mode 100644 index 0000000..9d6e65e --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineMonitorService.java @@ -0,0 +1,98 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.entity.domain.MachineMonitorDO; +import cn.orionsec.ops.entity.request.machine.MachineMonitorRequest; +import cn.orionsec.ops.entity.vo.machine.MachineMonitorVO; + +import java.util.List; + +public interface MachineMonitorService { + + /** + * 通过 机器id 查询 + * + * @param machineId machineId + * @return row + */ + MachineMonitorDO selectByMachineId(Long machineId); + + /** + * 获取监控列表 + * + * @param request request + * @return rows + */ + DataGrid getMonitorList(MachineMonitorRequest request); + + /** + * 获取监控配置 + * + * @param machineId machineId + * @return 配置信息 + */ + MachineMonitorVO getMonitorConfig(Long machineId); + + /** + * 更新监控配置 + * + * @param request request + * @return status + */ + MachineMonitorVO updateMonitorConfig(MachineMonitorRequest request); + + /** + * 更新监控配置 + * + * @param machineId machineId + * @param update update + * @return effect + */ + Integer updateMonitorConfigByMachineId(Long machineId, MachineMonitorDO update); + + /** + * 通过机器 id 删除 + * + * @param machineIdList machineIdList + * @return effect + */ + Integer deleteByMachineIdList(List machineIdList); + + /** + * 安装监控插件 + * + * @param machineId machineId + * @param upgrade upgrade + * @return status + */ + MachineMonitorVO installMonitorAgent(Long machineId, boolean upgrade); + + /** + * 检测监控插件状态 + * + * @param machineId machineId + * @return status + */ + MachineMonitorVO checkMonitorStatus(Long machineId); + + /** + * 获取版本 + * + * @param url url + * @param accessToken accessToken + * @return version + */ + String getMonitorVersion(String url, String accessToken); + + /** + * 同步机器插件信息 + * + * @param machineId machineId + * @param url url + * @param accessToken accessToken + * @return version + */ + String syncMonitorAgent(Long machineId, String url, String accessToken); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineProxyService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineProxyService.java new file mode 100644 index 0000000..e33b239 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineProxyService.java @@ -0,0 +1,52 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.entity.request.machine.MachineProxyRequest; +import cn.orionsec.ops.entity.vo.machine.MachineProxyVO; + +import java.util.List; + +public interface MachineProxyService { + + /** + * 添加代理 + * + * @param request request + * @return id + */ + Long addProxy(MachineProxyRequest request); + + /** + * 修改代理 + * + * @param request request + * @return effect + */ + Integer updateProxy(MachineProxyRequest request); + + /** + * 分页查询 + * + * @param request request + * @return rows + */ + DataGrid listProxy(MachineProxyRequest request); + + /** + * 查询详情 + * + * @param id id + * @return row + */ + MachineProxyVO getProxyDetail(Long id); + + /** + * 删除代理 + * + * @param idList idList + * @return effect + */ + Integer deleteProxy(List idList); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineTerminalService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineTerminalService.java new file mode 100644 index 0000000..0bf5660 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/MachineTerminalService.java @@ -0,0 +1,89 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.entity.domain.MachineTerminalLogDO; +import cn.orionsec.ops.entity.request.machine.MachineTerminalLogRequest; +import cn.orionsec.ops.entity.request.machine.MachineTerminalRequest; +import cn.orionsec.ops.entity.vo.machine.MachineTerminalLogVO; +import cn.orionsec.ops.entity.vo.machine.MachineTerminalVO; +import cn.orionsec.ops.entity.vo.machine.TerminalAccessVO; + +import java.util.List; + +public interface MachineTerminalService { + + /** + * 获取访问配置 + * + * @param machineId 机器id + * @return 访问配置 + */ + TerminalAccessVO getAccessConfig(Long machineId); + + /** + * 获取终端配置 + * + * @param machineId 机器id + * @return 配置 + */ + MachineTerminalVO getMachineConfig(Long machineId); + + /** + * 设置终端配置 + * + * @param request request + * @return effect + */ + Integer updateSetting(MachineTerminalRequest request); + + /** + * 添加日志 + * + * @param entity entity + * @return id + */ + Long addTerminalLog(MachineTerminalLogDO entity); + + /** + * 更新日志 + * + * @param token token + * @param entity entity + * @return effect + */ + Integer updateAccessLog(String token, MachineTerminalLogDO entity); + + /** + * 查询终端访问日志 + * + * @param request request + * @return dataGrid + */ + DataGrid listAccessLog(MachineTerminalLogRequest request); + + /** + * 删除终端日志 + * + * @param idList idList + * @return effect + */ + Integer deleteTerminalLog(List idList); + + /** + * 通过机器id删除终端配置 + * + * @param machineIdList 机器id + * @return effect + */ + Integer deleteTerminalByMachineIdList(List machineIdList); + + /** + * 获取下载 terminal 录屏路径 + * + * @param id id + * @return path + */ + String getTerminalScreenFilePath(Long id); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/PassportService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/PassportService.java new file mode 100644 index 0000000..4bc1ef1 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/PassportService.java @@ -0,0 +1,40 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.entity.request.user.UserLoginRequest; +import cn.orionsec.ops.entity.request.user.UserResetRequest; +import cn.orionsec.ops.entity.vo.user.UserLoginVO; + +public interface PassportService { + + /** + * 登录 + * + * @param request request + * @return wrapper + */ + UserLoginVO login(UserLoginRequest request); + + /** + * 登出 + */ + void logout(); + + /** + * 重置密码 + * + * @param request request + */ + void resetPassword(UserResetRequest request); + + /** + * 通过token获取用户 所有通过token查用户都要用此方法 + * + * @param token token + * @param checkIp 检查的ip 不检查设置为null + * @return user + */ + UserDTO getUserByToken(String token, String checkIp); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SchedulerTaskMachineRecordService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SchedulerTaskMachineRecordService.java new file mode 100644 index 0000000..6900671 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SchedulerTaskMachineRecordService.java @@ -0,0 +1,34 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.entity.domain.SchedulerTaskMachineRecordDO; + +import java.util.List; + +public interface SchedulerTaskMachineRecordService { + + /** + * 通过 taskId 删除 + * + * @param taskId taskId + * @return effect + */ + Integer deleteByTaskId(Long taskId); + + /** + * 通过 recordId 查询机器明细 + * + * @param recordId recordId + * @return rows + */ + List selectByRecordId(Long recordId); + + /** + * 获取任务机器执行日志路径 + * + * @param machineRecordId machineRecordId + * @return path + */ + String getTaskMachineLogPath(Long machineRecordId); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SchedulerTaskMachineService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SchedulerTaskMachineService.java new file mode 100644 index 0000000..04e5f1b --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SchedulerTaskMachineService.java @@ -0,0 +1,34 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.entity.domain.SchedulerTaskMachineDO; + +import java.util.List; + +public interface SchedulerTaskMachineService { + + /** + * 通过 taskId 查询 + * + * @param taskId taskId + * @return rows + */ + List selectByTaskId(Long taskId); + + /** + * 通过 taskId 删除 + * + * @param taskId taskId + * @return effect + */ + Integer deleteByTaskId(Long taskId); + + /** + * 通过 机器id 删除 + * + * @param machineIdList machineIdList + * @return effect + */ + Integer deleteByMachineIdList(List machineIdList); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SchedulerTaskRecordService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SchedulerTaskRecordService.java new file mode 100644 index 0000000..f585b46 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SchedulerTaskRecordService.java @@ -0,0 +1,109 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.entity.request.scheduler.SchedulerTaskRecordRequest; +import cn.orionsec.ops.entity.vo.scheduler.SchedulerTaskMachineRecordStatusVO; +import cn.orionsec.ops.entity.vo.scheduler.SchedulerTaskMachineRecordVO; +import cn.orionsec.ops.entity.vo.scheduler.SchedulerTaskRecordStatusVO; +import cn.orionsec.ops.entity.vo.scheduler.SchedulerTaskRecordVO; + +import java.util.List; + +public interface SchedulerTaskRecordService { + + /** + * 通过 taskId 删除 + * + * @param taskId taskId + * @return effect + */ + Integer deleteByTaskId(Long taskId); + + /** + * 创建调度明细 + * + * @param taskId taskId + * @return recordId + */ + Long createTaskRecord(Long taskId); + + /** + * 查询任务明细 + * + * @param request request + * @return rows + */ + DataGrid listTaskRecord(SchedulerTaskRecordRequest request); + + /** + * 通过 id 查询 + * + * @param id id + * @return row + */ + SchedulerTaskRecordVO getDetailById(Long id); + + /** + * 查询任务机器明细 + * + * @param recordId recordId + * @return rows + */ + List listMachinesRecord(Long recordId); + + /** + * 查询任务状态 + * + * @param idList idList + * @param machineRecordIdList machineRecordIdList + * @return status + */ + List listRecordStatus(List idList, List machineRecordIdList); + + /** + * 查询任务机器状态 + * + * @param idList id + * @return status + */ + List listMachineRecordStatus(List idList); + + /** + * 删除调度明细 + * + * @param idList idList + * @return effect + */ + Integer deleteTaskRecord(List idList); + + /** + * 停止所有 + * + * @param id id + */ + void terminateAll(Long id); + + /** + * 停止单个 + * + * @param machineRecordId machineRecordId + */ + void terminateMachine(Long machineRecordId); + + /** + * 跳过单个 + * + * @param machineRecordId machineRecordId + */ + void skipMachine(Long machineRecordId); + + /** + * 发送命令 + * + * @param machineRecordId machineRecordId + * @param command command + */ + void writeMachine(Long machineRecordId, String command); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SchedulerTaskService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SchedulerTaskService.java new file mode 100644 index 0000000..cb157fb --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SchedulerTaskService.java @@ -0,0 +1,66 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.entity.request.scheduler.SchedulerTaskRequest; +import cn.orionsec.ops.entity.vo.scheduler.SchedulerTaskVO; + +public interface SchedulerTaskService { + + /** + * 添加任务 + * + * @param request request + * @return id + */ + Long addTask(SchedulerTaskRequest request); + + /** + * 修改任务 + * + * @param request request + * @return effect + */ + Integer updateTask(SchedulerTaskRequest request); + + /** + * 任务详情 + * + * @param id id + * @return row + */ + SchedulerTaskVO getTaskDetail(Long id); + + /** + * 任务列表 + * + * @param request request + * @return rows + */ + DataGrid getTaskList(SchedulerTaskRequest request); + + /** + * 更新状态 + * + * @param id id + * @param status status + * @return effect + */ + Integer updateTaskStatus(Long id, Integer status); + + /** + * 删除任务 + * + * @param id id + * @return effect + */ + Integer deleteTask(Long id); + + /** + * 手动触发任务 + * + * @param id id + */ + void manualTriggerTask(Long id); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SftpService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SftpService.java new file mode 100644 index 0000000..907016a --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SftpService.java @@ -0,0 +1,258 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.constant.sftp.SftpPackageType; +import cn.orionsec.ops.entity.domain.FileTransferLogDO; +import cn.orionsec.ops.entity.dto.sftp.SftpSessionTokenDTO; +import cn.orionsec.ops.entity.dto.sftp.SftpUploadInfoDTO; +import cn.orionsec.ops.entity.request.sftp.*; +import cn.orionsec.ops.entity.vo.sftp.FileListVO; +import cn.orionsec.ops.entity.vo.sftp.FileOpenVO; +import cn.orionsec.ops.entity.vo.sftp.FileTransferLogVO; + +import java.util.List; + +public interface SftpService { + + /** + * 打开sftp连接 + * + * @param machineId 机器id + * @return FileOpenVO + */ + FileOpenVO open(Long machineId); + + /** + * 文件列表 + * + * @param request request + * @return list + */ + FileListVO list(FileListRequest request); + + /** + * 文件夹列表 + * + * @param request request + * @return list + */ + FileListVO listDir(FileListRequest request); + + /** + * mkdir + * + * @param request request + * @return 文件目录 + */ + String mkdir(FileMkdirRequest request); + + /** + * touch + * + * @param request request + * @return 文件目录 + */ + String touch(FileTouchRequest request); + + /** + * truncate + * + * @param request request + */ + void truncate(FileTruncateRequest request); + + /** + * mv + * + * @param request request + * @return 移动后的位置 + */ + String move(FileMoveRequest request); + + /** + * rm -rf + * + * @param request request + */ + void remove(FileRemoveRequest request); + + /** + * chmod + * + * @param request request + * @return 权限字符串 + */ + String chmod(FileChmodRequest request); + + /** + * chown + * + * @param request request + */ + void chown(FileChownRequest request); + + /** + * chgrp + * + * @param request request + */ + void changeGroup(FileChangeGroupRequest request); + + /** + * 检查文件是否存在 + * + * @param request request + * @return 存在的文件名称 + */ + List checkFilePresent(FilePresentCheckRequest request); + + /** + * 获取上传文件 accessToken + * + * @param request request + * @return accessToken + */ + String getUploadAccessToken(FileUploadRequest request); + + /** + * 检查上传token + * + * @param accessToken accessToken + * @return machineId + */ + SftpUploadInfoDTO checkUploadAccessToken(String accessToken); + + /** + * 上传文件 + * + * @param machineId machineId + * @param requestFiles requestFiles + */ + void upload(Long machineId, List requestFiles); + + /** + * 下载文件 + * + * @param request request + */ + void download(FileDownloadRequest request); + + /** + * 打包下载文件 + * + * @param request request + */ + void packageDownload(FileDownloadRequest request); + + /** + * 传输暂停 + * + * @param fileToken fileToken + */ + void transferPause(String fileToken); + + /** + * 传输恢复 + * + * @param fileToken fileToken + */ + void transferResume(String fileToken); + + /** + * 传输失败重试 + * + * @param fileToken fileToken + */ + void transferRetry(String fileToken); + + /** + * 重新上传 + * + * @param fileToken fileToken + */ + void transferReUpload(String fileToken); + + /** + * 重新下载 + * + * @param fileToken fileToken + */ + void transferReDownload(String fileToken); + + /** + * 传输暂停 + * + * @param sessionToken sessionToken + */ + void transferPauseAll(String sessionToken); + + /** + * 传输恢复 + * + * @param sessionToken sessionToken + */ + void transferResumeAll(String sessionToken); + + /** + * 传输失败重试 + * + * @param sessionToken sessionToken + */ + void transferRetryAll(String sessionToken); + + /** + * 传输列表 + * + * @param machineId 机器id + * @return rows + */ + List transferList(Long machineId); + + /** + * 传输删除 (单个) + * + * @param fileToken fileToken + */ + void transferRemove(String fileToken); + + /** + * 传输清空 (全部) + * + * @param machineId machineId + * @return effect + */ + Integer transferClear(Long machineId); + + /** + * 传输打包 全部已完成未删除的文件 + * + * @param sessionToken sessionToken + * @param packageType packageType + */ + void transferPackage(String sessionToken, SftpPackageType packageType); + + /** + * 获取 sftp 下载文件本地路径 + * + * @param id id + * @return FileTransferLogDO + */ + FileTransferLogDO getDownloadFilePath(Long id); + + /** + * 获取机器id + * + * @param sessionToken sessionToken + * @return 机器id + */ + Long getMachineId(String sessionToken); + + /** + * 获取 token 信息 + * + * @param sessionToken sessionToken + * @return token 信息 + */ + SftpSessionTokenDTO getTokenInfo(String sessionToken); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/StatisticsService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/StatisticsService.java new file mode 100644 index 0000000..aed351d --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/StatisticsService.java @@ -0,0 +1,107 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.entity.request.home.HomeStatisticsRequest; +import cn.orionsec.ops.entity.vo.app.*; +import cn.orionsec.ops.entity.vo.home.HomeStatisticsVO; +import cn.orionsec.ops.entity.vo.scheduler.SchedulerTaskRecordStatisticsVO; + +import java.util.List; + +public interface StatisticsService { + + /** + * 获取首页统计信息 + * + * @param request request + * @return 统计信息 + */ + HomeStatisticsVO homeStatistics(HomeStatisticsRequest request); + + /** + * 获取调度任务统计 + * + * @param taskId 任务id + * @return 统计 + */ + SchedulerTaskRecordStatisticsVO schedulerTaskStatistics(Long taskId); + + /** + * 获取应用构建统计 视图 + * + * @param appId appId + * @param profileId profileId + * @return 视图 + */ + ApplicationBuildStatisticsViewVO appBuildStatisticsView(Long appId, Long profileId); + + /** + * 获取应用构建统计 指标 + * + * @param appId appId + * @param profileId profileId + * @return 指标 + */ + ApplicationBuildStatisticsMetricsWrapperVO appBuildStatisticsMetrics(Long appId, Long profileId); + + /** + * 获取应用构建统计 表格 + * + * @param appId appId + * @param profileId profileId + * @return 折线图 + */ + List appBuildStatisticsChart(Long appId, Long profileId); + + /** + * 获取应用发布统计 视图 + * + * @param appId appId + * @param profileId profileId + * @return 视图 + */ + ApplicationReleaseStatisticsViewVO appReleaseStatisticView(Long appId, Long profileId); + + /** + * 获取应用发布统计 指标 + * + * @param appId appId + * @param profileId profileId + * @return 指标 + */ + ApplicationReleaseStatisticsMetricsWrapperVO appReleaseStatisticMetrics(Long appId, Long profileId); + + /** + * 获取应用发布统计 折线图 + * + * @param appId appId + * @param profileId profileId + * @return 折线图 + */ + List appReleaseStatisticChart(Long appId, Long profileId); + + /** + * 获取应用发布统计 视图 + * + * @param pipelineId pipelineId + * @return 视图 + */ + ApplicationPipelineTaskStatisticsViewVO appPipelineTaskStatisticView(Long pipelineId); + + /** + * 获取应用发布统计 指标 + * + * @param pipelineId pipelineId + * @return 指标 + */ + ApplicationPipelineTaskStatisticsMetricsWrapperVO appPipelineTaskStatisticMetrics(Long pipelineId); + + /** + * 获取应用发布统计 折线图 + * + * @param pipelineId pipelineId + * @return 折线图 + */ + List appPipelineTaskStatisticChart(Long pipelineId); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SystemEnvService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SystemEnvService.java new file mode 100644 index 0000000..70c8ae7 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SystemEnvService.java @@ -0,0 +1,101 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.collect.MutableLinkedHashMap; +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.entity.domain.SystemEnvDO; +import cn.orionsec.ops.entity.request.system.SystemEnvRequest; +import cn.orionsec.ops.entity.vo.system.SystemEnvVO; + +import java.util.List; +import java.util.Map; + +public interface SystemEnvService { + + /** + * 添加变量 + * + * @param request request + * @return id + */ + Long addEnv(SystemEnvRequest request); + + /** + * 修改变量 + * + * @param request request + * @return effect + */ + Integer updateEnv(SystemEnvRequest request); + + /** + * 修改变量 + * + * @param before before + * @param request request + * @return effect + */ + Integer updateEnv(SystemEnvDO before, SystemEnvRequest request); + + /** + * 通过id删除 + * + * @param idList idList + * @return effect + */ + Integer deleteEnv(List idList); + + /** + * 批量添加 + * + * @param env env + */ + void saveEnv(Map env); + + /** + * 列表 + * + * @param request request + * @return rows + */ + DataGrid listEnv(SystemEnvRequest request); + + /** + * 详情 + * + * @param id id + * @return row + */ + SystemEnvVO getEnvDetail(Long id); + + /** + * 获取系统变量 + * + * @param env envKey + * @return env + */ + String getEnvValue(String env); + + /** + * 通过名称获取 + * + * @param env env + * @return env + */ + SystemEnvDO selectByName(String env); + + /** + * 获取系统环境变量 + * + * @return map + */ + MutableLinkedHashMap getSystemEnv(); + + /** + * 获取系统环境变量 + * + * @return map + */ + MutableLinkedHashMap getFullSystemEnv(); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SystemService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SystemService.java new file mode 100644 index 0000000..a50dea3 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/SystemService.java @@ -0,0 +1,72 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.ops.constant.system.SystemCleanType; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.entity.request.system.ConfigIpListRequest; +import cn.orionsec.ops.entity.vo.system.IpListConfigVO; +import cn.orionsec.ops.entity.vo.system.SystemAnalysisVO; +import cn.orionsec.ops.entity.vo.system.SystemOptionVO; +import cn.orionsec.ops.entity.vo.system.ThreadPoolMetricsVO; + +import java.util.List; + +public interface SystemService { + + /** + * 获取 ip 配置 + * + * @param ip ip + * @return ip 信息 + */ + IpListConfigVO getIpInfo(String ip); + + /** + * 配置 ip 过滤器列表 + * + * @param request request + */ + void configIpFilterList(ConfigIpListRequest request); + + /** + * 清理系统文件 + * + * @param cleanType 文件类型 + */ + void cleanSystemFile(SystemCleanType cleanType); + + /** + * 分析磁盘占用空间 + */ + void analysisSystemSpace(); + + /** + * 获取系统分析信息 + * + * @return 系统分析信息 + */ + SystemAnalysisVO getSystemAnalysis(); + + /** + * 更新系统配置 + * + * @param env env + * @param value value + */ + void updateSystemOption(SystemEnvAttr env, String value); + + /** + * 获取系统配置项 + * + * @return 配置项 + */ + SystemOptionVO getSystemOptions(); + + /** + * 获取线程池指标 + * + * @return 指标 + */ + List getThreadPoolMetrics(); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/UserEventLogService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/UserEventLogService.java new file mode 100644 index 0000000..cc458d1 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/UserEventLogService.java @@ -0,0 +1,27 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.entity.request.user.EventLogRequest; +import cn.orionsec.ops.entity.vo.user.UserEventLogVO; + +public interface UserEventLogService { + + /** + * 记录日志 + * + * @param eventType 操作 + * @param isSuccess 是否成功 + */ + void recordLog(EventType eventType, boolean isSuccess); + + /** + * 获取操作日志 + * + * @param request request + * @return rows + */ + DataGrid getLogList(EventLogRequest request); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/UserService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/UserService.java new file mode 100644 index 0000000..655e027 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/UserService.java @@ -0,0 +1,85 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.entity.request.user.UserInfoRequest; +import cn.orionsec.ops.entity.vo.user.UserInfoVO; + +public interface UserService { + + /** + * 查询用户列表 + * + * @param request request + * @return rows + */ + DataGrid userList(UserInfoRequest request); + + /** + * 查询用户详情 + * + * @param request request + * @return row + */ + UserInfoVO userDetail(UserInfoRequest request); + + /** + * 添加用户 + * + * @param request request + * @return userId + */ + Long addUser(UserInfoRequest request); + + /** + * 更新用户 + * + * @param request request + * @return effect + */ + Integer updateUser(UserInfoRequest request); + + /** + * 删除用户 + * + * @param id id + * @return effect + */ + Integer deleteUser(Long id); + + /** + * 更新用户状态 + * + * @param id id + * @param status status + * @return effect + */ + Integer updateStatus(Long id, Integer status); + + /** + * 解锁用户 + * + * @param id id + * @return effect + */ + Integer unlockUser(Long id); + + /** + * 更新头像 + * + * @param avatar base64 + * @return effect + */ + Integer updateAvatar(String avatar); + + /** + * 自动生成管理员账号 + */ + void generatorDefaultAdminUser(); + + /** + * 重置默认管理员密码 + */ + void resetDefaultAdminUserPassword(); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/WebSideMessageService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/WebSideMessageService.java new file mode 100644 index 0000000..6c12e1a --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/WebSideMessageService.java @@ -0,0 +1,112 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.constant.message.MessageType; +import cn.orionsec.ops.entity.request.message.WebSideMessageRequest; +import cn.orionsec.ops.entity.vo.message.WebSideMessagePollVO; +import cn.orionsec.ops.entity.vo.message.WebSideMessageVO; + +import java.util.List; +import java.util.Map; + +public interface WebSideMessageService { + + /** + * 获取未读数量 + * + * @return 未读数量 + */ + Integer getUnreadCount(); + + /** + * 设置全部已读 + * + * @return effect + */ + Integer setAllRead(); + + /** + * 已读站内信 + * + * @param idList idList + * @return effect + */ + Integer readMessage(List idList); + + /** + * 删除全部已读站内信 + * + * @return effect + */ + Integer deleteAllRead(); + + /** + * 删除站内信 + * + * @param idList idList + * @return effect + */ + Integer deleteMessage(List idList); + + /** + * 站内信详情 + * + * @param id id + * @return message + */ + WebSideMessageVO getMessageDetail(Long id); + + /** + * 站内信列表 + * + * @param request request + * @return rows + */ + DataGrid getMessageList(WebSideMessageRequest request); + + /** + * 获取最新消息 + * + * @param request request + * @return message + */ + WebSideMessagePollVO getNewMessage(WebSideMessageRequest request); + + /** + * 获取更多消息 + * + * @param request request + * @return message + */ + List getMoreMessage(WebSideMessageRequest request); + + /** + * 轮询站内信 + * + * @param maxId maxId + * @return 轮询返回 + */ + WebSideMessagePollVO pollWebSideMessage(Long maxId); + + /** + * 添加站内信 给当前用户 + * + * @param type type + * @param relId relId + * @param params 参数 + */ + void addMessage(MessageType type, Long relId, Map params); + + /** + * 添加站内信 + * + * @param type type + * @param relId relId + * @param userId 收信人 userId + * @param username 收信人 username + * @param params 参数 + */ + void addMessage(MessageType type, Long relId, Long userId, String username, Map params); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/WebhookConfigService.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/WebhookConfigService.java new file mode 100644 index 0000000..63eded5 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/api/WebhookConfigService.java @@ -0,0 +1,50 @@ + +package cn.orionsec.ops.service.api; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.entity.request.webhook.WebhookConfigRequest; +import cn.orionsec.ops.entity.vo.webhook.WebhookConfigVO; + +public interface WebhookConfigService { + + /** + * 查询列表 + * + * @param request request + * @return rows + */ + DataGrid getWebhookList(WebhookConfigRequest request); + + /** + * 获取详情 + * + * @param id id + * @return row + */ + WebhookConfigVO getWebhookDetail(Long id); + + /** + * 添加 + * + * @param request request + * @return id + */ + Long addWebhook(WebhookConfigRequest request); + + /** + * 更新 + * + * @param request request + * @return effect + */ + Integer updateWebhook(WebhookConfigRequest request); + + /** + * 通过 id 删除 + * + * @param id id + * @return effect + */ + Integer deleteWebhook(Long id); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/AlarmGroupNotifyServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/AlarmGroupNotifyServiceImpl.java new file mode 100644 index 0000000..6ee2a05 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/AlarmGroupNotifyServiceImpl.java @@ -0,0 +1,49 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.ops.constant.alarm.AlarmGroupNotifyType; +import cn.orionsec.ops.dao.AlarmGroupNotifyDAO; +import cn.orionsec.ops.entity.domain.AlarmGroupNotifyDO; +import cn.orionsec.ops.service.api.AlarmGroupNotifyService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; + +@Service("alarmGroupNotifyService") +public class AlarmGroupNotifyServiceImpl implements AlarmGroupNotifyService { + + @Resource + private AlarmGroupNotifyDAO alarmGroupNotifyDAO; + + @Override + public List selectByGroupId(Long groupId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(AlarmGroupNotifyDO::getGroupId, groupId); + return alarmGroupNotifyDAO.selectList(wrapper); + } + + @Override + public List selectByGroupIdList(List groupIdList) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .in(AlarmGroupNotifyDO::getGroupId, groupIdList); + return alarmGroupNotifyDAO.selectList(wrapper); + } + + @Override + public Integer deleteByGroupId(Long groupId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(AlarmGroupNotifyDO::getGroupId, groupId); + return alarmGroupNotifyDAO.delete(wrapper); + } + + @Override + public Integer deleteByWebhookId(Long webhookId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(AlarmGroupNotifyDO::getNotifyId, webhookId) + .eq(AlarmGroupNotifyDO::getNotifyType, AlarmGroupNotifyType.WEBHOOK.getType()); + return alarmGroupNotifyDAO.delete(wrapper); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/AlarmGroupServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/AlarmGroupServiceImpl.java new file mode 100644 index 0000000..cd0d93f --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/AlarmGroupServiceImpl.java @@ -0,0 +1,242 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.alarm.AlarmGroupNotifyType; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.dao.*; +import cn.orionsec.ops.entity.domain.*; +import cn.orionsec.ops.entity.request.alarm.AlarmGroupRequest; +import cn.orionsec.ops.entity.vo.alarm.AlarmGroupUserVO; +import cn.orionsec.ops.entity.vo.alarm.AlarmGroupVO; +import cn.orionsec.ops.service.api.AlarmGroupNotifyService; +import cn.orionsec.ops.service.api.AlarmGroupService; +import cn.orionsec.ops.service.api.AlarmGroupUserService; +import cn.orionsec.ops.service.api.MachineAlarmGroupService; +import cn.orionsec.ops.utils.DataQuery; +import cn.orionsec.ops.utils.EventParamsHolder; +import cn.orionsec.ops.utils.Valid; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service("alarmGroupService") +public class AlarmGroupServiceImpl implements AlarmGroupService { + + @Resource + private AlarmGroupDAO alarmGroupDAO; + + @Resource + private AlarmGroupUserDAO alarmGroupUserDAO; + + @Resource + private AlarmGroupNotifyDAO alarmGroupNotifyDAO; + + @Resource + private UserInfoDAO userInfoDAO; + + @Resource + private WebhookConfigDAO webhookConfigDAO; + + @Resource + private AlarmGroupUserService alarmGroupUserService; + + @Resource + private AlarmGroupNotifyService alarmGroupNotifyService; + + @Resource + private MachineAlarmGroupService machineAlarmGroupService; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long addAlarmGroup(AlarmGroupRequest request) { + String name = request.getName(); + List userIdList = request.getUserIdList(); + List webhookIdList = request.getNotifyIdList(); + // 检查名称是否存在 + this.checkNamePresent(null, name); + // 查询组员 + List users = userInfoDAO.selectBatchIds(userIdList); + Valid.eq(users.size(), userIdList.size(), MessageConst.UNKNOWN_USER); + // 查询通知方式 + List webhooks = webhookConfigDAO.selectBatchIds(webhookIdList); + Valid.eq(webhooks.size(), webhookIdList.size(), MessageConst.WEBHOOK_ABSENT); + // 插入组表 + AlarmGroupDO group = new AlarmGroupDO(); + group.setGroupName(name); + group.setGroupDescription(request.getDescription()); + alarmGroupDAO.insert(group); + Long groupId = group.getId(); + // 插入组员表 + users.stream().map(u -> { + AlarmGroupUserDO groupUser = new AlarmGroupUserDO(); + groupUser.setGroupId(groupId); + groupUser.setUserId(u.getId()); + groupUser.setUsername(u.getUsername()); + return groupUser; + }).forEach(alarmGroupUserDAO::insert); + // 插入通知方式 + webhookIdList.stream().map(wid -> { + AlarmGroupNotifyDO groupNotify = new AlarmGroupNotifyDO(); + groupNotify.setGroupId(groupId); + groupNotify.setNotifyId(wid); + groupNotify.setNotifyType(AlarmGroupNotifyType.WEBHOOK.getType()); + return groupNotify; + }).forEach(alarmGroupNotifyDAO::insert); + // 设置日志参数 + EventParamsHolder.addParams(request); + return groupId; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer updateAlarmGroup(AlarmGroupRequest request) { + Long id = request.getId(); + String name = request.getName(); + List userIdList = request.getUserIdList(); + List webhookIdList = request.getNotifyIdList(); + // 查询 + AlarmGroupDO group = alarmGroupDAO.selectById(id); + Valid.notNull(group, MessageConst.ALARM_GROUP_ABSENT); + // 检查名称是否存在 + this.checkNamePresent(id, name); + // 查询组员 + List users = userInfoDAO.selectBatchIds(userIdList); + Valid.eq(users.size(), userIdList.size(), MessageConst.UNKNOWN_USER); + // 查询通知方式 + List webhooks = webhookConfigDAO.selectBatchIds(webhookIdList); + Valid.eq(webhooks.size(), webhookIdList.size(), MessageConst.WEBHOOK_ABSENT); + // 修改组表 + AlarmGroupDO update = new AlarmGroupDO(); + update.setId(id); + update.setGroupName(name); + update.setGroupDescription(request.getDescription()); + int effect = alarmGroupDAO.updateById(update); + // 重新插入组员表 + effect += alarmGroupUserService.deleteByGroupId(id); + users.stream().map(u -> { + AlarmGroupUserDO groupUser = new AlarmGroupUserDO(); + groupUser.setGroupId(id); + groupUser.setUserId(u.getId()); + groupUser.setUsername(u.getUsername()); + return groupUser; + }).forEach(alarmGroupUserDAO::insert); + // 重新插入通知方式 + effect += alarmGroupNotifyService.deleteByGroupId(id); + webhookIdList.stream().map(wid -> { + AlarmGroupNotifyDO groupNotify = new AlarmGroupNotifyDO(); + groupNotify.setGroupId(id); + groupNotify.setNotifyId(wid); + groupNotify.setNotifyType(AlarmGroupNotifyType.WEBHOOK.getType()); + return groupNotify; + }).forEach(alarmGroupNotifyDAO::insert); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.BEFORE, group.getGroupName()); + EventParamsHolder.addParams(request); + return effect; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer deleteAlarmGroup(Long id) { + // 查询 + AlarmGroupDO group = alarmGroupDAO.selectById(id); + Valid.notNull(group, MessageConst.ALARM_GROUP_ABSENT); + // 删除组 + int effect = alarmGroupDAO.deleteById(id); + // 删除组员 + effect += alarmGroupUserService.deleteByGroupId(id); + // 删除通知方式 + effect += alarmGroupNotifyService.deleteByGroupId(id); + // 删除机器报警配置组 + effect += machineAlarmGroupService.deleteByGroupId(id); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.NAME, group.getGroupName()); + return effect; + } + + @Override + public DataGrid getAlarmGroupList(AlarmGroupRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .like(Strings.isNotBlank(request.getName()), AlarmGroupDO::getGroupName, request.getName()) + .like(Strings.isNotBlank(request.getDescription()), AlarmGroupDO::getGroupDescription, request.getDescription()); + // 查询列表 + DataGrid dataGrid = DataQuery.of(alarmGroupDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(AlarmGroupVO.class); + if (dataGrid.isEmpty()) { + return dataGrid; + } + List groupIdList = dataGrid.stream() + .map(AlarmGroupVO::getId) + .collect(Collectors.toList()); + // 查询组员信息 + List groupUsers = alarmGroupUserService.selectByGroupIdList(groupIdList); + if (groupUsers.isEmpty()) { + return dataGrid; + } + // 查询用户信息 + List userIdList = groupUsers.stream() + .map(AlarmGroupUserDO::getUserId) + .distinct() + .collect(Collectors.toList()); + Map userNickNameMap = userInfoDAO.selectBatchIds(userIdList).stream() + .collect(Collectors.toMap(UserInfoDO::getId, UserInfoDO::getNickname)); + // 组合用户信息 + dataGrid.forEach(row -> { + List groupUserList = groupUsers.stream() + .filter(s -> s.getGroupId().equals(row.getId())) + .map(s -> Converts.to(s, AlarmGroupUserVO.class)) + .peek(s -> s.setNickname(userNickNameMap.get(s.getUserId()))) + .collect(Collectors.toList()); + row.setGroupUsers(groupUserList); + }); + return dataGrid; + } + + @Override + public AlarmGroupVO getAlarmGroupDetail(Long id) { + // 查询组 + AlarmGroupDO alarmGroup = alarmGroupDAO.selectById(id); + Valid.notNull(alarmGroup, MessageConst.ALARM_GROUP_ABSENT); + AlarmGroupVO group = Converts.to(alarmGroup, AlarmGroupVO.class); + // 查询组员 + List groupUsers = alarmGroupUserService.selectByGroupId(id); + List userIdList = groupUsers.stream() + .map(AlarmGroupUserDO::getUserId) + .collect(Collectors.toList()); + group.setUserIdList(userIdList); + // 查询通知方式 + List groupNotifies = alarmGroupNotifyService.selectByGroupId(id); + List notifyIdList = groupNotifies.stream() + .map(AlarmGroupNotifyDO::getNotifyId) + .collect(Collectors.toList()); + group.setNotifyIdList(notifyIdList); + return group; + } + + /** + * 检查是否存在 + * + * @param id id + * @param name name + */ + private void checkNamePresent(Long id, String name) { + LambdaQueryWrapper presentWrapper = new LambdaQueryWrapper() + .ne(id != null, AlarmGroupDO::getId, id) + .eq(AlarmGroupDO::getGroupName, name); + boolean present = DataQuery.of(alarmGroupDAO).wrapper(presentWrapper).present(); + Valid.isTrue(!present, MessageConst.NAME_PRESENT); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/AlarmGroupUserServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/AlarmGroupUserServiceImpl.java new file mode 100644 index 0000000..e7a885d --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/AlarmGroupUserServiceImpl.java @@ -0,0 +1,47 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.ops.dao.AlarmGroupUserDAO; +import cn.orionsec.ops.entity.domain.AlarmGroupUserDO; +import cn.orionsec.ops.service.api.AlarmGroupUserService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; + +@Service("alarmGroupUserService") +public class AlarmGroupUserServiceImpl implements AlarmGroupUserService { + + @Resource + private AlarmGroupUserDAO alarmGroupUserDAO; + + @Override + public List selectByGroupId(Long groupId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(AlarmGroupUserDO::getGroupId, groupId); + return alarmGroupUserDAO.selectList(wrapper); + } + + @Override + public List selectByGroupIdList(List groupIdList) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .in(AlarmGroupUserDO::getGroupId, groupIdList); + return alarmGroupUserDAO.selectList(wrapper); + } + + @Override + public Integer deleteByGroupId(Long groupId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(AlarmGroupUserDO::getGroupId, groupId); + return alarmGroupUserDAO.delete(wrapper); + } + + @Override + public Integer deleteByUserId(Long userId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(AlarmGroupUserDO::getUserId, userId); + return alarmGroupUserDAO.delete(wrapper); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationActionLogServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationActionLogServiceImpl.java new file mode 100644 index 0000000..5ff4595 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationActionLogServiceImpl.java @@ -0,0 +1,124 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.ops.constant.app.ActionStatus; +import cn.orionsec.ops.constant.app.StageType; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.dao.ApplicationActionLogDAO; +import cn.orionsec.ops.entity.domain.ApplicationActionLogDO; +import cn.orionsec.ops.entity.vo.app.ApplicationActionLogVO; +import cn.orionsec.ops.service.api.ApplicationActionLogService; +import cn.orionsec.ops.utils.DataQuery; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.List; +import java.util.Optional; + +@Service("applicationActionLogService") +public class ApplicationActionLogServiceImpl implements ApplicationActionLogService { + + @Resource + private ApplicationActionLogDAO applicationActionLogDAO; + + @Override + public ApplicationActionLogVO getDetailById(Long id) { + ApplicationActionLogDO log = applicationActionLogDAO.selectById(id); + return Converts.to(log, ApplicationActionLogVO.class); + } + + @Override + public ApplicationActionLogVO getStatusById(Long id) { + ApplicationActionLogDO log = applicationActionLogDAO.selectStatusInfoById(id); + return Converts.to(log, ApplicationActionLogVO.class); + } + + @Override + public List getActionLogsByRelId(Long relId, StageType stageType) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationActionLogDO::getRelId, relId) + .eq(ApplicationActionLogDO::getStageType, stageType.getType()) + .orderByAsc(ApplicationActionLogDO::getId); + return DataQuery.of(applicationActionLogDAO) + .wrapper(wrapper) + .list(ApplicationActionLogVO.class); + } + + @Override + public Integer deleteByRelId(Long relId, StageType stageType) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationActionLogDO::getRelId, relId) + .eq(ApplicationActionLogDO::getStageType, stageType.getType()); + return applicationActionLogDAO.delete(wrapper); + } + + @Override + public Integer deleteByRelIdList(List relIdList, StageType stageType) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .in(ApplicationActionLogDO::getRelId, relIdList) + .eq(ApplicationActionLogDO::getStageType, stageType.getType()); + return applicationActionLogDAO.delete(wrapper); + } + + @Override + public List selectActionByRelId(Long relId, StageType stageType) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationActionLogDO::getRelId, relId) + .eq(ApplicationActionLogDO::getStageType, stageType.getType()) + .orderByAsc(ApplicationActionLogDO::getId); + return applicationActionLogDAO.selectList(wrapper); + } + + @Override + public List selectActionByRelIdList(List relIdList, StageType stageType) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .in(ApplicationActionLogDO::getRelId, relIdList) + .eq(ApplicationActionLogDO::getStageType, stageType.getType()) + .orderByAsc(ApplicationActionLogDO::getId); + return applicationActionLogDAO.selectList(wrapper); + } + + @Override + public void updateActionById(ApplicationActionLogDO record) { + if (record.getUpdateTime() == null) { + record.setUpdateTime(new Date()); + } + applicationActionLogDAO.updateById(record); + } + + @Override + public String getActionLogPath(Long id) { + return Optional.ofNullable(applicationActionLogDAO.selectById(id)) + .map(ApplicationActionLogDO::getLogPath) + .filter(Strings::isNotBlank) + .map(s -> Files1.getPath(SystemEnvAttr.LOG_PATH.getValue(), s)) + .orElse(null); + } + + @Override + public void resetActionStatus(Long relId, StageType stageType) { + // 查询action + List actions = this.selectActionByRelId(relId, stageType); + // 修改状态 + for (ApplicationActionLogDO action : actions) { + ApplicationActionLogDO update = new ApplicationActionLogDO(); + update.setId(action.getId()); + update.setUpdateTime(new Date()); + switch (ActionStatus.of(action.getRunStatus())) { + case WAIT: + case RUNNABLE: + update.setRunStatus(ActionStatus.TERMINATED.getStatus()); + break; + default: + break; + } + applicationActionLogDAO.updateById(update); + } + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationActionServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationActionServiceImpl.java new file mode 100644 index 0000000..26e9f85 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationActionServiceImpl.java @@ -0,0 +1,144 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.ops.constant.app.StageType; +import cn.orionsec.ops.dao.ApplicationActionDAO; +import cn.orionsec.ops.entity.domain.ApplicationActionDO; +import cn.orionsec.ops.entity.dto.ApplicationActionConfigDTO; +import cn.orionsec.ops.entity.request.app.ApplicationConfigActionRequest; +import cn.orionsec.ops.entity.request.app.ApplicationConfigRequest; +import cn.orionsec.ops.service.api.ApplicationActionService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; + +@Service("applicationActionService") +public class ApplicationActionServiceImpl implements ApplicationActionService { + + @Resource + private ApplicationActionDAO applicationActionDAO; + + @Override + public Integer deleteAppActionByAppProfileId(Long appId, Long profileId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(appId != null, ApplicationActionDO::getAppId, appId) + .eq(profileId != null, ApplicationActionDO::getProfileId, profileId); + return applicationActionDAO.delete(wrapper); + } + + @Override + public List getAppProfileActions(Long appId, Long profileId, Integer stageType) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationActionDO::getAppId, appId) + .eq(ApplicationActionDO::getProfileId, profileId) + .eq(stageType != null, ApplicationActionDO::getStageType, stageType) + .orderByAsc(ApplicationActionDO::getId); + return applicationActionDAO.selectList(wrapper); + } + + @Override + public Integer getAppProfileActionCount(Long appId, Long profileId, Integer stageType) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationActionDO::getAppId, appId) + .eq(ApplicationActionDO::getProfileId, profileId) + .eq(stageType != null, ApplicationActionDO::getStageType, stageType); + return applicationActionDAO.selectCount(wrapper); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void configAppAction(ApplicationConfigRequest request) { + Long appId = request.getAppId(); + Long profileId = request.getProfileId(); + StageType stageType = StageType.of(request.getStageType()); + // 删除 + LambdaQueryWrapper deleteWrapper = new LambdaQueryWrapper() + .eq(ApplicationActionDO::getAppId, appId) + .eq(ApplicationActionDO::getProfileId, profileId) + .eq(ApplicationActionDO::getStageType, stageType.getType()); + applicationActionDAO.delete(deleteWrapper); + // 插入 + List actions; + if (StageType.BUILD.equals(stageType)) { + actions = request.getBuildActions(); + } else if (StageType.RELEASE.equals(stageType)) { + actions = request.getReleaseActions(); + } else { + throw Exceptions.unsupported(); + } + for (ApplicationConfigActionRequest action : actions) { + ApplicationActionDO insert = new ApplicationActionDO(); + insert.setAppId(appId); + insert.setProfileId(profileId); + insert.setActionName(action.getName()); + insert.setActionType(action.getType()); + insert.setStageType(stageType.getType()); + insert.setActionCommand(action.getCommand()); + applicationActionDAO.insert(insert); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void syncAppProfileAction(Long appId, Long sourceProfileId, Long targetProfileId) { + // 删除 + LambdaQueryWrapper deleteWrapper = new LambdaQueryWrapper() + .eq(ApplicationActionDO::getAppId, appId) + .eq(ApplicationActionDO::getProfileId, targetProfileId); + applicationActionDAO.delete(deleteWrapper); + // 查询 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() + .eq(ApplicationActionDO::getAppId, appId) + .eq(ApplicationActionDO::getProfileId, sourceProfileId); + List actions = applicationActionDAO.selectList(queryWrapper); + // 新增 + for (ApplicationActionDO action : actions) { + action.setId(null); + action.setCreateTime(null); + action.setUpdateTime(null); + action.setProfileId(targetProfileId); + applicationActionDAO.insert(action); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void copyAppAction(Long appId, Long targetAppId) { + // 查询 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() + .eq(ApplicationActionDO::getAppId, appId); + List actions = applicationActionDAO.selectList(queryWrapper); + // 插入 + for (ApplicationActionDO action : actions) { + action.setId(null); + action.setAppId(targetAppId); + action.setCreateTime(null); + action.setUpdateTime(null); + applicationActionDAO.insert(action); + } + } + + @Override + public Map getAppIsConfig(Long profileId, List appIdList) { + // 查询 + List configList = applicationActionDAO.getAppIsConfig(profileId, appIdList); + // 设置结果 + Map result = Maps.newMap(); + for (Long appId : appIdList) { + Boolean isConfig = configList.stream() + .filter(s -> appId.equals(s.getAppId())) + .findFirst() + .map(s -> s.getBuildStageCount() > 0 && s.getReleaseStageCount() > 0) + .orElse(false); + result.put(appId, isConfig); + } + return result; + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationBuildServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationBuildServiceImpl.java new file mode 100644 index 0000000..b277121 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationBuildServiceImpl.java @@ -0,0 +1,370 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.collect.MutableLinkedHashMap; +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.app.*; +import cn.orionsec.ops.constant.env.EnvConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.dao.*; +import cn.orionsec.ops.entity.domain.*; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.entity.request.app.ApplicationBuildRequest; +import cn.orionsec.ops.entity.vo.app.*; +import cn.orionsec.ops.handler.app.build.BuildSessionHolder; +import cn.orionsec.ops.handler.app.machine.BuildMachineProcessor; +import cn.orionsec.ops.handler.app.machine.IMachineProcessor; +import cn.orionsec.ops.service.api.*; +import cn.orionsec.ops.utils.*; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +@Slf4j +@Service("applicationBuildService") +public class ApplicationBuildServiceImpl implements ApplicationBuildService { + + @Resource + private ApplicationBuildDAO applicationBuildDAO; + + @Resource + private ApplicationActionLogDAO applicationActionLogDAO; + + @Resource + private ApplicationInfoDAO applicationInfoDAO; + + @Resource + private ApplicationProfileDAO applicationProfileDAO; + + @Resource + private ApplicationRepositoryDAO applicationRepositoryDAO; + + @Resource + private ApplicationEnvService applicationEnvService; + + @Resource + private ApplicationActionService applicationActionService; + + @Resource + private ApplicationActionLogService applicationActionLogService; + + @Resource + private MachineEnvService machineEnvService; + + @Resource + private SystemEnvService systemEnvService; + + @Resource + private BuildSessionHolder buildSessionHolder; + + @Override + public Long submitBuildTask(ApplicationBuildRequest request, boolean execute) { + Long appId = request.getAppId(); + Long profileId = request.getProfileId(); + // 查询应用 + ApplicationInfoDO app = applicationInfoDAO.selectById(appId); + Valid.notNull(app, MessageConst.APP_ABSENT); + // 查询环境 + ApplicationProfileDO profile = applicationProfileDAO.selectById(profileId); + Valid.notNull(profile, MessageConst.PROFILE_ABSENT); + // 查询应用执行块 + List actions = applicationActionService.getAppProfileActions(appId, profileId, StageType.BUILD.getType()); + Valid.notEmpty(actions, MessageConst.APP_PROFILE_NOT_CONFIGURED); + UserDTO user = Currents.getUser(); + // 获取构建序列 + Integer buildSeq = applicationEnvService.getBuildSeqAndIncrement(appId, profileId); + // 设置构建参数 + ApplicationBuildDO buildTask = new ApplicationBuildDO(); + buildTask.setAppId(appId); + buildTask.setAppName(app.getAppName()); + buildTask.setAppTag(app.getAppTag()); + buildTask.setProfileId(profileId); + buildTask.setProfileName(profile.getProfileName()); + buildTask.setProfileTag(profile.getProfileTag()); + buildTask.setBuildSeq(buildSeq); + buildTask.setBranchName(request.getBranchName()); + buildTask.setCommitId(request.getCommitId()); + buildTask.setRepoId(app.getRepoId()); + buildTask.setBuildStatus(BuildStatus.WAIT.getStatus()); + buildTask.setDescription(request.getDescription()); + buildTask.setCreateUserId(user.getId()); + buildTask.setCreateUserName(user.getUsername()); + applicationBuildDAO.insert(buildTask); + Long buildId = buildTask.getId(); + // 设置目录信息 + buildTask.setLogPath(PathBuilders.getBuildLogPath(buildId)); + String bundlePathEnv = applicationEnvService.getAppEnvValue(appId, profileId, ApplicationEnvAttr.BUNDLE_PATH.getKey()); + String bundlePathReal = PathBuilders.getBuildBundlePath(buildId) + "/" + Files1.getFileName(bundlePathEnv); + buildTask.setBundlePath(bundlePathReal); + // 更新构建信息 + applicationBuildDAO.updateById(buildTask); + // 检查是否包含环境变量命令 + final boolean hasEnvCommand = actions.stream() + .filter(s -> ActionType.BUILD_COMMAND.getType().equals(s.getActionType())) + .map(ApplicationActionDO::getActionCommand) + .filter(Strings::isNotBlank) + .anyMatch(s -> s.contains(EnvConst.SYMBOL)); + + Map env = Maps.newMap(); + if (hasEnvCommand) { + // 查询应用环境变量 + env.putAll(applicationEnvService.getAppProfileFullEnv(appId, profileId)); + // 查询机器环境变量 + env.putAll(machineEnvService.getFullMachineEnv(Const.HOST_MACHINE_ID)); + // 查询系统环境变量 + env.putAll(systemEnvService.getFullSystemEnv()); + // 添加构建环境变量 + env.putAll(this.getBuildEnv(buildId, buildSeq, app.getRepoId(), bundlePathReal, request)); + } + // 设置action + for (ApplicationActionDO action : actions) { + ApplicationActionLogDO actionLog = new ApplicationActionLogDO(); + actionLog.setRelId(buildId); + actionLog.setStageType(StageType.BUILD.getType()); + actionLog.setMachineId(Const.HOST_MACHINE_ID); + actionLog.setActionId(action.getAppId()); + actionLog.setActionName(action.getActionName()); + actionLog.setActionType(action.getActionType()); + if (ActionType.BUILD_COMMAND.equals(ActionType.of(action.getActionType()))) { + actionLog.setActionCommand(Strings.format(action.getActionCommand(), EnvConst.SYMBOL, env)); + } + actionLog.setRunStatus(ActionStatus.WAIT.getStatus()); + applicationActionLogDAO.insert(actionLog); + // 设置日志路径 + actionLog.setLogPath(PathBuilders.getBuildActionLogPath(buildId, actionLog.getId())); + // 更新 + applicationActionLogDAO.updateById(actionLog); + } + // 提交构建任务 + log.info("提交应用构建任务 buildId: {}", buildId); + if (execute) { + new BuildMachineProcessor(buildId).exec(); + } + // 设置日志参数 + EventParamsHolder.addParams(buildTask); + return buildId; + } + + @Override + public DataGrid getBuildList(ApplicationBuildRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationBuildDO::getProfileId, request.getProfileId()) + .eq(Objects.nonNull(request.getId()), ApplicationBuildDO::getId, request.getId()) + .eq(Objects.nonNull(request.getAppId()), ApplicationBuildDO::getAppId, request.getAppId()) + .eq(Objects.nonNull(request.getSeq()), ApplicationBuildDO::getBuildSeq, request.getSeq()) + .eq(Objects.nonNull(request.getStatus()), ApplicationBuildDO::getBuildStatus, request.getStatus()) + .eq(Const.ENABLE.equals(request.getOnlyMyself()), ApplicationBuildDO::getCreateUserId, Currents.getUserId()) + .like(Strings.isNotBlank(request.getAppName()), ApplicationBuildDO::getAppName, request.getAppName()) + .like(Strings.isNotBlank(request.getDescription()), ApplicationBuildDO::getDescription, request.getDescription()) + .orderByDesc(ApplicationBuildDO::getId); + // 查询列表 + DataGrid dataGrid = DataQuery.of(applicationBuildDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(ApplicationBuildVO.class); + // 查询版本信息 + Map repos = Maps.newMap(); + for (ApplicationBuildVO row : dataGrid) { + Long repoId = row.getRepoId(); + if (repoId == null) { + continue; + } + ApplicationRepositoryDO repo = repos.computeIfAbsent(repoId, i -> applicationRepositoryDAO.selectById(repoId)); + if (repo != null) { + row.setRepoName(repo.getRepoName()); + } + } + return dataGrid; + } + + @Override + public ApplicationBuildVO getBuildDetail(Long id) { + ApplicationBuildDO build = applicationBuildDAO.selectById(id); + Valid.notNull(build, MessageConst.UNKNOWN_DATA); + ApplicationBuildVO detail = Converts.to(build, ApplicationBuildVO.class); + // 查询版本信息 + Optional.ofNullable(build.getRepoId()) + .map(applicationRepositoryDAO::selectById) + .ifPresent(v -> detail.setRepoName(v.getRepoName())); + // 查询action + List actions = applicationActionLogService.getActionLogsByRelId(id, StageType.BUILD); + detail.setActions(actions); + return detail; + } + + @Override + public ApplicationBuildStatusVO getBuildStatus(Long id) { + // 查询构建状态 + ApplicationBuildDO buildStatus = applicationBuildDAO.selectStatusInfoById(id); + Valid.notNull(buildStatus, MessageConst.UNKNOWN_DATA); + ApplicationBuildStatusVO status = Converts.to(buildStatus, ApplicationBuildStatusVO.class); + // 查询操作状态 + List actions = applicationActionLogDAO.selectStatusInfoByRelId(id, StageType.BUILD.getType()); + status.setActions(Converts.toList(actions, ApplicationActionStatusVO.class)); + return status; + } + + @Override + public List getBuildStatusList(List buildIdList) { + List buildList = applicationBuildDAO.selectStatusInfoByIdList(buildIdList); + return Converts.toList(buildList, ApplicationBuildStatusVO.class); + } + + @Override + public void terminateBuildTask(Long id) { + // 获取数据 + ApplicationBuildDO build = applicationBuildDAO.selectById(id); + Valid.notNull(build, MessageConst.UNKNOWN_DATA); + // 检查状态 + Valid.isTrue(BuildStatus.RUNNABLE.getStatus().equals(build.getBuildStatus()), MessageConst.ILLEGAL_STATUS); + // 获取实例 + IMachineProcessor session = buildSessionHolder.getSession(id); + Valid.notNull(session, MessageConst.SESSION_PRESENT); + // 调用终止 + session.terminate(); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.BUILD_SEQ, build.getBuildSeq()); + EventParamsHolder.addParam(EventKeys.APP_NAME, build.getAppName()); + EventParamsHolder.addParam(EventKeys.PROFILE_NAME, build.getProfileName()); + } + + @Override + public void writeBuildTask(Long id, String command) { + // 获取数据 + ApplicationBuildDO build = applicationBuildDAO.selectById(id); + Valid.notNull(build, MessageConst.UNKNOWN_DATA); + // 检查状态 + Valid.isTrue(BuildStatus.RUNNABLE.getStatus().equals(build.getBuildStatus()), MessageConst.ILLEGAL_STATUS); + // 获取实例 + IMachineProcessor session = buildSessionHolder.getSession(id); + Valid.notNull(session, MessageConst.SESSION_PRESENT); + // 写入命令 + session.write(command); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer deleteBuildTask(List idList) { + // 获取数据 + List buildList = applicationBuildDAO.selectBatchIds(idList); + Valid.notEmpty(buildList, MessageConst.UNKNOWN_DATA); + boolean canDelete = buildList.stream() + .map(ApplicationBuildDO::getBuildStatus) + .noneMatch(s -> BuildStatus.WAIT.getStatus().equals(s) || BuildStatus.RUNNABLE.getStatus().equals(s)); + Valid.isTrue(canDelete, MessageConst.ILLEGAL_STATUS); + // 删除主表 + int effect = applicationBuildDAO.deleteBatchIds(idList); + // 删除详情 + effect += applicationActionLogService.deleteByRelIdList(idList, StageType.BUILD); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID_LIST, idList); + EventParamsHolder.addParam(EventKeys.COUNT, idList.size()); + return effect; + } + + @Override + public Long rebuild(Long id) { + // 查询构建 + ApplicationBuildDO build = applicationBuildDAO.selectById(id); + Valid.notNull(build, MessageConst.UNKNOWN_DATA); + // 重新提交 + ApplicationBuildRequest request = new ApplicationBuildRequest(); + request.setAppId(build.getAppId()); + request.setProfileId(build.getProfileId()); + request.setBranchName(build.getBranchName()); + request.setCommitId(build.getCommitId()); + request.setDescription(build.getDescription()); + return this.submitBuildTask(request, true); + } + + @Override + public ApplicationBuildDO selectById(Long id) { + return applicationBuildDAO.selectById(id); + } + + @Override + public String getBuildLogPath(Long id) { + return Optional.ofNullable(applicationBuildDAO.selectById(id)) + .map(ApplicationBuildDO::getLogPath) + .filter(Strings::isNotBlank) + .map(s -> Files1.getPath(SystemEnvAttr.LOG_PATH.getValue(), s)) + .orElse(null); + } + + @Override + public String getBuildBundlePath(Long id) { + return Optional.ofNullable(applicationBuildDAO.selectById(id)) + .map(ApplicationBuildDO::getBundlePath) + .filter(Strings::isNotBlank) + .map(s -> Files1.getPath(SystemEnvAttr.DIST_PATH.getValue(), s)) + .orElse(null); + } + + @Override + public String checkBuildBundlePath(ApplicationBuildDO build) { + // 构建产物 + String buildBundlePath = build.getBundlePath(); + File bundleFile = new File(Files1.getPath(SystemEnvAttr.DIST_PATH.getValue(), buildBundlePath)); + Valid.isTrue(bundleFile.exists(), MessageConst.BUNDLE_FILE_ABSENT); + if (bundleFile.isFile()) { + return buildBundlePath; + } + // 如果是文件夹则需要检查传输文件是文件夹还是压缩文件 + String transferValueValue = applicationEnvService.getAppEnvValue(build.getAppId(), build.getProfileId(), ApplicationEnvAttr.TRANSFER_FILE_TYPE.getKey()); + if (TransferFileType.ZIP.equals(TransferFileType.of(transferValueValue))) { + buildBundlePath = build.getBundlePath() + "." + Const.SUFFIX_ZIP; + bundleFile = new File(Files1.getPath(SystemEnvAttr.DIST_PATH.getValue(), buildBundlePath)); + Valid.isTrue(bundleFile.exists(), MessageConst.BUNDLE_ZIP_FILE_ABSENT); + } + return buildBundlePath; + } + + @Override + public List getBuildReleaseList(Long appId, Long profileId) { + List list = applicationBuildDAO.selectBuildReleaseList(appId, profileId, Const.BUILD_RELEASE_LIMIT); + return Converts.toList(list, ApplicationBuildReleaseListVO.class); + } + + /** + * 获取构建环境变量 + * + * @param buildId buildId + * @param buildSeq buildSeq + * @param repoId repoId + * @param bundlePathReal bundlePathReal + * @param request request + * @return env + */ + private MutableLinkedHashMap getBuildEnv(Long buildId, Integer buildSeq, + Long repoId, String bundlePathReal, ApplicationBuildRequest request) { + // 设置变量 + MutableLinkedHashMap env = Maps.newMutableLinkedMap(); + env.put(EnvConst.BUILD_PREFIX + EnvConst.BUILD_ID, buildId + Strings.EMPTY); + env.put(EnvConst.BUILD_PREFIX + EnvConst.BUILD_SEQ, buildSeq + Strings.EMPTY); + env.put(EnvConst.BUILD_PREFIX + EnvConst.BRANCH, request.getBranchName()); + env.put(EnvConst.BUILD_PREFIX + EnvConst.COMMIT, request.getCommitId()); + env.put(EnvConst.BUILD_PREFIX + EnvConst.BUNDLE_PATH, Files1.getPath(SystemEnvAttr.DIST_PATH.getValue(), bundlePathReal)); + if (repoId != null) { + env.put(EnvConst.BUILD_PREFIX + EnvConst.REPO_HOME, Files1.getPath(SystemEnvAttr.REPO_PATH.getValue(), repoId + "/" + buildId)); + env.put(EnvConst.BUILD_PREFIX + EnvConst.REPO_EVENT_HOME, Utils.getRepositoryEventDir(repoId)); + } + return env; + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationEnvServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationEnvServiceImpl.java new file mode 100644 index 0000000..ed0fd54 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationEnvServiceImpl.java @@ -0,0 +1,432 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.collect.MutableLinkedHashMap; +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.app.ApplicationEnvAttr; +import cn.orionsec.ops.constant.app.StageType; +import cn.orionsec.ops.constant.app.TransferFileType; +import cn.orionsec.ops.constant.app.TransferMode; +import cn.orionsec.ops.constant.common.ExceptionHandlerType; +import cn.orionsec.ops.constant.common.SerialType; +import cn.orionsec.ops.constant.env.EnvConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.history.HistoryOperator; +import cn.orionsec.ops.constant.history.HistoryValueType; +import cn.orionsec.ops.dao.ApplicationEnvDAO; +import cn.orionsec.ops.dao.ApplicationInfoDAO; +import cn.orionsec.ops.dao.ApplicationProfileDAO; +import cn.orionsec.ops.entity.domain.ApplicationEnvDO; +import cn.orionsec.ops.entity.domain.ApplicationInfoDO; +import cn.orionsec.ops.entity.domain.ApplicationProfileDO; +import cn.orionsec.ops.entity.request.app.ApplicationConfigEnvRequest; +import cn.orionsec.ops.entity.request.app.ApplicationConfigRequest; +import cn.orionsec.ops.entity.request.app.ApplicationEnvRequest; +import cn.orionsec.ops.entity.vo.app.ApplicationEnvVO; +import cn.orionsec.ops.service.api.ApplicationEnvService; +import cn.orionsec.ops.service.api.HistoryValueService; +import cn.orionsec.ops.utils.DataQuery; +import cn.orionsec.ops.utils.EventParamsHolder; +import cn.orionsec.ops.utils.Valid; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@Service("applicationEnvService") +public class ApplicationEnvServiceImpl implements ApplicationEnvService { + + @Resource + private ApplicationInfoDAO applicationInfoDAO; + + @Resource + private ApplicationProfileDAO applicationProfileDAO; + + @Resource + private ApplicationEnvDAO applicationEnvDAO; + + @Resource + private HistoryValueService historyValueService; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long addAppEnv(ApplicationEnvRequest request) { + // 数据检查 + Long appId = request.getAppId(); + Long profileId = request.getProfileId(); + String key = request.getKey(); + Valid.notNull(applicationInfoDAO.selectById(appId), MessageConst.APP_ABSENT); + Valid.notNull(applicationProfileDAO.selectById(profileId), MessageConst.PROFILE_ABSENT); + // 重复检查 + ApplicationEnvDO env = applicationEnvDAO.selectOneRel(appId, profileId, key); + // 修改 + if (env != null) { + SpringHolder.getBean(ApplicationEnvService.class).updateAppEnv(env, request); + return env.getId(); + } + // 新增 + ApplicationEnvDO insert = new ApplicationEnvDO(); + insert.setAppId(appId); + insert.setProfileId(profileId); + insert.setAttrKey(key); + insert.setAttrValue(request.getValue()); + insert.setDescription(request.getDescription()); + applicationEnvDAO.insert(insert); + // 插入历史值 + Long id = insert.getId(); + historyValueService.addHistory(id, HistoryValueType.APP_ENV, HistoryOperator.ADD, null, request.getValue()); + return id; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer updateAppEnv(ApplicationEnvRequest request) { + // 查询 + ApplicationEnvDO before = applicationEnvDAO.selectById(request.getId()); + Valid.notNull(before, MessageConst.ENV_ABSENT); + return SpringHolder.getBean(ApplicationEnvService.class).updateAppEnv(before, request); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer updateAppEnv(ApplicationEnvDO before, ApplicationEnvRequest request) { + // 查询 + Long id = before.getId(); + String beforeValue = before.getAttrValue(); + String afterValue = request.getValue(); + if (Const.IS_DELETED.equals(before.getDeleted())) { + // 设置新增历史值 + historyValueService.addHistory(id, HistoryValueType.APP_ENV, HistoryOperator.ADD, null, afterValue); + // 恢复 + applicationEnvDAO.setDeleted(id, Const.NOT_DELETED); + } else if (afterValue != null && !afterValue.equals(beforeValue)) { + // 检查是否修改了值 增加历史值 + historyValueService.addHistory(id, HistoryValueType.APP_ENV, HistoryOperator.UPDATE, beforeValue, afterValue); + } + // 更新 + ApplicationEnvDO update = new ApplicationEnvDO(); + update.setId(id); + update.setAttrValue(afterValue); + update.setDescription(request.getDescription()); + update.setUpdateTime(new Date()); + return applicationEnvDAO.updateById(update); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer deleteAppEnv(List idList) { + int effect = 0; + for (Long id : idList) { + // 获取元数据 + ApplicationEnvDO env = applicationEnvDAO.selectById(id); + Valid.notNull(env, MessageConst.ENV_ABSENT); + String key = env.getAttrKey(); + Valid.isTrue(ApplicationEnvAttr.of(key) == null, "{} " + MessageConst.FORBID_DELETE, key); + // 删除 + effect += applicationEnvDAO.deleteById(id); + // 插入历史值 + historyValueService.addHistory(id, HistoryValueType.APP_ENV, HistoryOperator.DELETE, env.getAttrValue(), null); + } + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID_LIST, idList); + EventParamsHolder.addParam(EventKeys.COUNT, effect); + return effect; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveEnv(Long appId, Long profileId, Map env) { + ApplicationEnvService self = SpringHolder.getBean(ApplicationEnvService.class); + // 倒排 + List> entries = Lists.newList(env.entrySet()); + for (int i = entries.size() - 1; i >= 0; i--) { + // 更新 + Map.Entry entry = entries.get(i); + ApplicationEnvRequest request = new ApplicationEnvRequest(); + request.setAppId(appId); + request.setProfileId(profileId); + request.setKey(entry.getKey()); + request.setValue(entry.getValue()); + self.addAppEnv(request); + } + } + + @Override + public DataGrid listAppEnv(ApplicationEnvRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .like(Strings.isNotBlank(request.getKey()), ApplicationEnvDO::getAttrKey, request.getKey()) + .like(Strings.isNotBlank(request.getValue()), ApplicationEnvDO::getAttrValue, request.getValue()) + .like(Strings.isNotBlank(request.getDescription()), ApplicationEnvDO::getDescription, request.getDescription()) + .eq(ApplicationEnvDO::getAppId, request.getAppId()) + .eq(ApplicationEnvDO::getProfileId, request.getProfileId()) + .eq(ApplicationEnvDO::getSystemEnv, Const.NOT_SYSTEM) + .orderByDesc(ApplicationEnvDO::getUpdateTime); + return DataQuery.of(applicationEnvDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(ApplicationEnvVO.class); + } + + @Override + public ApplicationEnvVO getAppEnvDetail(Long id) { + ApplicationEnvDO env = applicationEnvDAO.selectById(id); + Valid.notNull(env, MessageConst.UNKNOWN_DATA); + return Converts.to(env, ApplicationEnvVO.class); + } + + @Override + public String getAppEnvValue(Long appId, Long profileId, String key) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationEnvDO::getAppId, appId) + .eq(ApplicationEnvDO::getProfileId, profileId) + .eq(ApplicationEnvDO::getAttrKey, key) + .last(Const.LIMIT_1); + return Optional.ofNullable(applicationEnvDAO.selectOne(wrapper)) + .map(ApplicationEnvDO::getAttrValue) + .orElse(null); + } + + @Override + public MutableLinkedHashMap getAppProfileEnv(Long appId, Long profileId) { + MutableLinkedHashMap env = Maps.newMutableLinkedMap(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationEnvDO::getAppId, appId) + .eq(ApplicationEnvDO::getProfileId, profileId) + .orderByAsc(ApplicationEnvDO::getId); + applicationEnvDAO.selectList(wrapper).forEach(e -> env.put(e.getAttrKey(), e.getAttrValue())); + return env; + } + + @Override + public MutableLinkedHashMap getAppProfileFullEnv(Long appId, Long profileId) { + // 查询应用环境 + ApplicationInfoDO app = Valid.notNull(applicationInfoDAO.selectById(appId), MessageConst.APP_ABSENT); + ApplicationProfileDO profile = Valid.notNull(applicationProfileDAO.selectById(profileId), MessageConst.PROFILE_ABSENT); + MutableLinkedHashMap env = Maps.newMutableLinkedMap(); + env.put(EnvConst.APP_PREFIX + EnvConst.APP_ID, app.getId() + Const.EMPTY); + env.put(EnvConst.APP_PREFIX + EnvConst.APP_NAME, app.getAppName()); + env.put(EnvConst.APP_PREFIX + EnvConst.APP_TAG, app.getAppTag()); + env.put(EnvConst.APP_PREFIX + EnvConst.PROFILE_ID, profile.getId() + Const.EMPTY); + env.put(EnvConst.APP_PREFIX + EnvConst.PROFILE_NAME, profile.getProfileName()); + env.put(EnvConst.APP_PREFIX + EnvConst.PROFILE_TAG, profile.getProfileTag()); + // 插入应用环境变量 + Map appProfileEnv = this.getAppProfileEnv(app.getId(), profile.getId()); + appProfileEnv.forEach((k, v) -> { + env.put(EnvConst.APP_PREFIX + k, v); + }); + return env; + } + + @Override + public Integer deleteAppProfileEnvByAppProfileId(Long appId, Long profileId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(appId != null, ApplicationEnvDO::getAppId, appId) + .eq(profileId != null, ApplicationEnvDO::getProfileId, profileId); + return applicationEnvDAO.delete(wrapper); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void configAppEnv(ApplicationConfigRequest request) { + ApplicationEnvService self = SpringHolder.getBean(ApplicationEnvService.class); + StageType stageType = StageType.of(request.getStageType()); + List list = Lists.newList(); + Long appId = request.getAppId(); + Long profileId = request.getProfileId(); + ApplicationConfigEnvRequest requestEnv = request.getEnv(); + // 构建产物目录 + String bundlePath = requestEnv.getBundlePath(); + if (!Strings.isBlank(bundlePath)) { + ApplicationEnvRequest bundlePathEnv = new ApplicationEnvRequest(); + bundlePathEnv.setKey(ApplicationEnvAttr.BUNDLE_PATH.getKey()); + bundlePathEnv.setValue(bundlePath); + bundlePathEnv.setDescription(ApplicationEnvAttr.BUNDLE_PATH.getDescription()); + list.add(bundlePathEnv); + } + // 产物传输目录 + String transferPath = requestEnv.getTransferPath(); + if (!Strings.isBlank(transferPath)) { + ApplicationEnvRequest transferPathEnv = new ApplicationEnvRequest(); + transferPathEnv.setKey(ApplicationEnvAttr.TRANSFER_PATH.getKey()); + transferPathEnv.setValue(transferPath); + transferPathEnv.setDescription(ApplicationEnvAttr.TRANSFER_PATH.getDescription()); + list.add(transferPathEnv); + } + // 产物传输方式 + String transferMode = requestEnv.getTransferMode(); + if (!Strings.isBlank(transferMode)) { + ApplicationEnvRequest transferModeEnv = new ApplicationEnvRequest(); + transferModeEnv.setKey(ApplicationEnvAttr.TRANSFER_MODE.getKey()); + transferModeEnv.setValue(TransferMode.of(transferMode).getValue()); + transferModeEnv.setDescription(ApplicationEnvAttr.TRANSFER_MODE.getDescription()); + list.add(transferModeEnv); + } + // 产物传输文件类型 + String transferFileType = requestEnv.getTransferFileType(); + if (!Strings.isBlank(transferFileType)) { + ApplicationEnvRequest transferFileTypeEnv = new ApplicationEnvRequest(); + transferFileTypeEnv.setKey(ApplicationEnvAttr.TRANSFER_FILE_TYPE.getKey()); + transferFileTypeEnv.setValue(TransferFileType.of(transferFileType).getValue()); + transferFileTypeEnv.setDescription(ApplicationEnvAttr.TRANSFER_FILE_TYPE.getDescription()); + list.add(transferFileTypeEnv); + } + // 发布序列 + Integer releaseSerial = requestEnv.getReleaseSerial(); + if (releaseSerial != null) { + ApplicationEnvRequest releaseSerialEnv = new ApplicationEnvRequest(); + releaseSerialEnv.setKey(ApplicationEnvAttr.RELEASE_SERIAL.getKey()); + releaseSerialEnv.setValue(SerialType.of(releaseSerial).getValue()); + releaseSerialEnv.setDescription(ApplicationEnvAttr.RELEASE_SERIAL.getDescription()); + list.add(releaseSerialEnv); + } + // 异常处理 + Integer exceptionHandler = requestEnv.getExceptionHandler(); + if (exceptionHandler != null) { + ApplicationEnvRequest exceptionHandlerEnv = new ApplicationEnvRequest(); + exceptionHandlerEnv.setKey(ApplicationEnvAttr.EXCEPTION_HANDLER.getKey()); + exceptionHandlerEnv.setValue(ExceptionHandlerType.of(exceptionHandler).getValue()); + exceptionHandlerEnv.setDescription(ApplicationEnvAttr.EXCEPTION_HANDLER.getDescription()); + list.add(exceptionHandlerEnv); + } + // 构建检查是否有构建序列 + if (StageType.BUILD.equals(stageType)) { + self.checkInitSystemEnv(appId, profileId); + } + // 添加环境变量 + for (ApplicationEnvRequest env : list) { + env.setAppId(appId); + env.setProfileId(profileId); + self.addAppEnv(env); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void syncAppEnv(Long id, Long appId, Long profileId, List targetProfileIdList) { + ApplicationEnvService self = SpringHolder.getBean(ApplicationEnvService.class); + List envList; + // 查询数据 + if (Const.L_N_1.equals(id)) { + // 全量同步 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationEnvDO::getAppId, appId) + .eq(ApplicationEnvDO::getProfileId, profileId) + .eq(ApplicationEnvDO::getSystemEnv, Const.NOT_SYSTEM) + .orderByAsc(ApplicationEnvDO::getUpdateTime); + envList = applicationEnvDAO.selectList(wrapper); + } else { + // 查询数据 + ApplicationEnvDO env = applicationEnvDAO.selectById(id); + Valid.notNull(env, MessageConst.UNKNOWN_DATA); + envList = Lists.singleton(env); + } + // 同步数据 + for (Long targetProfileId : targetProfileIdList) { + // 同步环境变量 + for (ApplicationEnvDO syncEnv : envList) { + ApplicationEnvRequest request = new ApplicationEnvRequest(); + request.setAppId(syncEnv.getAppId()); + request.setProfileId(targetProfileId); + request.setKey(syncEnv.getAttrKey()); + request.setValue(syncEnv.getAttrValue()); + request.setDescription(syncEnv.getDescription()); + self.addAppEnv(request); + } + // 初始化系统变量 + self.checkInitSystemEnv(appId, targetProfileId); + } + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.APP_ID, appId); + EventParamsHolder.addParam(EventKeys.PROFILE_ID, profileId); + EventParamsHolder.addParam(EventKeys.ID_LIST, targetProfileIdList); + EventParamsHolder.addParam(EventKeys.ENV_COUNT, envList.size()); + EventParamsHolder.addParam(EventKeys.PROFILE_COUNT, targetProfileIdList.size()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void copyAppEnv(Long appId, Long targetAppId) { + ApplicationEnvService self = SpringHolder.getBean(ApplicationEnvService.class); + // 查询 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationEnvDO::getAppId, appId) + .eq(ApplicationEnvDO::getSystemEnv, Const.NOT_SYSTEM) + .orderByAsc(ApplicationEnvDO::getUpdateTime); + List envList = applicationEnvDAO.selectList(wrapper); + // 插入 + for (ApplicationEnvDO env : envList) { + env.setId(null); + env.setAppId(targetAppId); + env.setCreateTime(null); + env.setUpdateTime(null); + applicationEnvDAO.insert(env); + // 插入历史值 + historyValueService.addHistory(env.getId(), HistoryValueType.APP_ENV, HistoryOperator.ADD, null, env.getAttrValue()); + } + // 初始化系统变量 + envList.stream() + .map(ApplicationEnvDO::getProfileId) + .distinct() + .forEach(profileId -> self.checkInitSystemEnv(targetAppId, profileId)); + } + + @Override + public Integer getBuildSeqAndIncrement(Long appId, Long profileId) { + // 构建序号 + int seq; + String buildSeqValue = this.getAppEnvValue(appId, profileId, ApplicationEnvAttr.BUILD_SEQ.getKey()); + if (!Strings.isBlank(buildSeqValue)) { + if (Strings.isInteger(buildSeqValue)) { + seq = Integer.parseInt(buildSeqValue); + } else { + seq = Const.N_0; + } + } else { + seq = Const.N_0; + } + seq++; + // 修改构建序列 + ApplicationEnvRequest update = new ApplicationEnvRequest(); + update.setAppId(appId); + update.setProfileId(profileId); + update.setKey(ApplicationEnvAttr.BUILD_SEQ.getKey()); + update.setValue(seq + Const.EMPTY); + update.setDescription(ApplicationEnvAttr.BUILD_SEQ.getDescription()); + this.addAppEnv(update); + return seq; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void checkInitSystemEnv(Long appId, Long profileId) { + List list = Lists.newList(); + String buildSeqValue = this.getAppEnvValue(appId, profileId, ApplicationEnvAttr.BUILD_SEQ.getKey()); + if (buildSeqValue == null) { + // 构建序列 + ApplicationEnvDO buildSeq = new ApplicationEnvDO(); + buildSeq.setAttrKey(ApplicationEnvAttr.BUILD_SEQ.getKey()); + buildSeq.setAttrValue(Const.N_0 + Strings.EMPTY); + buildSeq.setDescription(ApplicationEnvAttr.BUILD_SEQ.getDescription()); + buildSeq.setSystemEnv(Const.IS_SYSTEM); + buildSeq.setAppId(appId); + buildSeq.setProfileId(profileId); + list.add(buildSeq); + } + // 插入 + list.forEach(applicationEnvDAO::insert); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationInfoServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationInfoServiceImpl.java new file mode 100644 index 0000000..7e4a7c7 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationInfoServiceImpl.java @@ -0,0 +1,429 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.app.*; +import cn.orionsec.ops.constant.common.ExceptionHandlerType; +import cn.orionsec.ops.constant.common.SerialType; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.dao.ApplicationInfoDAO; +import cn.orionsec.ops.dao.ApplicationProfileDAO; +import cn.orionsec.ops.dao.ApplicationRepositoryDAO; +import cn.orionsec.ops.entity.domain.*; +import cn.orionsec.ops.entity.request.app.ApplicationConfigRequest; +import cn.orionsec.ops.entity.request.app.ApplicationInfoRequest; +import cn.orionsec.ops.entity.vo.app.*; +import cn.orionsec.ops.service.api.*; +import cn.orionsec.ops.utils.DataQuery; +import cn.orionsec.ops.utils.EventParamsHolder; +import cn.orionsec.ops.utils.Utils; +import cn.orionsec.ops.utils.Valid; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.*; +import java.util.stream.Collectors; + +@Service("applicationInfoService") +public class ApplicationInfoServiceImpl implements ApplicationInfoService { + + @Resource + private ApplicationInfoDAO applicationInfoDAO; + + @Resource + private ApplicationRepositoryDAO applicationRepositoryDAO; + + @Resource + private ApplicationMachineService applicationMachineService; + + @Resource + private ApplicationProfileDAO applicationProfileDAO; + + @Resource + private ApplicationEnvService applicationEnvService; + + @Resource + private MachineInfoService machineInfoService; + + @Resource + private ApplicationActionService applicationActionService; + + @Resource + private ApplicationPipelineDetailService applicationPipelineDetailService; + + @Override + public Long insertApp(ApplicationInfoRequest request) { + String name = request.getName(); + String tag = request.getTag(); + // 名称重复检查 + this.checkNamePresent(null, name); + // 标识重复检查 + this.checkTagPresent(null, tag); + // 插入 + ApplicationInfoDO insert = new ApplicationInfoDO(); + insert.setAppName(name); + insert.setAppTag(tag); + insert.setRepoId(request.getRepoId()); + insert.setDescription(request.getDescription()); + insert.setAppSort(this.getNextSort()); + applicationInfoDAO.insert(insert); + // 设置日志参数 + EventParamsHolder.addParams(insert); + return insert.getId(); + } + + @Override + public Integer updateApp(ApplicationInfoRequest request) { + Long id = request.getId(); + String name = request.getName(); + String tag = request.getTag(); + // 查询应用 + ApplicationInfoDO app = Valid.notNull(applicationInfoDAO.selectById(id), MessageConst.APP_ABSENT); + // 名称重复检查 + this.checkNamePresent(id, name); + // 标识重复检查 + this.checkTagPresent(id, tag); + // 更新 + ApplicationInfoDO update = new ApplicationInfoDO(); + update.setId(id); + update.setAppName(name); + update.setAppTag(tag); + update.setRepoId(request.getRepoId()); + update.setDescription(request.getDescription()); + update.setUpdateTime(new Date()); + int effect = applicationInfoDAO.updateById(update); + // 设置日志参数 + EventParamsHolder.addParams(update); + EventParamsHolder.addParam(EventKeys.NAME, app.getAppName()); + return effect; + } + + @Override + public Integer updateAppSort(Long id, boolean increment) { + // 查询原来的排序 + ApplicationInfoDO app = applicationInfoDAO.selectById(id); + Valid.notNull(app, MessageConst.APP_ABSENT); + Integer beforeSort = app.getAppSort(); + // 查询下一个排序 + LambdaQueryWrapper wrapper; + if (increment) { + wrapper = new LambdaQueryWrapper() + .ne(ApplicationInfoDO::getId, id) + .le(ApplicationInfoDO::getAppSort, beforeSort) + .orderByDesc(ApplicationInfoDO::getAppSort) + .last(Const.LIMIT_1); + } else { + wrapper = new LambdaQueryWrapper() + .ne(ApplicationInfoDO::getId, id) + .ge(ApplicationInfoDO::getAppSort, beforeSort) + .orderByAsc(ApplicationInfoDO::getAppSort) + .last(Const.LIMIT_1); + } + // 查询需要交换的 + ApplicationInfoDO swapApp = applicationInfoDAO.selectOne(wrapper); + if (swapApp == null) { + return 0; + } + // 交换 + ApplicationInfoDO updateSwap = new ApplicationInfoDO(); + updateSwap.setId(swapApp.getId()); + updateSwap.setRepoId(swapApp.getRepoId()); + updateSwap.setAppSort(beforeSort); + applicationInfoDAO.updateById(updateSwap); + // 更新 + Integer afterSort = swapApp.getAppSort(); + if (afterSort.equals(beforeSort)) { + if (increment) { + afterSort -= 1; + } else { + afterSort += 1; + } + } + ApplicationInfoDO updateTarget = new ApplicationInfoDO(); + updateTarget.setId(id); + updateTarget.setRepoId(app.getRepoId()); + updateTarget.setAppSort(afterSort); + return applicationInfoDAO.updateById(updateTarget); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer deleteApp(Long id) { + // 查询应用 + ApplicationInfoDO app = Valid.notNull(applicationInfoDAO.selectById(id), MessageConst.APP_ABSENT); + int effect = 0; + // 删除应用 + effect += applicationInfoDAO.deleteById(id); + // 删除环境变量 + effect += applicationEnvService.deleteAppProfileEnvByAppProfileId(id, null); + // 删除机器 + effect += applicationMachineService.deleteAppMachineByAppProfileId(id, null); + // 删除构建发布流程 + effect += applicationActionService.deleteAppActionByAppProfileId(id, null); + // 删除应用流水线 + effect += applicationPipelineDetailService.deleteByAppId(id); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.NAME, app.getAppName()); + return effect; + } + + @Override + public DataGrid listApp(ApplicationInfoRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(Objects.nonNull(request.getId()), ApplicationInfoDO::getId, request.getId()) + .like(!Strings.isBlank(request.getName()), ApplicationInfoDO::getAppName, request.getName()) + .like(!Strings.isBlank(request.getTag()), ApplicationInfoDO::getAppTag, request.getTag()) + .like(!Strings.isBlank(request.getDescription()), ApplicationInfoDO::getDescription, request.getDescription()) + .orderByAsc(ApplicationInfoDO::getAppSort) + .orderByAsc(ApplicationInfoDO::getId); + // 查询应用 + DataGrid appList = DataQuery.of(applicationInfoDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(ApplicationInfoVO.class); + // 设置版本仓库id + List repoIdList = appList.stream().map(ApplicationInfoVO::getRepoId) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (!repoIdList.isEmpty()) { + applicationRepositoryDAO.selectNameByIdList(repoIdList).forEach(v -> { + appList.stream().filter(s -> v.getId().equals(s.getRepoId())) + .forEach(s -> s.setRepoName(v.getRepoName())); + }); + } + Long profileId = request.getProfileId(); + if (appList.isEmpty() || profileId == null) { + return appList; + } + // 设置配置状态 + List appIdList = appList.stream() + .map(ApplicationInfoVO::getId) + .collect(Collectors.toList()); + Map configMap = applicationActionService.getAppIsConfig(profileId, appIdList); + for (ApplicationInfoVO app : appList) { + app.setIsConfig(configMap.get(app.getId()) ? Const.CONFIGURED : Const.NOT_CONFIGURED); + } + // 查询机器 + if (Const.ENABLE.equals(request.getQueryMachine())) { + Map machineCache = Maps.newMap(); + for (ApplicationInfoVO app : appList) { + List appMachines = applicationMachineService.getAppProfileMachineList(app.getId(), profileId); + if (appMachines.size() == 0) { + app.setMachines(Lists.empty()); + continue; + } + List machines = Converts.toList(appMachines, ApplicationMachineVO.class); + for (ApplicationMachineVO machine : machines) { + MachineInfoDO machineInfo = machineCache.computeIfAbsent(machine.getMachineId(), m -> machineInfoService.selectById(m)); + if (machineInfo != null) { + machine.setMachineName(machineInfo.getMachineName()); + machine.setMachineTag(machineInfo.getMachineTag()); + machine.setMachineHost(machineInfo.getMachineHost()); + } + } + app.setMachines(machines); + } + } + return appList; + } + + @Override + public List getAppMachines(Long appId, Long profileId) { + List appMachines = applicationMachineService.getAppProfileMachineList(appId, profileId); + if (appMachines.size() == 0) { + return Lists.empty(); + } + List machines = Converts.toList(appMachines, ApplicationMachineVO.class); + for (ApplicationMachineVO machine : machines) { + MachineInfoDO machineInfo = machineInfoService.selectById(machine.getMachineId()); + if (machineInfo != null) { + machine.setMachineName(machineInfo.getMachineName()); + machine.setMachineTag(machineInfo.getMachineTag()); + machine.setMachineHost(machineInfo.getMachineHost()); + } + } + return machines; + } + + @Override + public ApplicationDetailVO getAppDetail(Long appId, Long profileId) { + // 查询应用 + ApplicationInfoDO app = applicationInfoDAO.selectById(appId); + Valid.notNull(app, MessageConst.APP_ABSENT); + ApplicationDetailVO detail = Converts.to(app, ApplicationDetailVO.class); + // 查询仓库 + Optional.ofNullable(app.getRepoId()) + .map(applicationRepositoryDAO::selectById) + .map(ApplicationRepositoryDO::getRepoName) + .ifPresent(detail::setRepoName); + // 没传环境id直接返回 + if (profileId == null) { + return detail; + } + // 查询环境 + ApplicationProfileDO profile = applicationProfileDAO.selectById(profileId); + Valid.notNull(profile, MessageConst.PROFILE_ABSENT); + // 查询环境变量 + String bundlePath = applicationEnvService.getAppEnvValue(appId, profileId, ApplicationEnvAttr.BUNDLE_PATH.getKey()); + String transferPath = applicationEnvService.getAppEnvValue(appId, profileId, ApplicationEnvAttr.TRANSFER_PATH.getKey()); + String transferMode = applicationEnvService.getAppEnvValue(appId, profileId, ApplicationEnvAttr.TRANSFER_MODE.getKey()); + String transferFileType = applicationEnvService.getAppEnvValue(appId, profileId, ApplicationEnvAttr.TRANSFER_FILE_TYPE.getKey()); + String releaseSerial = applicationEnvService.getAppEnvValue(appId, profileId, ApplicationEnvAttr.RELEASE_SERIAL.getKey()); + String exceptionHandler = applicationEnvService.getAppEnvValue(appId, profileId, ApplicationEnvAttr.EXCEPTION_HANDLER.getKey()); + // 查询发布机器 + List machines = applicationMachineService.getAppProfileMachineDetail(appId, profileId); + // 查询发布流程 + List actions = applicationActionService.getAppProfileActions(appId, profileId, null); + // 组装数据 + ApplicationConfigEnvVO env = new ApplicationConfigEnvVO(); + env.setBundlePath(bundlePath); + env.setTransferPath(transferPath); + env.setTransferMode(TransferMode.of(transferMode).getValue()); + env.setTransferFileType(TransferFileType.of(transferFileType).getValue()); + env.setReleaseSerial(SerialType.of(releaseSerial).getType()); + env.setExceptionHandler(ExceptionHandlerType.of(exceptionHandler).getType()); + List buildActions = actions.stream() + .filter(s -> ActionType.isBuildAction(s.getActionType())) + .map(s -> Converts.to(s, ApplicationActionVO.class)) + .collect(Collectors.toList()); + List releaseActions = actions.stream() + .filter(s -> ActionType.isReleaseAction(s.getActionType())) + .map(s -> Converts.to(s, ApplicationActionVO.class)) + .collect(Collectors.toList()); + detail.setEnv(env); + detail.setReleaseMachines(machines); + detail.setBuildActions(buildActions); + detail.setReleaseActions(releaseActions); + return detail; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void configAppProfile(ApplicationConfigRequest request) { + Long appId = request.getAppId(); + Long profileId = request.getProfileId(); + // 查询应用和环境 + ApplicationInfoDO app = Valid.notNull(applicationInfoDAO.selectById(appId), MessageConst.APP_ABSENT); + ApplicationProfileDO profile = Valid.notNull(applicationProfileDAO.selectById(profileId), MessageConst.PROFILE_ABSENT); + StageType stageType = StageType.of(request.getStageType()); + if (StageType.RELEASE.equals(stageType)) { + // 配置机器 + applicationMachineService.configAppMachines(request); + } + // 配置环境变量 + applicationEnvService.configAppEnv(request); + // 配置发布 + applicationActionService.configAppAction(request); + // 设置日志参数 + EventParamsHolder.addParams(request); + EventParamsHolder.addParam(EventKeys.APP_NAME, app.getAppName()); + EventParamsHolder.addParam(EventKeys.PROFILE_NAME, profile.getProfileName()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void syncAppProfileConfig(Long appId, Long profileId, List targetProfileIdList) { + // 查询应用和环境 + ApplicationInfoDO app = Valid.notNull(applicationInfoDAO.selectById(appId), MessageConst.APP_ABSENT); + Valid.notNull(applicationProfileDAO.selectById(profileId), MessageConst.PROFILE_ABSENT); + // 检查环境是否已配置 + boolean isConfig = this.checkAppConfig(appId, profileId); + Valid.isTrue(isConfig, MessageConst.APP_PROFILE_NOT_CONFIGURED); + for (Long targetProfileId : targetProfileIdList) { + // 同步环境变量 + applicationEnvService.syncAppEnv(Const.L_N_1, appId, profileId, Lists.singleton(targetProfileId)); + // 同步机器 + applicationMachineService.syncAppProfileMachine(appId, profileId, targetProfileId); + // 同步发布流程 + applicationActionService.syncAppProfileAction(appId, profileId, targetProfileId); + } + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.NAME, app.getAppName()); + EventParamsHolder.addParam(EventKeys.COUNT, targetProfileIdList.size()); + EventParamsHolder.addParam(EventKeys.APP_ID, appId); + EventParamsHolder.addParam(EventKeys.PROFILE_ID, profileId); + EventParamsHolder.addParam(EventKeys.ID_LIST, targetProfileIdList); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void copyApplication(Long appId) { + // 查询app + ApplicationInfoDO app = Valid.notNull(applicationInfoDAO.selectById(appId), MessageConst.APP_ABSENT); + String beforeName = app.getAppName(); + app.setId(null); + String copySuffix = Utils.getRandomSuffix(); + app.setAppName(beforeName + copySuffix); + app.setAppTag(app.getAppTag() + copySuffix); + app.setAppSort(this.getNextSort()); + app.setCreateTime(null); + app.setUpdateTime(null); + applicationInfoDAO.insert(app); + Long targetAppId = app.getId(); + // 复制环境变量 + applicationEnvService.copyAppEnv(appId, targetAppId); + // 复制机器 + applicationMachineService.copyAppMachine(appId, targetAppId); + // 复制发布流程 + applicationActionService.copyAppAction(appId, targetAppId); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, appId); + EventParamsHolder.addParam(EventKeys.NAME, beforeName); + } + + @Override + public boolean checkAppConfig(Long appId, Long profileId) { + return applicationActionService.getAppProfileActionCount(appId, profileId, StageType.BUILD.getType()) > 0 + && applicationActionService.getAppProfileActionCount(appId, profileId, StageType.RELEASE.getType()) > 0; + } + + @Override + public Integer getNextSort() { + Wrapper wrapper = new LambdaQueryWrapper() + .orderByDesc(ApplicationInfoDO::getAppSort) + .last(Const.LIMIT_1); + return Optional.ofNullable(applicationInfoDAO.selectOne(wrapper)) + .map(ApplicationInfoDO::getAppSort) + .map(s -> s + Const.N_10) + .orElse(Const.N_10); + } + + /** + * 检查名称是否存在 + * + * @param id id + * @param name name + */ + private void checkNamePresent(Long id, String name) { + LambdaQueryWrapper presentWrapper = new LambdaQueryWrapper() + .ne(id != null, ApplicationInfoDO::getId, id) + .eq(ApplicationInfoDO::getAppName, name); + boolean present = DataQuery.of(applicationInfoDAO).wrapper(presentWrapper).present(); + Valid.isTrue(!present, MessageConst.NAME_PRESENT); + } + + /** + * 检查标签是否存在 + * + * @param id id + * @param tag tag + */ + private void checkTagPresent(Long id, String tag) { + LambdaQueryWrapper presentWrapper = new LambdaQueryWrapper() + .ne(id != null, ApplicationInfoDO::getId, id) + .eq(ApplicationInfoDO::getAppTag, tag); + boolean present = DataQuery.of(applicationInfoDAO).wrapper(presentWrapper).present(); + Valid.isTrue(!present, MessageConst.TAG_PRESENT); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationMachineServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationMachineServiceImpl.java new file mode 100644 index 0000000..ed1dfbb --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationMachineServiceImpl.java @@ -0,0 +1,215 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.dao.ApplicationMachineDAO; +import cn.orionsec.ops.dao.MachineInfoDAO; +import cn.orionsec.ops.entity.domain.ApplicationMachineDO; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.request.app.ApplicationConfigRequest; +import cn.orionsec.ops.entity.vo.app.ApplicationMachineVO; +import cn.orionsec.ops.service.api.ApplicationMachineService; +import cn.orionsec.ops.utils.DataQuery; +import cn.orionsec.ops.utils.Valid; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service("applicationMachineService") +public class ApplicationMachineServiceImpl implements ApplicationMachineService { + + @Resource + private ApplicationMachineDAO applicationMachineDAO; + + @Resource + private MachineInfoDAO machineInfoDAO; + + @Override + public Long getAppProfileMachineId(Long machineId, Long appId, Long profileId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationMachineDO::getMachineId, machineId) + .eq(ApplicationMachineDO::getAppId, appId) + .eq(ApplicationMachineDO::getProfileId, profileId); + return Optional.ofNullable(applicationMachineDAO.selectOne(wrapper)) + .map(ApplicationMachineDO::getMachineId) + .orElse(null); + } + + @Override + public List getAppProfileMachineIdList(Long appId, Long profileId, boolean filterMachineStatus) { + LambdaQueryWrapper appMachineWrapper = new LambdaQueryWrapper() + .eq(ApplicationMachineDO::getAppId, appId) + .eq(ApplicationMachineDO::getProfileId, profileId); + List appMachineIdList = applicationMachineDAO.selectList(appMachineWrapper).stream() + .map(ApplicationMachineDO::getMachineId) + .collect(Collectors.toList()); + if (!filterMachineStatus || appMachineIdList.isEmpty()) { + return appMachineIdList; + } + // 过滤进行中的机器 + LambdaQueryWrapper machineWrapper = new LambdaQueryWrapper() + .eq(MachineInfoDO::getMachineStatus, Const.ENABLE) + .in(MachineInfoDO::getId, appMachineIdList); + return machineInfoDAO.selectList(machineWrapper).stream() + .map(MachineInfoDO::getId) + .collect(Collectors.toList()); + } + + @Override + public Integer getAppProfileMachineCount(Long appId, Long profileId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationMachineDO::getAppId, appId) + .eq(ApplicationMachineDO::getMachineId, profileId); + return applicationMachineDAO.selectCount(wrapper); + } + + @Override + public List getAppProfileMachineDetail(Long appId, Long profileId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationMachineDO::getAppId, appId) + .eq(ApplicationMachineDO::getProfileId, profileId); + // 查询关联 + List list = DataQuery.of(applicationMachineDAO) + .wrapper(wrapper) + .list(ApplicationMachineVO.class); + // 查询机器 + list.forEach(m -> Optional.ofNullable(m.getMachineId()) + .map(machineInfoDAO::selectById) + .ifPresent(s -> { + m.setMachineName(s.getMachineName()); + m.setMachineHost(s.getMachineHost()); + m.setMachineTag(s.getMachineTag()); + })); + return list; + } + + @Override + public List getAppProfileMachineList(Long appId, Long profileId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationMachineDO::getAppId, appId) + .eq(ApplicationMachineDO::getProfileId, profileId); + return applicationMachineDAO.selectList(wrapper); + } + + @Override + public Integer deleteAppMachineByMachineIdList(List machineIdList) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .in(ApplicationMachineDO::getMachineId, machineIdList); + return applicationMachineDAO.delete(wrapper); + } + + @Override + public Integer deleteAppMachineByAppProfileId(Long appId, Long profileId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(appId != null, ApplicationMachineDO::getAppId, appId) + .eq(profileId != null, ApplicationMachineDO::getProfileId, profileId); + return applicationMachineDAO.delete(wrapper); + } + + @Override + public Integer deleteAppMachineByAppProfileMachineId(Long appId, Long profileId, Long machineId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationMachineDO::getAppId, appId) + .eq(ApplicationMachineDO::getProfileId, profileId) + .eq(ApplicationMachineDO::getMachineId, machineId); + return applicationMachineDAO.delete(wrapper); + } + + @Override + public Integer deleteById(Long id) { + return applicationMachineDAO.deleteById(id); + } + + @Override + public List selectAppProfileMachineIdList(Long appId, Long profileId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationMachineDO::getAppId, appId) + .eq(ApplicationMachineDO::getProfileId, profileId); + return applicationMachineDAO.selectList(wrapper) + .stream() + .map(ApplicationMachineDO::getMachineId) + .collect(Collectors.toList()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void configAppMachines(ApplicationConfigRequest request) { + Long appId = request.getAppId(); + Long profileId = request.getProfileId(); + List machineIdList = request.getMachineIdList(); + // 检查 + for (Long machineId : machineIdList) { + MachineInfoDO machine = machineInfoDAO.selectById(machineId); + Valid.notNull(machine, MessageConst.INVALID_MACHINE); + } + // 删除 + SpringHolder.getBean(ApplicationMachineService.class).deleteAppMachineByAppProfileId(appId, profileId); + // 添加 + for (Long machineId : machineIdList) { + ApplicationMachineDO machine = new ApplicationMachineDO(); + machine.setAppId(appId); + machine.setProfileId(profileId); + machine.setMachineId(machineId); + applicationMachineDAO.insert(machine); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void syncAppProfileMachine(Long appId, Long profileId, Long targetProfileId) { + // 删除 + LambdaQueryWrapper deleteWrapper = new LambdaQueryWrapper() + .eq(ApplicationMachineDO::getAppId, appId) + .eq(ApplicationMachineDO::getProfileId, targetProfileId); + applicationMachineDAO.delete(deleteWrapper); + // 查询 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper() + .eq(ApplicationMachineDO::getAppId, appId) + .eq(ApplicationMachineDO::getProfileId, profileId); + List machines = applicationMachineDAO.selectList(queryWrapper); + // 新增 + for (ApplicationMachineDO machine : machines) { + machine.setId(null); + machine.setProfileId(targetProfileId); + machine.setCreateTime(null); + machine.setUpdateTime(null); + applicationMachineDAO.insert(machine); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void copyAppMachine(Long appId, Long targetAppId) { + // 查询 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationMachineDO::getAppId, appId); + List machines = applicationMachineDAO.selectList(wrapper); + // 新增 + for (ApplicationMachineDO machine : machines) { + machine.setId(null); + machine.setAppId(targetAppId); + machine.setCreateTime(null); + machine.setUpdateTime(null); + applicationMachineDAO.insert(machine); + } + } + + @Override + public void updateAppMachineReleaseId(Long appId, Long profileId, Long releaseId, List machineIdList) { + ApplicationMachineDO update = new ApplicationMachineDO(); + update.setReleaseId(releaseId); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationMachineDO::getAppId, appId) + .eq(ApplicationMachineDO::getProfileId, profileId) + .in(ApplicationMachineDO::getMachineId, machineIdList); + applicationMachineDAO.update(update, wrapper); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationPipelineDetailServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationPipelineDetailServiceImpl.java new file mode 100644 index 0000000..15919dd --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationPipelineDetailServiceImpl.java @@ -0,0 +1,95 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.ops.dao.ApplicationInfoDAO; +import cn.orionsec.ops.dao.ApplicationPipelineDetailDAO; +import cn.orionsec.ops.entity.domain.ApplicationInfoDO; +import cn.orionsec.ops.entity.domain.ApplicationPipelineDetailDO; +import cn.orionsec.ops.entity.vo.app.ApplicationPipelineDetailVO; +import cn.orionsec.ops.service.api.ApplicationPipelineDetailService; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; +import java.util.stream.Collectors; + +@Service("applicationPipelineDetailService") +public class ApplicationPipelineDetailServiceImpl implements ApplicationPipelineDetailService { + + @Resource + private ApplicationPipelineDetailDAO applicationPipelineDetailDAO; + + @Resource + private ApplicationInfoDAO applicationInfoDAO; + + @Override + public List getByPipelineId(Long pipelineId) { + Wrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationPipelineDetailDO::getPipelineId, pipelineId); + // 查询列表 + List pipelineDetails = applicationPipelineDetailDAO.selectList(wrapper); + List details = Converts.toList(pipelineDetails, ApplicationPipelineDetailVO.class); + // 查询应用名称 + List appIdList = details.stream() + .map(ApplicationPipelineDetailVO::getAppId) + .collect(Collectors.toList()); + List appList = applicationInfoDAO.selectBatchIds(appIdList); + for (ApplicationPipelineDetailVO detail : details) { + appList.stream() + .filter(s -> s.getId().equals(detail.getAppId())) + .findFirst() + .ifPresent(app -> { + detail.setAppName(app.getAppName()); + detail.setAppTag(app.getAppTag()); + detail.setRepoId(app.getRepoId()); + }); + } + return details; + } + + @Override + public List selectByPipelineId(Long pipelineId) { + Wrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationPipelineDetailDO::getPipelineId, pipelineId); + return applicationPipelineDetailDAO.selectList(wrapper); + } + + @Override + public List selectByPipelineIdList(List pipelineIdList) { + Wrapper wrapper = new LambdaQueryWrapper() + .in(ApplicationPipelineDetailDO::getPipelineId, pipelineIdList); + return applicationPipelineDetailDAO.selectList(wrapper); + } + + @Override + public Integer deleteByPipelineId(Long pipelineId) { + Wrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationPipelineDetailDO::getPipelineId, pipelineId); + return applicationPipelineDetailDAO.delete(wrapper); + } + + @Override + public Integer deleteByPipelineIdList(List pipelineIdList) { + Wrapper wrapper = new LambdaQueryWrapper() + .in(ApplicationPipelineDetailDO::getPipelineId, pipelineIdList); + return applicationPipelineDetailDAO.delete(wrapper); + } + + @Override + public Integer deleteByProfileId(Long profileId) { + Wrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationPipelineDetailDO::getProfileId, profileId); + return applicationPipelineDetailDAO.delete(wrapper); + } + + @Override + public Integer deleteByAppId(Long appId) { + Wrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationPipelineDetailDO::getAppId, appId); + return applicationPipelineDetailDAO.delete(wrapper); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationPipelineServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationPipelineServiceImpl.java new file mode 100644 index 0000000..19545f2 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationPipelineServiceImpl.java @@ -0,0 +1,220 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.dao.ApplicationInfoDAO; +import cn.orionsec.ops.dao.ApplicationPipelineDAO; +import cn.orionsec.ops.dao.ApplicationPipelineDetailDAO; +import cn.orionsec.ops.entity.domain.ApplicationInfoDO; +import cn.orionsec.ops.entity.domain.ApplicationPipelineDO; +import cn.orionsec.ops.entity.domain.ApplicationPipelineDetailDO; +import cn.orionsec.ops.entity.request.app.ApplicationPipelineRequest; +import cn.orionsec.ops.entity.vo.app.ApplicationPipelineDetailVO; +import cn.orionsec.ops.entity.vo.app.ApplicationPipelineVO; +import cn.orionsec.ops.service.api.ApplicationPipelineDetailService; +import cn.orionsec.ops.service.api.ApplicationPipelineService; +import cn.orionsec.ops.utils.DataQuery; +import cn.orionsec.ops.utils.EventParamsHolder; +import cn.orionsec.ops.utils.Valid; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +@Service("applicationPipelineService") +public class ApplicationPipelineServiceImpl implements ApplicationPipelineService { + + @Resource + private ApplicationPipelineDAO applicationPipelineDAO; + + @Resource + private ApplicationPipelineDetailDAO applicationPipelineDetailDAO; + + @Resource + private ApplicationPipelineDetailService applicationPipelineDetailService; + + @Resource + private ApplicationInfoDAO applicationInfoDAO; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long addPipeline(ApplicationPipelineRequest request) { + Long profileId = request.getProfileId(); + String name = request.getName(); + // 检查名称是否重复 + this.checkNamePresent(null, profileId, name); + // 插入主表 + ApplicationPipelineDO insertPipeline = new ApplicationPipelineDO(); + insertPipeline.setProfileId(profileId); + insertPipeline.setPipelineName(name); + insertPipeline.setDescription(request.getDescription()); + applicationPipelineDAO.insert(insertPipeline); + Long id = insertPipeline.getId(); + // 插入详情 + List details = request.getDetails().stream() + .map(s -> { + ApplicationPipelineDetailDO pipelineDetail = new ApplicationPipelineDetailDO(); + pipelineDetail.setPipelineId(id); + pipelineDetail.setAppId(s.getAppId()); + pipelineDetail.setProfileId(profileId); + pipelineDetail.setStageType(s.getStageType()); + return pipelineDetail; + }).collect(Collectors.toList()); + details.forEach(applicationPipelineDetailDAO::insert); + // 设置日志参数 + EventParamsHolder.addParams(insertPipeline); + EventParamsHolder.addParam(EventKeys.DETAILS, details); + return id; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer updatePipeline(ApplicationPipelineRequest request) { + Long id = request.getId(); + Long profileId = request.getProfileId(); + String name = request.getName(); + // 检查名称是否重复 + this.checkNamePresent(id, profileId, name); + // 更新主表 + ApplicationPipelineDO updatePipeline = new ApplicationPipelineDO(); + updatePipeline.setId(id); + updatePipeline.setProfileId(profileId); + updatePipeline.setPipelineName(name); + updatePipeline.setDescription(request.getDescription()); + int effect = applicationPipelineDAO.updateById(updatePipeline); + // 删除详情 + applicationPipelineDetailService.deleteByPipelineId(id); + // 重新插入详情 + List details = request.getDetails().stream() + .map(s -> { + ApplicationPipelineDetailDO pipelineDetail = new ApplicationPipelineDetailDO(); + pipelineDetail.setPipelineId(id); + pipelineDetail.setStageType(s.getStageType()); + pipelineDetail.setAppId(s.getAppId()); + pipelineDetail.setProfileId(profileId); + return pipelineDetail; + }).collect(Collectors.toList()); + details.forEach(applicationPipelineDetailDAO::insert); + // 设置日志参数 + EventParamsHolder.addParams(updatePipeline); + EventParamsHolder.addParam(EventKeys.DETAILS, details); + return effect; + } + + @Override + public DataGrid listPipeline(ApplicationPipelineRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(Objects.nonNull(request.getId()), ApplicationPipelineDO::getId, request.getId()) + .eq(Objects.nonNull(request.getProfileId()), ApplicationPipelineDO::getProfileId, request.getProfileId()) + .like(Strings.isNotBlank(request.getName()), ApplicationPipelineDO::getPipelineName, request.getName()) + .like(Strings.isNotBlank(request.getDescription()), ApplicationPipelineDO::getDescription, request.getDescription()); + // 查询列表 + DataGrid dataGrid = DataQuery.of(applicationPipelineDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(ApplicationPipelineVO.class); + // 不查询详情直接返回 + if (!Const.ENABLE.equals(request.getQueryDetail())) { + return dataGrid; + } + // 获取流水线详情 + List pipelineIdList = dataGrid.stream() + .map(ApplicationPipelineVO::getId) + .collect(Collectors.toList()); + if (pipelineIdList.isEmpty()) { + return dataGrid; + } + List pipelineDetails = applicationPipelineDetailService.selectByPipelineIdList(pipelineIdList); + List details = Converts.toList(pipelineDetails, ApplicationPipelineDetailVO.class); + // 获取应用名称 + if (!details.isEmpty()) { + List appIdList = details.stream() + .map(ApplicationPipelineDetailVO::getAppId) + .distinct() + .collect(Collectors.toList()); + List appNameList = applicationInfoDAO.selectBatchIds(appIdList); + // 设置应用名称 + for (ApplicationPipelineDetailVO detail : details) { + appNameList.stream() + .filter(s -> s.getId().equals(detail.getAppId())) + .findFirst() + .ifPresent(app -> { + detail.setRepoId(app.getRepoId()); + detail.setAppName(app.getAppName()); + detail.setAppTag(app.getAppTag()); + }); + } + } + // 设置流水线详情 + dataGrid.forEach(s -> { + List currentPipelineDetails = details.stream() + .filter(d -> d.getPipelineId().equals(s.getId())) + .collect(Collectors.toList()); + s.setDetails(currentPipelineDetails); + }); + return dataGrid; + } + + @Override + public ApplicationPipelineVO getPipeline(Long id) { + // 查询主表 + ApplicationPipelineDO pipeline = applicationPipelineDAO.selectById(id); + Valid.notNull(pipeline, MessageConst.UNKNOWN_DATA); + ApplicationPipelineVO pipelineVO = Converts.to(pipeline, ApplicationPipelineVO.class); + // 查询子表 + List details = applicationPipelineDetailService.getByPipelineId(id); + pipelineVO.setDetails(details); + return pipelineVO; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer deletePipeline(List idList) { + // 删除主表 + int effect = applicationPipelineDAO.deleteBatchIds(idList); + // 删除详情表 + effect += applicationPipelineDetailService.deleteByPipelineIdList(idList); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID_LIST, idList); + EventParamsHolder.addParam(EventKeys.COUNT, idList.size()); + return effect; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer deleteByProfileId(Long profileId) { + // 删除主表 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationPipelineDO::getProfileId, profileId); + int effect = applicationPipelineDAO.delete(wrapper); + // 删除详情 + effect += applicationPipelineDetailService.deleteByProfileId(profileId); + return effect; + } + + /** + * 检查名称是否存在 + * + * @param id id + * @param profileId profileId + * @param name name + */ + private void checkNamePresent(Long id, Long profileId, String name) { + LambdaQueryWrapper presentWrapper = new LambdaQueryWrapper() + .ne(id != null, ApplicationPipelineDO::getId, id) + .eq(ApplicationPipelineDO::getProfileId, profileId) + .eq(ApplicationPipelineDO::getPipelineName, name); + boolean present = DataQuery.of(applicationPipelineDAO).wrapper(presentWrapper).present(); + Valid.isTrue(!present, MessageConst.NAME_PRESENT); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationPipelineTaskDetailServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationPipelineTaskDetailServiceImpl.java new file mode 100644 index 0000000..abed2a5 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationPipelineTaskDetailServiceImpl.java @@ -0,0 +1,120 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.ops.dao.ApplicationBuildDAO; +import cn.orionsec.ops.dao.ApplicationPipelineTaskDetailDAO; +import cn.orionsec.ops.dao.MachineInfoDAO; +import cn.orionsec.ops.entity.domain.ApplicationBuildDO; +import cn.orionsec.ops.entity.domain.ApplicationPipelineTaskDetailDO; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.vo.app.ApplicationPipelineStageConfigVO; +import cn.orionsec.ops.entity.vo.app.ApplicationPipelineTaskDetailVO; +import cn.orionsec.ops.entity.vo.machine.MachineInfoVO; +import cn.orionsec.ops.service.api.ApplicationPipelineTaskDetailService; +import cn.orionsec.ops.utils.DataQuery; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service("applicationPipelineTaskDetailService") +public class ApplicationPipelineTaskDetailServiceImpl implements ApplicationPipelineTaskDetailService { + + @Resource + private ApplicationPipelineTaskDetailDAO applicationPipelineTaskDetailDAO; + + @Resource + private ApplicationBuildDAO applicationBuildDAO; + + @Resource + private MachineInfoDAO machineInfoDAO; + + @Override + public List getTaskDetails(Long taskId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationPipelineTaskDetailDO::getTaskId, taskId); + // 查询列表 + List details = DataQuery.of(applicationPipelineTaskDetailDAO) + .wrapper(wrapper) + .list(ApplicationPipelineTaskDetailVO.class); + // 设置配置信息 构建序列 + List buildConfigList = details.stream() + .map(ApplicationPipelineTaskDetailVO::getConfig) + .filter(s -> s.getBuildId() != null) + .collect(Collectors.toList()); + if (!buildConfigList.isEmpty()) { + // 构建版本id + List buildIdList = buildConfigList.stream() + .map(ApplicationPipelineStageConfigVO::getBuildId) + .collect(Collectors.toList()); + Map buildMap = applicationBuildDAO.selectBatchIds(buildIdList).stream() + .collect(Collectors.toMap(ApplicationBuildDO::getId, Function.identity())); + // 设置构建序列 + for (ApplicationPipelineStageConfigVO buildConfig : buildConfigList) { + Optional.ofNullable(buildConfig.getBuildId()) + .map(buildMap::get) + .map(ApplicationBuildDO::getBuildSeq) + .ifPresent(buildConfig::setBuildSeq); + } + } + // 设置配置信息 构建序列 + List specifyMachineReleaseConfigList = details.stream() + .map(ApplicationPipelineTaskDetailVO::getConfig) + .filter(s -> Lists.isNotEmpty(s.getMachineIdList())) + .collect(Collectors.toList()); + if (!specifyMachineReleaseConfigList.isEmpty()) { + List machineIdList = specifyMachineReleaseConfigList.stream() + .map(ApplicationPipelineStageConfigVO::getMachineIdList) + .flatMap(Collection::stream) + .distinct() + .collect(Collectors.toList()); + Map machineMap = machineInfoDAO.selectBatchIds(machineIdList).stream() + .collect(Collectors.toMap(MachineInfoDO::getId, Function.identity())); + // 设置机器信息 + for (ApplicationPipelineStageConfigVO config : specifyMachineReleaseConfigList) { + List machineList = config.getMachineIdList().stream() + .map(machineMap::get) + .filter(Objects::nonNull) + .map(s -> Converts.to(s, MachineInfoVO.class)) + .collect(Collectors.toList()); + config.setMachineList(machineList); + } + } + return details; + } + + @Override + public List selectTaskDetails(Long taskId) { + Wrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationPipelineTaskDetailDO::getTaskId, taskId); + return applicationPipelineTaskDetailDAO.selectList(wrapper); + } + + @Override + public List selectTaskDetails(List taskIdList) { + Wrapper wrapper = new LambdaQueryWrapper() + .in(ApplicationPipelineTaskDetailDO::getTaskId, taskIdList); + return applicationPipelineTaskDetailDAO.selectList(wrapper); + } + + @Override + public Integer deleteByTaskId(Long taskId) { + Wrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationPipelineTaskDetailDO::getTaskId, taskId); + return applicationPipelineTaskDetailDAO.delete(wrapper); + } + + @Override + public Integer deleteByTaskIdList(List taskIdList) { + Wrapper wrapper = new LambdaQueryWrapper() + .in(ApplicationPipelineTaskDetailDO::getTaskId, taskIdList); + return applicationPipelineTaskDetailDAO.delete(wrapper); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationPipelineTaskLogServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationPipelineTaskLogServiceImpl.java new file mode 100644 index 0000000..86a490a --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationPipelineTaskLogServiceImpl.java @@ -0,0 +1,38 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.ops.dao.ApplicationPipelineTaskLogDAO; +import cn.orionsec.ops.entity.domain.ApplicationPipelineTaskLogDO; +import cn.orionsec.ops.entity.vo.app.ApplicationPipelineTaskLogVO; +import cn.orionsec.ops.service.api.ApplicationPipelineTaskLogService; +import cn.orionsec.ops.utils.DataQuery; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; + +@Service("applicationPipelineTaskLogService") +public class ApplicationPipelineTaskLogServiceImpl implements ApplicationPipelineTaskLogService { + + @Resource + private ApplicationPipelineTaskLogDAO applicationPipelineTaskLogDAO; + + @Override + public List getLogList(Long taskId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationPipelineTaskLogDO::getTaskId, taskId); + return DataQuery.of(applicationPipelineTaskLogDAO) + .wrapper(wrapper) + .list(ApplicationPipelineTaskLogVO.class); + } + + @Override + public Integer deleteByTaskIdList(List taskIdList) { + Wrapper wrapper = new LambdaQueryWrapper() + .in(ApplicationPipelineTaskLogDO::getTaskId, taskIdList); + return applicationPipelineTaskLogDAO.delete(wrapper); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationPipelineTaskServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationPipelineTaskServiceImpl.java new file mode 100644 index 0000000..22971a3 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationPipelineTaskServiceImpl.java @@ -0,0 +1,629 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.constant.Letters; +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.collect.Sets; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.CnConst; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.app.PipelineDetailStatus; +import cn.orionsec.ops.constant.app.PipelineStatus; +import cn.orionsec.ops.constant.app.StageType; +import cn.orionsec.ops.constant.app.TimedType; +import cn.orionsec.ops.constant.common.AuditStatus; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.message.MessageType; +import cn.orionsec.ops.constant.user.RoleType; +import cn.orionsec.ops.dao.*; +import cn.orionsec.ops.entity.domain.*; +import cn.orionsec.ops.entity.dto.app.ApplicationPipelineStageConfigDTO; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.entity.request.app.ApplicationPipelineTaskDetailRequest; +import cn.orionsec.ops.entity.request.app.ApplicationPipelineTaskRequest; +import cn.orionsec.ops.entity.vo.app.*; +import cn.orionsec.ops.handler.app.pipeline.IPipelineProcessor; +import cn.orionsec.ops.handler.app.pipeline.PipelineProcessor; +import cn.orionsec.ops.handler.app.pipeline.PipelineSessionHolder; +import cn.orionsec.ops.service.api.*; +import cn.orionsec.ops.task.TaskRegister; +import cn.orionsec.ops.task.TaskType; +import cn.orionsec.ops.utils.Currents; +import cn.orionsec.ops.utils.DataQuery; +import cn.orionsec.ops.utils.EventParamsHolder; +import cn.orionsec.ops.utils.Valid; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service("applicationPipelineTaskService") +public class ApplicationPipelineTaskServiceImpl implements ApplicationPipelineTaskService { + + @Resource + private ApplicationPipelineDAO applicationPipelineDAO; + + @Resource + private ApplicationPipelineDetailService applicationPipelineDetailService; + + @Resource + private ApplicationPipelineTaskDAO applicationPipelineTaskDAO; + + @Resource + private ApplicationPipelineTaskDetailDAO applicationPipelineTaskDetailDAO; + + @Resource + private ApplicationPipelineTaskDetailService applicationPipelineTaskDetailService; + + @Resource + private ApplicationProfileDAO applicationProfileDAO; + + @Resource + private ApplicationInfoDAO applicationInfoDAO; + + @Resource + private ApplicationBuildDAO applicationBuildDAO; + + @Resource + private ApplicationBuildService applicationBuildService; + + @Resource + private ApplicationPipelineTaskLogService applicationPipelineTaskLogService; + + @Resource + private WebSideMessageService webSideMessageService; + + @Resource + private PipelineSessionHolder pipelineSessionHolder; + + @Resource + private TaskRegister taskRegister; + + @Override + public DataGrid getPipelineTaskList(ApplicationPipelineTaskRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(Objects.nonNull(request.getId()), ApplicationPipelineTaskDO::getId, request.getId()) + .eq(Objects.nonNull(request.getProfileId()), ApplicationPipelineTaskDO::getProfileId, request.getProfileId()) + .eq(Objects.nonNull(request.getPipelineId()), ApplicationPipelineTaskDO::getPipelineId, request.getPipelineId()) + .eq(Objects.nonNull(request.getStatus()), ApplicationPipelineTaskDO::getExecStatus, request.getStatus()) + .like(Strings.isNotBlank(request.getPipelineName()), ApplicationPipelineTaskDO::getPipelineName, request.getPipelineName()) + .like(Strings.isNotBlank(request.getTitle()), ApplicationPipelineTaskDO::getExecTitle, request.getTitle()) + .like(Strings.isNotBlank(request.getDescription()), ApplicationPipelineTaskDO::getExecDescription, request.getDescription()) + .orderByDesc(ApplicationPipelineTaskDO::getId); + // 查询列表 + return DataQuery.of(applicationPipelineTaskDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(ApplicationPipelineTaskListVO.class); + } + + @Override + public ApplicationPipelineTaskVO getPipelineTaskDetail(Long id) { + // 查询明细 + ApplicationPipelineTaskDO task = applicationPipelineTaskDAO.selectById(id); + Valid.notNull(task, MessageConst.PIPELINE_TASK_ABSENT); + ApplicationPipelineTaskVO res = Converts.to(task, ApplicationPipelineTaskVO.class); + // 查询详情 + List details = applicationPipelineTaskDetailService.getTaskDetails(id); + res.setDetails(details); + return res; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Long submitPipelineTask(ApplicationPipelineTaskRequest request) { + // 查询流水线 + Long pipelineId = request.getPipelineId(); + ApplicationPipelineDO pipeline = applicationPipelineDAO.selectById(pipelineId); + Valid.notNull(pipeline, MessageConst.PIPELINE_ABSENT); + // 查询流水线详情 + List pipelineDetails = applicationPipelineDetailService.selectByPipelineId(pipelineId); + Valid.notEmpty(pipelineDetails, MessageConst.PIPELINE_DETAIL_EMPTY); + // 检查是否存在详情 + Map requestDetailsMap = request.getDetails().stream() + .collect(Collectors.toMap(ApplicationPipelineTaskDetailRequest::getId, Function.identity(), (e1, e2) -> e2)); + boolean detailAllPresent = pipelineDetails.stream() + .map(ApplicationPipelineDetailDO::getId) + .map(requestDetailsMap::get) + .allMatch(Objects::nonNull); + Valid.isTrue(detailAllPresent, MessageConst.PIPELINE_DETAIL_ABSENT); + // 设置详情信息 + for (ApplicationPipelineDetailDO pipelineDetail : pipelineDetails) { + ApplicationPipelineTaskDetailRequest requestDetail = requestDetailsMap.get(pipelineDetail.getId()); + requestDetail.setAppId(pipelineDetail.getAppId()); + requestDetail.setProfileId(pipelineDetail.getProfileId()); + requestDetail.setStageType(pipelineDetail.getStageType()); + } + // 查询环境信息 + Long profileId = pipeline.getProfileId(); + ApplicationProfileDO profile = applicationProfileDAO.selectById(profileId); + Valid.notNull(profile, MessageConst.PROFILE_ABSENT); + // 查询应用信息 + List appIdList = pipelineDetails.stream() + .map(ApplicationPipelineDetailDO::getAppId) + .distinct() + .collect(Collectors.toList()); + Map appInfoMap = applicationInfoDAO.selectBatchIds(appIdList).stream() + .collect(Collectors.toMap(ApplicationInfoDO::getId, Function.identity(), (e1, e2) -> e2)); + Valid.eq(appIdList.size(), appInfoMap.size(), MessageConst.APP_ABSENT); + ApplicationPipelineTaskDO pipelineTask = this.setPipelineTask(request, pipeline, profile); + request.setStatus(pipelineTask.getExecStatus()); + // 插入明细 + applicationPipelineTaskDAO.insert(pipelineTask); + Long taskId = pipelineTask.getId(); + // 设置详情 + List taskDetailList = this.setPipelineTaskDetails(taskId, appInfoMap, pipelineDetails, requestDetailsMap); + taskDetailList.forEach(applicationPipelineTaskDetailDAO::insert); + // 设置日志参数 + EventParamsHolder.addParams(pipelineTask); + EventParamsHolder.addParam(EventKeys.DETAILS, taskDetailList); + return taskId; + } + + @Override + public Integer auditPipelineTask(ApplicationPipelineTaskRequest request) { + // 查询明细 + Long id = request.getId(); + ApplicationPipelineTaskDO task = applicationPipelineTaskDAO.selectById(id); + Valid.notNull(task, MessageConst.PIPELINE_TASK_ABSENT); + PipelineStatus status = PipelineStatus.of(task.getExecStatus()); + if (!PipelineStatus.WAIT_AUDIT.equals(status) && !PipelineStatus.AUDIT_REJECT.equals(status)) { + throw Exceptions.argument(MessageConst.ILLEGAL_STATUS); + } + AuditStatus auditStatus = AuditStatus.of(request.getAuditStatus()); + UserDTO user = Currents.getUser(); + // 更新 + ApplicationPipelineTaskDO update = new ApplicationPipelineTaskDO(); + update.setId(id); + update.setAuditUserId(user.getId()); + update.setAuditUserName(user.getUsername()); + update.setAuditTime(new Date()); + update.setAuditReason(request.getAuditReason()); + final boolean resolve = AuditStatus.RESOLVE.equals(auditStatus); + // 发送站内信 + Map params = Maps.newMap(); + params.put(EventKeys.ID, task.getId()); + params.put(EventKeys.NAME, task.getPipelineName()); + params.put(EventKeys.TITLE, task.getExecTitle()); + if (resolve) { + // 通过 + final boolean timedExec = TimedType.TIMED.getType().equals(task.getTimedExec()); + if (!timedExec) { + update.setExecStatus(PipelineStatus.WAIT_RUNNABLE.getStatus()); + } else { + update.setExecStatus(PipelineStatus.WAIT_SCHEDULE.getStatus()); + // 提交任务 + taskRegister.submit(TaskType.PIPELINE, task.getTimedExecTime(), id); + } + webSideMessageService.addMessage(MessageType.PIPELINE_AUDIT_RESOLVE, id, task.getCreateUserId(), task.getCreateUserName(), params); + } else { + // 驳回 + update.setExecStatus(PipelineStatus.AUDIT_REJECT.getStatus()); + webSideMessageService.addMessage(MessageType.PIPELINE_AUDIT_REJECT, id, task.getCreateUserId(), task.getCreateUserName(), params); + } + int effect = applicationPipelineTaskDAO.updateById(update); + this.setEventLogParams(task); + EventParamsHolder.addParam(EventKeys.OPERATOR, resolve ? CnConst.RESOLVE : CnConst.REJECT); + return effect; + } + + @Override + public Long copyPipeline(Long id) { + // 查询明细 + ApplicationPipelineTaskDO task = applicationPipelineTaskDAO.selectById(id); + Valid.notNull(task, MessageConst.PIPELINE_TASK_ABSENT); + // 查询详情 + List taskDetails = applicationPipelineTaskDetailService.selectTaskDetails(id); + Valid.notEmpty(taskDetails, MessageConst.PIPELINE_DETAIL_ABSENT); + // 提交 + ApplicationPipelineTaskRequest request = new ApplicationPipelineTaskRequest(); + request.setPipelineId(task.getPipelineId()); + request.setTitle(task.getExecTitle()); + request.setDescription(task.getExecDescription()); + request.setTimedExec(TimedType.NORMAL.getType()); + // 设置详情 + List details = taskDetails.stream().map(s -> { + ApplicationPipelineStageConfigDTO config = JSON.parseObject(s.getStageConfig(), ApplicationPipelineStageConfigDTO.class); + ApplicationPipelineTaskDetailRequest detail = Converts.to(config, ApplicationPipelineTaskDetailRequest.class); + detail.setId(s.getPipelineDetailId()); + return detail; + }).collect(Collectors.toList()); + request.setDetails(details); + return SpringHolder.getBean(ApplicationPipelineTaskService.class).submitPipelineTask(request); + } + + @Override + public void execPipeline(Long id, boolean isSystem) { + // 查询明细 + ApplicationPipelineTaskDO task = applicationPipelineTaskDAO.selectById(id); + Valid.notNull(task, MessageConst.PIPELINE_TASK_ABSENT); + PipelineStatus status = PipelineStatus.of(task.getExecStatus()); + if (!PipelineStatus.WAIT_RUNNABLE.equals(status) + && !PipelineStatus.WAIT_SCHEDULE.equals(status)) { + throw Exceptions.argument(MessageConst.ILLEGAL_STATUS); + } + // 检查构建版本 + this.checkPipelineExecutable(task); + // 更新执行人 + ApplicationPipelineTaskDO update = new ApplicationPipelineTaskDO(); + update.setId(id); + update.setUpdateTime(new Date()); + if (isSystem) { + update.setExecUserId(task.getCreateUserId()); + update.setExecUserName(task.getCreateUserName()); + } else { + UserDTO user = Currents.getUser(); + update.setExecUserId(user.getId()); + update.setExecUserName(user.getUsername()); + // 移除 + taskRegister.cancel(TaskType.PIPELINE, id); + } + applicationPipelineTaskDAO.updateById(update); + // 执行 + new PipelineProcessor(id).exec(); + // 设置日志参数 + this.setEventLogParams(task); + EventParamsHolder.addParam(EventKeys.SYSTEM, isSystem); + } + + /** + * 检查流水线是否可执行 + * + * @param task task + */ + private void checkPipelineExecutable(ApplicationPipelineTaskDO task) { + // 检查环境是否存在 + Long profileId = task.getProfileId(); + Valid.notNull(applicationProfileDAO.selectById(profileId), MessageConst.PROFILE_ABSENT); + // 查询详情 + List details = applicationPipelineTaskDetailService.selectTaskDetails(task.getId()); + Valid.notEmpty(details, MessageConst.PIPELINE_DETAIL_ABSENT); + // 检查应用是否存在 + Set appIdList = details.stream() + .map(ApplicationPipelineTaskDetailDO::getAppId) + .collect(Collectors.toSet()); + Map appMap = applicationInfoDAO.selectBatchIds(appIdList).stream() + .collect(Collectors.toMap(ApplicationInfoDO::getId, Function.identity())); + // 检查操作详情 + Set appBuildStage = Sets.newSet(); + for (ApplicationPipelineTaskDetailDO detail : details) { + // 检查应用是否存在 + Long appId = detail.getAppId(); + ApplicationInfoDO appInfo = appMap.get(appId); + Valid.notNull(appInfo, detail.getAppName() + MessageConst.ABSENT); + String appSymbol = CnConst.APP + Strings.SPACE + appInfo.getAppName() + Strings.SPACE; + // 检查构建版本 + if (StageType.BUILD.getType().equals(detail.getStageType())) { + appBuildStage.add(appId); + } else if (StageType.RELEASE.getType().equals(detail.getStageType())) { + ApplicationPipelineStageConfigDTO stageConfig = JSON.parseObject(detail.getStageConfig(), ApplicationPipelineStageConfigDTO.class); + Long checkBuildId = stageConfig.getBuildId(); + ApplicationBuildDO checkBuild; + if (checkBuildId == null) { + // 查询最新版本 如果上面有构建操作则跳过 + boolean hasBuildDetail = appBuildStage.stream().anyMatch(appId::equals); + if (hasBuildDetail) { + continue; + } + // 如果没有构建流水线操作则查询最后的构建版本 + List lastBuild = applicationBuildDAO.selectBuildReleaseList(appId, profileId, 1); + Valid.notEmpty(lastBuild, Strings.format(MessageConst.APP_LAST_BUILD_VERSION_ABSENT, appSymbol)); + checkBuildId = lastBuild.get(0).getId(); + } + // 查询构建版本 + checkBuild = applicationBuildDAO.selectById(checkBuildId); + Valid.notNull(checkBuild, appSymbol + MessageConst.BUILD_ABSENT); + try { + // 检查产物是否存在 + applicationBuildService.checkBuildBundlePath(checkBuild); + } catch (Exception e) { + throw Exceptions.argument(appSymbol + Letters.POUND + checkBuild.getBuildSeq() + Strings.SPACE + e.getMessage(), e); + } + } + } + } + + @Override + public Integer deletePipeline(List idList) { + // 查询明细 + List pipelineStatus = applicationPipelineTaskDAO.selectBatchIds(idList); + Valid.notEmpty(pipelineStatus, MessageConst.PIPELINE_TASK_ABSENT); + boolean canDelete = pipelineStatus.stream() + .map(ApplicationPipelineTaskDO::getExecStatus) + .noneMatch(s -> PipelineStatus.WAIT_SCHEDULE.getStatus().equals(s) + || PipelineStatus.RUNNABLE.getStatus().equals(s)); + Valid.isTrue(canDelete, MessageConst.ILLEGAL_STATUS); + // 删除主表 + int effect = applicationPipelineTaskDAO.deleteBatchIds(idList); + // 删除详情 + effect += applicationPipelineTaskDetailService.deleteByTaskIdList(idList); + // 删除日志 + effect += applicationPipelineTaskLogService.deleteByTaskIdList(idList); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID_LIST, idList); + EventParamsHolder.addParam(EventKeys.COUNT, idList.size()); + return effect; + } + + @Override + public void setPipelineTimedExec(Long id, Date timedExecDate) { + // 查询明细 + ApplicationPipelineTaskDO task = applicationPipelineTaskDAO.selectById(id); + Valid.notNull(task, MessageConst.PIPELINE_TASK_ABSENT); + PipelineStatus status = PipelineStatus.of(task.getExecStatus()); + if (!PipelineStatus.WAIT_RUNNABLE.equals(status) && !PipelineStatus.WAIT_SCHEDULE.equals(status)) { + throw Exceptions.argument(MessageConst.ILLEGAL_STATUS); + } + // 取消调度任务 + taskRegister.cancel(TaskType.PIPELINE, id); + // 更新状态 + ApplicationPipelineTaskDO update = new ApplicationPipelineTaskDO(); + update.setId(id); + update.setUpdateTime(new Date()); + update.setExecStatus(PipelineStatus.WAIT_SCHEDULE.getStatus()); + update.setTimedExec(TimedType.TIMED.getType()); + update.setTimedExecTime(timedExecDate); + applicationPipelineTaskDAO.updateById(update); + // 提交任务 + taskRegister.submit(TaskType.PIPELINE, timedExecDate, id); + // 设置日志参数 + this.setEventLogParams(task); + EventParamsHolder.addParam(EventKeys.TIME, Dates.format(timedExecDate)); + } + + @Override + public void cancelPipelineTimedExec(Long id) { + // 查询明细 + ApplicationPipelineTaskDO task = applicationPipelineTaskDAO.selectById(id); + Valid.notNull(task, MessageConst.PIPELINE_TASK_ABSENT); + PipelineStatus status = PipelineStatus.of(task.getExecStatus()); + if (!PipelineStatus.WAIT_SCHEDULE.equals(status)) { + throw Exceptions.argument(MessageConst.ILLEGAL_STATUS); + } + // 更新状态 + ApplicationPipelineTaskDO update = new ApplicationPipelineTaskDO(); + update.setId(id); + update.setUpdateTime(new Date()); + update.setTimedExec(TimedType.NORMAL.getType()); + update.setExecStatus(PipelineStatus.WAIT_RUNNABLE.getStatus()); + applicationPipelineTaskDAO.updateById(update); + applicationPipelineTaskDAO.setTimedExecTimeNull(id); + // 取消调度任务 + taskRegister.cancel(TaskType.PIPELINE, id); + // 设置日志参数 + this.setEventLogParams(task); + } + + @Override + public void terminateExec(Long id) { + // 查询明细 + ApplicationPipelineTaskDO task = applicationPipelineTaskDAO.selectById(id); + Valid.notNull(task, MessageConst.PIPELINE_TASK_ABSENT); + PipelineStatus status = PipelineStatus.of(task.getExecStatus()); + if (!PipelineStatus.RUNNABLE.equals(status)) { + throw Exceptions.argument(MessageConst.ILLEGAL_STATUS); + } + // 获取实例 + IPipelineProcessor session = pipelineSessionHolder.getSession(id); + Valid.notNull(session, MessageConst.SESSION_PRESENT); + // 调用终止 + session.terminate(); + // 设置日志参数 + this.setEventLogParams(task); + } + + @Override + public void terminateExecDetail(Long id, Long detailId) { + this.skipOrTerminateTaskDetail(id, detailId, true); + } + + @Override + public void skipExecDetail(Long id, Long detailId) { + this.skipOrTerminateTaskDetail(id, detailId, false); + } + + /** + * 跳过或终止任务操作 + * + * @param id id + * @param detailId detailId + * @param terminate 终止/跳过 + */ + private void skipOrTerminateTaskDetail(Long id, Long detailId, boolean terminate) { + // 查询数据 + ApplicationPipelineTaskDO task = applicationPipelineTaskDAO.selectById(id); + Valid.notNull(task, MessageConst.PIPELINE_TASK_ABSENT); + // 检查状态 + Valid.isTrue(PipelineStatus.RUNNABLE.getStatus().equals(task.getExecStatus()), MessageConst.ILLEGAL_STATUS); + ApplicationPipelineTaskDetailDO detail = applicationPipelineTaskDetailDAO.selectById(detailId); + Valid.notNull(detail, MessageConst.UNKNOWN_DATA); + // 检查状态 + if (terminate) { + Valid.isTrue(PipelineDetailStatus.RUNNABLE.getStatus().equals(detail.getExecStatus()), MessageConst.ILLEGAL_STATUS); + } else { + Valid.isTrue(PipelineDetailStatus.WAIT.getStatus().equals(detail.getExecStatus()), MessageConst.ILLEGAL_STATUS); + } + // 停止 + IPipelineProcessor session = pipelineSessionHolder.getSession(id); + Valid.notNull(session, MessageConst.SESSION_PRESENT); + if (terminate) { + session.terminateDetail(detailId); + } else { + session.skipDetail(detailId); + } + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.DETAIL_ID, detailId); + EventParamsHolder.addParam(EventKeys.NAME, task.getPipelineName()); + EventParamsHolder.addParam(EventKeys.TITLE, task.getExecTitle()); + EventParamsHolder.addParam(EventKeys.STAGE, StageType.of(detail.getStageType()).getLabel()); + EventParamsHolder.addParam(EventKeys.APP_NAME, detail.getAppName()); + } + + @Override + public ApplicationPipelineTaskStatusVO getTaskStatus(Long id) { + // 查询任务状态 + ApplicationPipelineTaskDO task = applicationPipelineTaskDAO.selectStatusById(id); + Valid.notNull(task, MessageConst.PIPELINE_TASK_ABSENT); + ApplicationPipelineTaskStatusVO status = Converts.to(task, ApplicationPipelineTaskStatusVO.class); + // 查询详情状态 + List details = applicationPipelineTaskDetailDAO.selectStatusByTaskId(id); + List detailStatus = Converts.toList(details, ApplicationPipelineTaskDetailStatusVO.class); + status.setDetails(detailStatus); + return status; + } + + @Override + public List getTaskStatusList(List idList, List detailIdList) { + // 查询任务状态 + List taskList = applicationPipelineTaskDAO.selectStatusByIdList(idList); + List statusList = Converts.toList(taskList, ApplicationPipelineTaskStatusVO.class); + // 查询详情状态 + if (Lists.isNotEmpty(detailIdList)) { + List detailList = applicationPipelineTaskDetailDAO.selectStatusByIdList(detailIdList); + for (ApplicationPipelineTaskStatusVO status : statusList) { + List detailStatus = detailList.stream() + .filter(s -> s.getTaskId().equals(status.getId())) + .collect(Collectors.toList()); + status.setDetails(Converts.toList(detailStatus, ApplicationPipelineTaskDetailStatusVO.class)); + } + } + return statusList; + } + + /** + * 设置流水线明细 + * + * @param request request + * @param pipeline pipeline + * @param profile profile + * @return task + */ + private ApplicationPipelineTaskDO setPipelineTask(ApplicationPipelineTaskRequest request, ApplicationPipelineDO pipeline, ApplicationProfileDO profile) { + // 获取指定构建版本的发布操作 + List specifiedBuildList = request.getDetails().stream() + .filter(s -> StageType.RELEASE.getType().equals(s.getStageType())) + .map(ApplicationPipelineTaskDetailRequest::getBuildId) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (!specifiedBuildList.isEmpty()) { + List buildList = applicationBuildDAO.selectBatchIds(specifiedBuildList); + Valid.isTrue(buildList.size() == specifiedBuildList.size(), MessageConst.BUILD_ABSENT); + // 检查构建产物是否存在 + for (ApplicationBuildDO build : buildList) { + try { + applicationBuildService.checkBuildBundlePath(build); + } catch (Exception e) { + String message = Strings.format("{} #{} {}", build.getAppName(), build.getBuildSeq(), e.getMessage()); + throw Exceptions.argument(message, e); + } + } + } + // 设置流水线 + UserDTO user = Currents.getUser(); + ApplicationPipelineTaskDO pipelineTask = new ApplicationPipelineTaskDO(); + pipelineTask.setPipelineId(pipeline.getId()); + pipelineTask.setPipelineName(pipeline.getPipelineName()); + pipelineTask.setProfileId(profile.getId()); + pipelineTask.setProfileName(profile.getProfileName()); + pipelineTask.setProfileTag(profile.getProfileTag()); + pipelineTask.setExecTitle(request.getTitle()); + pipelineTask.setExecDescription(request.getDescription()); + pipelineTask.setTimedExec(request.getTimedExec()); + pipelineTask.setTimedExecTime(request.getTimedExecTime()); + pipelineTask.setCreateUserId(user.getId()); + pipelineTask.setCreateUserName(user.getUsername()); + // 设置审核信息 + this.setCreateAuditInfo(pipelineTask, user, Const.ENABLE.equals(profile.getReleaseAudit())); + return pipelineTask; + } + + /** + * 设置流水线详情明细 + * + * @param taskId taskId + * @param appInfoMap appInfoMap + * @param pipelineDetails pipelineDetails + * @param requestDetailsMap requestDetailsMap + * @return details + */ + private List setPipelineTaskDetails(Long taskId, Map appInfoMap, + List pipelineDetails, + Map requestDetailsMap) { + List taskDetailList = Lists.newList(); + for (ApplicationPipelineDetailDO pipelineDetail : pipelineDetails) { + // 设置流水操作 + ApplicationPipelineTaskDetailDO taskDetail = new ApplicationPipelineTaskDetailDO(); + taskDetail.setPipelineId(pipelineDetail.getPipelineId()); + taskDetail.setPipelineDetailId(pipelineDetail.getId()); + taskDetail.setTaskId(taskId); + // 应用 + ApplicationInfoDO app = appInfoMap.get(pipelineDetail.getAppId()); + taskDetail.setAppId(app.getId()); + taskDetail.setAppName(app.getAppName()); + taskDetail.setAppTag(app.getAppTag()); + taskDetail.setStageType(pipelineDetail.getStageType()); + // 阶段配置 + ApplicationPipelineTaskDetailRequest requestDetail = requestDetailsMap.get(pipelineDetail.getId()); + ApplicationPipelineStageConfigDTO stageConfig = Converts.to(requestDetail, ApplicationPipelineStageConfigDTO.class); + taskDetail.setStageConfig(JSON.toJSONString(stageConfig)); + taskDetail.setExecStatus(PipelineDetailStatus.WAIT.getStatus()); + taskDetailList.add(taskDetail); + } + return taskDetailList; + } + + /** + * 创建时设置是否需要审核 + * + * @param task task + * @param user user + * @param needAudit needAudit + */ + private void setCreateAuditInfo(ApplicationPipelineTaskDO task, UserDTO user, boolean needAudit) { + boolean isAdmin = RoleType.isAdministrator(user.getRoleType()); + // 需要审核 & 不是管理员 + if (needAudit && !isAdmin) { + task.setExecStatus(PipelineStatus.WAIT_AUDIT.getStatus()); + } else { + if (TimedType.TIMED.getType().equals(task.getTimedExec())) { + task.setExecStatus(PipelineStatus.WAIT_SCHEDULE.getStatus()); + } else { + task.setExecStatus(PipelineStatus.WAIT_RUNNABLE.getStatus()); + } + task.setAuditUserId(user.getId()); + task.setAuditUserName(user.getUsername()); + task.setAuditTime(new Date()); + if (isAdmin) { + task.setAuditReason(MessageConst.AUTO_AUDIT_RESOLVE); + } else { + task.setAuditReason(MessageConst.AUDIT_NOT_REQUIRED); + } + } + } + + /** + * 设置操作日志参数 + * + * @param task task + */ + private void setEventLogParams(ApplicationPipelineTaskDO task) { + EventParamsHolder.addParam(EventKeys.ID, task.getId()); + EventParamsHolder.addParam(EventKeys.PIPELINE_ID, task.getPipelineId()); + EventParamsHolder.addParam(EventKeys.NAME, task.getPipelineName()); + EventParamsHolder.addParam(EventKeys.TITLE, task.getExecTitle()); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationProfileServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationProfileServiceImpl.java new file mode 100644 index 0000000..104c673 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationProfileServiceImpl.java @@ -0,0 +1,224 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.ops.constant.KeyConst; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.dao.ApplicationProfileDAO; +import cn.orionsec.ops.entity.domain.ApplicationProfileDO; +import cn.orionsec.ops.entity.dto.app.ApplicationProfileDTO; +import cn.orionsec.ops.entity.request.app.ApplicationProfileRequest; +import cn.orionsec.ops.entity.vo.app.ApplicationProfileFastVO; +import cn.orionsec.ops.entity.vo.app.ApplicationProfileVO; +import cn.orionsec.ops.service.api.*; +import cn.orionsec.ops.utils.DataQuery; +import cn.orionsec.ops.utils.EventParamsHolder; +import cn.orionsec.ops.utils.Valid; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +@Service("applicationProfileService") +public class ApplicationProfileServiceImpl implements ApplicationProfileService { + + @Resource + private ApplicationProfileDAO applicationProfileDAO; + + @Resource + private ApplicationMachineService applicationMachineService; + + @Resource + private ApplicationEnvService applicationEnvService; + + @Resource + private ApplicationActionService applicationActionService; + + @Resource + private ApplicationPipelineService applicationPipelineService; + + @Resource + private RedisTemplate redisTemplate; + + @Override + public Long addProfile(ApplicationProfileRequest request) { + String name = request.getName(); + String tag = request.getTag(); + // 重复名称检查 + this.checkNamePresent(null, name); + // 重复标识检查 + this.checkTagPresent(null, tag); + // 插入 + ApplicationProfileDO insert = new ApplicationProfileDO(); + insert.setProfileName(name); + insert.setProfileTag(tag); + insert.setDescription(request.getDescription()); + insert.setReleaseAudit(request.getReleaseAudit()); + applicationProfileDAO.insert(insert); + Long id = insert.getId(); + // 插入缓存 + this.setProfileToCache(insert); + // 设置日志参数 + EventParamsHolder.addParams(insert); + return id; + } + + @Override + public Integer updateProfile(ApplicationProfileRequest request) { + // 检查是否存在 + Long id = request.getId(); + ApplicationProfileDO beforeProfile = applicationProfileDAO.selectById(id); + Valid.notNull(beforeProfile, MessageConst.PROFILE_ABSENT); + String name = request.getName(); + String tag = request.getTag(); + // 重复名称检查 + this.checkNamePresent(id, name); + // 重复标识检查 + this.checkTagPresent(id, tag); + // 修改 + ApplicationProfileDO update = new ApplicationProfileDO(); + update.setId(id); + update.setProfileName(name); + update.setProfileTag(tag); + update.setDescription(request.getDescription()); + update.setReleaseAudit(request.getReleaseAudit()); + update.setUpdateTime(new Date()); + int updateEffect = applicationProfileDAO.updateById(update); + // 修改缓存 + this.setProfileToCache(update); + // 设置日志参数 + EventParamsHolder.addParams(update); + EventParamsHolder.addParam(EventKeys.NAME, beforeProfile.getProfileName()); + return updateEffect; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer deleteProfile(Long id) { + // 检查是否存在 + ApplicationProfileDO beforeProfile = applicationProfileDAO.selectById(id); + Valid.notNull(beforeProfile, MessageConst.PROFILE_ABSENT); + int effect = 0; + // 删除环境 + effect += applicationProfileDAO.deleteById(id); + // 删除环境变量 + effect += applicationEnvService.deleteAppProfileEnvByAppProfileId(null, id); + // 删除环境机器 + effect += applicationMachineService.deleteAppMachineByAppProfileId(null, id); + // 删除环境构建发布流程 + effect += applicationActionService.deleteAppActionByAppProfileId(null, id); + // 删除应用流水线 + effect += applicationPipelineService.deleteByProfileId(id); + // 删除缓存 + redisTemplate.opsForHash().delete(KeyConst.DATA_PROFILE_KEY, id.toString()); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.NAME, beforeProfile.getProfileName()); + return effect; + } + + @Override + public DataGrid listProfiles(ApplicationProfileRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(Objects.nonNull(request.getId()), ApplicationProfileDO::getId, request.getId()) + .like(!Strings.isBlank(request.getName()), ApplicationProfileDO::getProfileName, request.getName()) + .like(!Strings.isBlank(request.getTag()), ApplicationProfileDO::getProfileTag, request.getTag()) + .like(!Strings.isBlank(request.getDescription()), ApplicationProfileDO::getDescription, request.getDescription()); + return DataQuery.of(applicationProfileDAO) + .wrapper(wrapper) + .page(request) + .dataGrid(ApplicationProfileVO.class); + } + + @Override + public List fastListProfiles() { + List list = Lists.newList(); + // 查询缓存 + List profileCache = redisTemplate.opsForHash().values(KeyConst.DATA_PROFILE_KEY); + if (Lists.isEmpty(profileCache)) { + // 查库 + List profiles = applicationProfileDAO.selectList(null) + .stream() + .map(s -> Converts.to(s, ApplicationProfileDTO.class)) + .collect(Collectors.toList()); + // 设置缓存 + Map cacheData = profiles.stream() + .collect(Collectors.toMap(s -> s.getId().toString(), JSON::toJSONString)); + redisTemplate.opsForHash().putAll(KeyConst.DATA_PROFILE_KEY, cacheData); + redisTemplate.expire(KeyConst.DATA_PROFILE_KEY, KeyConst.DATA_PROFILE_EXPIRE, TimeUnit.SECONDS); + // 返回 + profiles.stream().map(p -> Converts.to(p, ApplicationProfileFastVO.class)) + .forEach(list::add); + } else { + // 返回 + profileCache.stream().map(p -> JSON.parseObject(p.toString(), ApplicationProfileDTO.class)) + .map(p -> Converts.to(p, ApplicationProfileFastVO.class)) + .forEach(list::add); + // 排序 + list.sort(Comparator.comparing(ApplicationProfileFastVO::getId)); + } + return list; + } + + @Override + public ApplicationProfileVO getProfile(Long id) { + ApplicationProfileDO profile = applicationProfileDAO.selectById(id); + Valid.notNull(profile, MessageConst.UNKNOWN_DATA); + return Converts.to(profile, ApplicationProfileVO.class); + } + + @Override + public void clearProfileCache() { + redisTemplate.delete(KeyConst.DATA_PROFILE_KEY); + } + + /** + * 检查名称是否存在 + * + * @param id id + * @param name name + */ + private void checkNamePresent(Long id, String name) { + LambdaQueryWrapper presentWrapper = new LambdaQueryWrapper() + .ne(id != null, ApplicationProfileDO::getId, id) + .eq(ApplicationProfileDO::getProfileName, name); + boolean present = DataQuery.of(applicationProfileDAO).wrapper(presentWrapper).present(); + Valid.isTrue(!present, MessageConst.NAME_PRESENT); + } + + /** + * 检查唯一标识是否存在 + * + * @param id id + * @param tag tag + */ + private void checkTagPresent(Long id, String tag) { + LambdaQueryWrapper presentWrapper = new LambdaQueryWrapper() + .ne(id != null, ApplicationProfileDO::getId, id) + .eq(ApplicationProfileDO::getProfileTag, tag); + boolean present = DataQuery.of(applicationProfileDAO).wrapper(presentWrapper).present(); + Valid.isTrue(!present, MessageConst.TAG_PRESENT); + } + + /** + * 设置环境缓存 + * + * @param profile profile + */ + private void setProfileToCache(ApplicationProfileDO profile) { + ApplicationProfileDTO profileDTO = Converts.to(profile, ApplicationProfileDTO.class); + redisTemplate.opsForHash().put(KeyConst.DATA_PROFILE_KEY, profile.getId().toString(), JSON.toJSONString(profileDTO)); + redisTemplate.expire(KeyConst.DATA_PROFILE_KEY, KeyConst.DATA_PROFILE_EXPIRE, TimeUnit.SECONDS); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationReleaseMachineServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationReleaseMachineServiceImpl.java new file mode 100644 index 0000000..270d2e6 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationReleaseMachineServiceImpl.java @@ -0,0 +1,66 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.dao.ApplicationReleaseMachineDAO; +import cn.orionsec.ops.entity.domain.ApplicationReleaseMachineDO; +import cn.orionsec.ops.service.api.ApplicationReleaseMachineService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service("applicationReleaseMachineService") +public class ApplicationReleaseMachineServiceImpl implements ApplicationReleaseMachineService { + + @Resource + private ApplicationReleaseMachineDAO applicationReleaseMachineDAO; + + @Override + public List getReleaseMachines(Long releaseId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(ApplicationReleaseMachineDO::getReleaseId, releaseId); + return applicationReleaseMachineDAO.selectList(wrapper); + } + + @Override + public List getReleaseMachines(List releaseIdList) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .in(ApplicationReleaseMachineDO::getReleaseId, releaseIdList) + .orderByAsc(ApplicationReleaseMachineDO::getId); + return applicationReleaseMachineDAO.selectList(wrapper); + } + + @Override + public List getReleaseMachineIdList(List releaseIdList) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .select(ApplicationReleaseMachineDO::getId) + .in(ApplicationReleaseMachineDO::getReleaseId, releaseIdList) + .orderByAsc(ApplicationReleaseMachineDO::getId); + return applicationReleaseMachineDAO.selectList(wrapper).stream() + .map(ApplicationReleaseMachineDO::getId) + .collect(Collectors.toList()); + } + + @Override + public Integer deleteByReleaseId(Long releaseId) { + LambdaQueryWrapper machineWrapper = new LambdaQueryWrapper() + .eq(ApplicationReleaseMachineDO::getReleaseId, releaseId); + return applicationReleaseMachineDAO.delete(machineWrapper); + } + + @Override + public String getReleaseMachineLogPath(Long id) { + return Optional.ofNullable(applicationReleaseMachineDAO.selectById(id)) + .map(ApplicationReleaseMachineDO::getLogPath) + .filter(Strings::isNotBlank) + .map(s -> Files1.getPath(SystemEnvAttr.LOG_PATH.getValue(), s)) + .orElse(null); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationReleaseServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationReleaseServiceImpl.java new file mode 100644 index 0000000..974a925 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationReleaseServiceImpl.java @@ -0,0 +1,856 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.collect.MutableLinkedHashMap; +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.CnConst; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.app.*; +import cn.orionsec.ops.constant.common.AuditStatus; +import cn.orionsec.ops.constant.common.ExceptionHandlerType; +import cn.orionsec.ops.constant.common.SerialType; +import cn.orionsec.ops.constant.env.EnvConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.message.MessageType; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.constant.user.RoleType; +import cn.orionsec.ops.dao.*; +import cn.orionsec.ops.entity.domain.*; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.entity.request.app.ApplicationReleaseAuditRequest; +import cn.orionsec.ops.entity.request.app.ApplicationReleaseRequest; +import cn.orionsec.ops.entity.vo.app.*; +import cn.orionsec.ops.handler.app.release.IReleaseProcessor; +import cn.orionsec.ops.handler.app.release.ReleaseSessionHolder; +import cn.orionsec.ops.service.api.*; +import cn.orionsec.ops.task.TaskRegister; +import cn.orionsec.ops.task.TaskType; +import cn.orionsec.ops.utils.*; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.io.File; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service("applicationReleaseService") +public class ApplicationReleaseServiceImpl implements ApplicationReleaseService { + + @Resource + private ApplicationReleaseDAO applicationReleaseDAO; + + @Resource + private ApplicationReleaseMachineService applicationReleaseMachineService; + + @Resource + private ApplicationReleaseMachineDAO applicationReleaseMachineDAO; + + @Resource + private ApplicationActionLogService applicationActionLogService; + + @Resource + private ApplicationActionLogDAO applicationActionLogDAO; + + @Resource + private ApplicationInfoDAO applicationInfoDAO; + + @Resource + private ApplicationProfileDAO applicationProfileDAO; + + @Resource + private ApplicationActionService applicationActionService; + + @Resource + private ApplicationEnvService applicationEnvService; + + @Resource + private ApplicationMachineService applicationMachineService; + + @Resource + private ApplicationBuildDAO applicationBuildDAO; + + @Resource + private ApplicationBuildService applicationBuildService; + + @Resource + private MachineInfoDAO machineInfoDAO; + + @Resource + private MachineEnvService machineEnvService; + + @Resource + private SystemEnvService systemEnvService; + + @Resource + private WebSideMessageService webSideMessageService; + + @Resource + private ReleaseSessionHolder releaseSessionHolder; + + @Resource + private TaskRegister taskRegister; + + @Override + public DataGrid getReleaseList(ApplicationReleaseRequest request) { + Long userId = Currents.getUserId(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .like(!Strings.isBlank(request.getTitle()), ApplicationReleaseDO::getReleaseTitle, request.getTitle()) + .like(!Strings.isBlank(request.getDescription()), ApplicationReleaseDO::getReleaseDescription, request.getDescription()) + .like(!Strings.isBlank(request.getAppName()), ApplicationReleaseDO::getAppName, request.getAppName()) + .eq(Objects.nonNull(request.getId()), ApplicationReleaseDO::getId, request.getId()) + .eq(Objects.nonNull(request.getAppId()), ApplicationReleaseDO::getAppId, request.getAppId()) + .eq(Objects.nonNull(request.getProfileId()), ApplicationReleaseDO::getProfileId, request.getProfileId()) + .eq(Objects.nonNull(request.getStatus()), ApplicationReleaseDO::getReleaseStatus, request.getStatus()) + .and(Const.ENABLE.equals(request.getOnlyMyself()), w -> w + .eq(ApplicationReleaseDO::getCreateUserId, userId) + .or() + .eq(ApplicationReleaseDO::getReleaseUserId, userId)) + .orderByDesc(ApplicationReleaseDO::getId); + // 查询列表 + DataGrid dataGrid = DataQuery.of(applicationReleaseDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(ApplicationReleaseListVO.class); + if (Const.ENABLE.equals(request.getQueryMachine())) { + // 查询发布机器 + List machineIdList = dataGrid.stream() + .map(ApplicationReleaseListVO::getId) + .collect(Collectors.toList()); + if (!machineIdList.isEmpty()) { + // 查询机器 + Map> releaseMachineMap = applicationReleaseMachineService.getReleaseMachines(machineIdList) + .stream() + .map(s -> Converts.to(s, ApplicationReleaseMachineVO.class)) + .collect(Collectors.groupingBy(ApplicationReleaseMachineVO::getReleaseId)); + dataGrid.forEach(release -> release.setMachines(releaseMachineMap.get(release.getId()))); + } + } + return dataGrid; + } + + @Override + public List getReleaseMachineList(Long id) { + List machines = applicationReleaseMachineService.getReleaseMachines(id); + return Converts.toList(machines, ApplicationReleaseMachineVO.class); + } + + @Override + public ApplicationReleaseDetailVO getReleaseDetail(ApplicationReleaseRequest request) { + // 查询 + Long id = request.getId(); + ApplicationReleaseDO release = applicationReleaseDAO.selectById(id); + Valid.notNull(release, MessageConst.RELEASE_ABSENT); + ApplicationReleaseDetailVO vo = Converts.to(release, ApplicationReleaseDetailVO.class); + // 设置action + List actions = JSON.parseArray(release.getActionConfig(), ApplicationActionDO.class); + vo.setActions(Converts.toList(actions, ApplicationActionVO.class)); + // 查询机器 + List machines = applicationReleaseMachineService.getReleaseMachines(id).stream() + .map(s -> Converts.to(s, ApplicationReleaseMachineVO.class)) + .collect(Collectors.toList()); + vo.setMachines(machines); + // 查询操作 + if (Const.ENABLE.equals(request.getQueryAction())) { + List releaseMachineIdList = machines.stream().map(ApplicationReleaseMachineVO::getId).collect(Collectors.toList()); + if (!releaseMachineIdList.isEmpty()) { + // 机器操作 + List machineActions = applicationActionLogService.selectActionByRelIdList(releaseMachineIdList, StageType.RELEASE); + Map> machineActionsMap = machineActions.stream() + .map(s -> Converts.to(s, ApplicationActionLogVO.class)) + .collect(Collectors.groupingBy(ApplicationActionLogVO::getRelId)); + for (ApplicationReleaseMachineVO machine : machines) { + machine.setActions(machineActionsMap.get(machine.getId())); + } + } + } + return vo; + } + + @Override + public ApplicationReleaseMachineVO getReleaseMachineDetail(Long releaseMachineId) { + // 查询数据 + ApplicationReleaseMachineDO machine = applicationReleaseMachineDAO.selectById(releaseMachineId); + Valid.notNull(machine, MessageConst.RELEASE_ABSENT); + ApplicationReleaseMachineVO vo = Converts.to(machine, ApplicationReleaseMachineVO.class); + // 查询action + List actions = applicationActionLogService.getActionLogsByRelId(releaseMachineId, StageType.RELEASE); + vo.setActions(actions); + return vo; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Long submitAppRelease(ApplicationReleaseRequest request) { + Long appId = request.getAppId(); + Long profileId = request.getProfileId(); + // 查询应用 + ApplicationInfoDO app = applicationInfoDAO.selectById(appId); + Valid.notNull(app, MessageConst.APP_ABSENT); + // 查询环境 + ApplicationProfileDO profile = applicationProfileDAO.selectById(profileId); + Valid.notNull(profile, MessageConst.PROFILE_ABSENT); + // 查询应用执行块 + List actions = applicationActionService.getAppProfileActions(appId, profileId, StageType.RELEASE.getType()); + Valid.notEmpty(actions, MessageConst.APP_PROFILE_NOT_CONFIGURED); + // 检查发布机器 + this.checkReleaseMachine(request); + // 查询机器 + Map machines = this.getReleaseMachineInfo(request); + // 查询构建信息 + ApplicationBuildDO build = applicationBuildDAO.selectById(request.getBuildId()); + Valid.notNull(build, MessageConst.BUILD_ABSENT); + // 设置主表信息 + ApplicationReleaseDO release = this.setReleaseInfo(request, app, profile, build, actions); + applicationReleaseDAO.insert(release); + Long releaseId = release.getId(); + // 设置机器信息 + List releaseMachines = this.setReleaseMachineInfo(request, machines, releaseId); + releaseMachines.forEach(applicationReleaseMachineDAO::insert); + // 检查是否包含命令 + final boolean hasEnvCommand = actions.stream() + .filter(s -> ActionType.RELEASE_COMMAND.getType().equals(s.getActionType())) + .map(ApplicationActionDO::getActionCommand) + .filter(Strings::isNotBlank) + .anyMatch(s -> s.contains(EnvConst.SYMBOL)); + Map releaseEnv = Maps.newMap(); + if (hasEnvCommand) { + // 查询应用环境变量 + releaseEnv.putAll(applicationEnvService.getAppProfileFullEnv(appId, profileId)); + // 添加系统环境变量 + releaseEnv.putAll(systemEnvService.getFullSystemEnv()); + // 添加发布环境变量 + releaseEnv.putAll(this.getReleaseEnv(build, release)); + } + // 设置部署操作 + List releaseActions = this.setReleaseActions(actions, releaseMachines, releaseEnv, hasEnvCommand); + releaseActions.forEach(applicationActionLogDAO::insert); + // 设置日志参数 + EventParamsHolder.addParams(release); + return releaseId; + } + + @Override + public Long copyAppRelease(Long id) { + // 查询 + ApplicationReleaseDO release = applicationReleaseDAO.selectById(id); + Valid.notNull(release, MessageConst.RELEASE_ABSENT); + List machines = applicationReleaseMachineService.getReleaseMachines(id); + List machineIdList = machines.stream() + .map(ApplicationReleaseMachineDO::getMachineId) + .collect(Collectors.toList()); + // 提交 + ApplicationReleaseRequest request = new ApplicationReleaseRequest(); + request.setTitle(release.getReleaseTitle()); + request.setAppId(release.getAppId()); + request.setProfileId(release.getProfileId()); + request.setBuildId(release.getBuildId()); + request.setMachineIdList(machineIdList); + return SpringHolder.getBean(ApplicationReleaseService.class).submitAppRelease(request); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer auditAppRelease(ApplicationReleaseAuditRequest request) { + // 查询状态 + Long id = request.getId(); + ApplicationReleaseDO release = applicationReleaseDAO.selectById(id); + Valid.notNull(release, MessageConst.RELEASE_ABSENT); + ReleaseStatus status = ReleaseStatus.of(release.getReleaseStatus()); + if (!ReleaseStatus.WAIT_AUDIT.equals(status) && !ReleaseStatus.AUDIT_REJECT.equals(status)) { + throw Exceptions.argument(MessageConst.ILLEGAL_STATUS); + } + AuditStatus auditStatus = AuditStatus.of(request.getStatus()); + UserDTO user = Currents.getUser(); + // 更新 + ApplicationReleaseDO update = new ApplicationReleaseDO(); + update.setId(id); + update.setAuditUserId(user.getId()); + update.setAuditUserName(user.getUsername()); + update.setAuditTime(new Date()); + update.setAuditReason(request.getReason()); + final boolean resolve = AuditStatus.RESOLVE.equals(auditStatus); + // 发送站内信 + Map params = Maps.newMap(); + params.put(EventKeys.ID, release.getId()); + params.put(EventKeys.TITLE, release.getReleaseTitle()); + if (resolve) { + // 通过 + final boolean timedRelease = TimedType.TIMED.getType().equals(release.getTimedRelease()); + if (!timedRelease) { + update.setReleaseStatus(ReleaseStatus.WAIT_RUNNABLE.getStatus()); + } else { + update.setReleaseStatus(ReleaseStatus.WAIT_SCHEDULE.getStatus()); + // 提交任务 + taskRegister.submit(TaskType.RELEASE, release.getTimedReleaseTime(), id); + } + webSideMessageService.addMessage(MessageType.RELEASE_AUDIT_RESOLVE, id, release.getCreateUserId(), release.getCreateUserName(), params); + } else { + // 驳回 + update.setReleaseStatus(ReleaseStatus.AUDIT_REJECT.getStatus()); + webSideMessageService.addMessage(MessageType.RELEASE_AUDIT_REJECT, id, release.getCreateUserId(), release.getCreateUserName(), params); + } + int effect = applicationReleaseDAO.updateById(update); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.TITLE, release.getReleaseTitle()); + EventParamsHolder.addParam(EventKeys.OPERATOR, resolve ? CnConst.RESOLVE : CnConst.REJECT); + return effect; + } + + @Override + public void runnableAppRelease(Long id, boolean systemSchedule, boolean execute) { + // 查询状态 + ApplicationReleaseDO release = applicationReleaseDAO.selectById(id); + Valid.notNull(release, MessageConst.RELEASE_ABSENT); + ReleaseStatus status = ReleaseStatus.of(release.getReleaseStatus()); + if (!ReleaseStatus.WAIT_RUNNABLE.equals(status) + && !ReleaseStatus.WAIT_SCHEDULE.equals(status)) { + throw Exceptions.argument(MessageConst.ILLEGAL_STATUS); + } + // 更新发布人 + ApplicationReleaseDO update = new ApplicationReleaseDO(); + update.setId(id); + update.setUpdateTime(new Date()); + if (systemSchedule) { + update.setReleaseUserId(release.getCreateUserId()); + update.setReleaseUserName(release.getCreateUserName()); + } else { + UserDTO user = Currents.getUser(); + update.setReleaseUserId(user.getId()); + update.setReleaseUserName(user.getUsername()); + // 移除 + taskRegister.cancel(TaskType.RELEASE, id); + } + applicationReleaseDAO.updateById(update); + // 发布 + if (execute) { + IReleaseProcessor.with(release).exec(); + } + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.TITLE, release.getReleaseTitle()); + EventParamsHolder.addParam(EventKeys.SYSTEM, systemSchedule); + } + + @Override + public void cancelAppTimedRelease(Long id) { + // 查询状态 + ApplicationReleaseDO release = applicationReleaseDAO.selectById(id); + Valid.notNull(release, MessageConst.RELEASE_ABSENT); + ReleaseStatus status = ReleaseStatus.of(release.getReleaseStatus()); + if (!ReleaseStatus.WAIT_SCHEDULE.equals(status)) { + throw Exceptions.argument(MessageConst.ILLEGAL_STATUS); + } + // 更新状态 + ApplicationReleaseDO update = new ApplicationReleaseDO(); + update.setId(id); + update.setUpdateTime(new Date()); + update.setTimedRelease(TimedType.NORMAL.getType()); + update.setReleaseStatus(ReleaseStatus.WAIT_RUNNABLE.getStatus()); + applicationReleaseDAO.updateById(update); + applicationReleaseDAO.setTimedReleaseTimeNull(id); + // 取消调度任务 + taskRegister.cancel(TaskType.RELEASE, id); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.TITLE, release.getReleaseTitle()); + } + + @Override + public void setTimedRelease(Long id, Date releaseTime) { + // 查询状态 + ApplicationReleaseDO release = applicationReleaseDAO.selectById(id); + Valid.notNull(release, MessageConst.RELEASE_ABSENT); + ReleaseStatus status = ReleaseStatus.of(release.getReleaseStatus()); + if (!ReleaseStatus.WAIT_RUNNABLE.equals(status) && !ReleaseStatus.WAIT_SCHEDULE.equals(status)) { + throw Exceptions.argument(MessageConst.ILLEGAL_STATUS); + } + // 取消调度任务 + taskRegister.cancel(TaskType.RELEASE, id); + // 更新状态 + ApplicationReleaseDO update = new ApplicationReleaseDO(); + update.setId(id); + update.setUpdateTime(new Date()); + update.setReleaseStatus(ReleaseStatus.WAIT_SCHEDULE.getStatus()); + update.setTimedRelease(TimedType.TIMED.getType()); + update.setTimedReleaseTime(releaseTime); + applicationReleaseDAO.updateById(update); + // 提交任务 + taskRegister.submit(TaskType.RELEASE, releaseTime, id); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.TITLE, release.getReleaseTitle()); + EventParamsHolder.addParam(EventKeys.TIME, Dates.format(releaseTime)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Long rollbackAppRelease(Long id) { + // 查询状态 + ApplicationReleaseDO rollback = applicationReleaseDAO.selectById(id); + Valid.notNull(rollback, MessageConst.RELEASE_ABSENT); + ReleaseStatus status = ReleaseStatus.of(rollback.getReleaseStatus()); + if (!ReleaseStatus.FINISH.equals(status)) { + throw Exceptions.argument(MessageConst.ILLEGAL_STATUS); + } + // 检查产物是否存在 + String path = Files1.getPath(SystemEnvAttr.DIST_PATH.getValue(), rollback.getBundlePath()); + if (!new File(path).exists()) { + throw Exceptions.argument(MessageConst.FILE_ABSENT_UNABLE_ROLLBACK); + } + // 查询环境 + ApplicationProfileDO profile = applicationProfileDAO.selectById(rollback.getProfileId()); + Valid.notNull(profile, MessageConst.PROFILE_ABSENT); + // 设置主表信息 + this.setRollbackReleaseInfo(rollback, profile); + applicationReleaseDAO.insert(rollback); + Long releaseId = rollback.getId(); + // 设置机器信息 + List machines = applicationReleaseMachineService.getReleaseMachines(id); + for (ApplicationReleaseMachineDO machine : machines) { + Long beforeId = machine.getId(); + machine.setId(null); + machine.setReleaseId(releaseId); + machine.setRunStatus(ActionStatus.WAIT.getStatus()); + machine.setLogPath(PathBuilders.getReleaseMachineLogPath(releaseId, machine.getMachineId())); + machine.setStartTime(null); + machine.setEndTime(null); + machine.setCreateTime(null); + machine.setUpdateTime(null); + applicationReleaseMachineDAO.insert(machine); + // 设置操作信息 + List actions = applicationActionLogService.selectActionByRelId(beforeId, StageType.RELEASE); + for (ApplicationActionLogDO action : actions) { + action.setId(null); + action.setRelId(machine.getId()); + action.setLogPath(PathBuilders.getReleaseActionLogPath(releaseId, machine.getMachineId(), action.getActionId())); + action.setRunStatus(ActionStatus.WAIT.getStatus()); + action.setExitCode(null); + action.setStartTime(null); + action.setEndTime(null); + action.setCreateTime(null); + action.setUpdateTime(null); + applicationActionLogDAO.insert(action); + } + } + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.TITLE, rollback.getReleaseTitle()); + return releaseId; + } + + @Override + public void terminateRelease(Long id) { + // 获取数据 + ApplicationReleaseDO release = applicationReleaseDAO.selectById(id); + Valid.notNull(release, MessageConst.RELEASE_ABSENT); + // 检查状态 + Valid.isTrue(ReleaseStatus.RUNNABLE.getStatus().equals(release.getReleaseStatus()), MessageConst.ILLEGAL_STATUS); + // 获取实例 + IReleaseProcessor session = releaseSessionHolder.getSession(id); + Valid.notNull(session, MessageConst.SESSION_PRESENT); + // 调用终止 + session.terminateAll(); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.TITLE, release.getReleaseTitle()); + } + + + @Override + public void terminateMachine(Long releaseMachineId) { + this.skipOrTerminateReleaseMachine(releaseMachineId, true); + } + + @Override + public void skipMachine(Long releaseMachineId) { + this.skipOrTerminateReleaseMachine(releaseMachineId, false); + } + + /** + * 跳过或停止发布机器 + * + * @param releaseMachineId releaseMachineId + * @param terminate terminate / skip + */ + private void skipOrTerminateReleaseMachine(Long releaseMachineId, boolean terminate) { + // 获取发布机器 + ApplicationReleaseMachineDO machine = applicationReleaseMachineDAO.selectById(releaseMachineId); + Valid.notNull(machine, MessageConst.RELEASE_MACHINE_ABSENT); + Long id = machine.getReleaseId(); + // 获取数据 + ApplicationReleaseDO release = applicationReleaseDAO.selectById(id); + Valid.notNull(release, MessageConst.RELEASE_ABSENT); + // 检查状态 + Valid.isTrue(ReleaseStatus.RUNNABLE.getStatus().equals(release.getReleaseStatus()), MessageConst.ILLEGAL_STATUS); + // 检查状态 + if (terminate) { + Valid.isTrue(ActionStatus.RUNNABLE.getStatus().equals(machine.getRunStatus()), MessageConst.ILLEGAL_STATUS); + } else { + Valid.isTrue(ActionStatus.WAIT.getStatus().equals(machine.getRunStatus()), MessageConst.ILLEGAL_STATUS); + } + // 获取实例 + IReleaseProcessor session = releaseSessionHolder.getSession(id); + Valid.notNull(session, MessageConst.SESSION_PRESENT); + if (terminate) { + // 调用终止 + session.terminateMachine(releaseMachineId); + } else { + // 调用跳过 + session.skipMachine(releaseMachineId); + } + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.MACHINE_ID, releaseMachineId); + EventParamsHolder.addParam(EventKeys.TITLE, release.getReleaseTitle()); + EventParamsHolder.addParam(EventKeys.MACHINE_NAME, machine.getMachineName()); + } + + @Override + public void writeMachine(Long releaseMachineId, String command) { + // 获取发布机器 + ApplicationReleaseMachineDO machine = applicationReleaseMachineDAO.selectById(releaseMachineId); + Valid.notNull(machine, MessageConst.RELEASE_MACHINE_ABSENT); + Valid.isTrue(ActionStatus.RUNNABLE.getStatus().equals(machine.getRunStatus()), MessageConst.ILLEGAL_STATUS); + // 获取实例 + IReleaseProcessor session = releaseSessionHolder.getSession(machine.getReleaseId()); + Valid.notNull(session, MessageConst.SESSION_PRESENT); + // 输入命令 + session.writeMachine(releaseMachineId, command); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer deleteRelease(List idList) { + // 查询状态 + List releaseList = applicationReleaseDAO.selectBatchIds(idList); + Valid.notEmpty(releaseList, MessageConst.RELEASE_ABSENT); + boolean canDelete = releaseList.stream() + .map(ApplicationReleaseDO::getReleaseStatus) + .noneMatch(s -> ReleaseStatus.WAIT_SCHEDULE.getStatus().equals(s) + || ReleaseStatus.RUNNABLE.getStatus().equals(s)); + Valid.isTrue(canDelete, MessageConst.ILLEGAL_STATUS); + // 查询机器 + List releaseMachineIdList = applicationReleaseMachineService.getReleaseMachineIdList(idList); + // 删除主表 + int effect = applicationReleaseDAO.deleteBatchIds(idList); + // 删除机器 + effect += applicationReleaseMachineDAO.deleteBatchIds(releaseMachineIdList); + // 删除操作 + effect += applicationActionLogService.deleteByRelIdList(releaseMachineIdList, StageType.RELEASE); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID_LIST, idList); + EventParamsHolder.addParam(EventKeys.COUNT, idList.size()); + return effect; + } + + @Override + public List getReleaseStatusList(List idList, List machineIdList) { + // 查询发布状态 + List releaseList = applicationReleaseDAO.selectStatusByIdList(idList); + List statusList = Converts.toList(releaseList, ApplicationReleaseStatusVO.class); + // 查询机器状态 + if (Lists.isNotEmpty(machineIdList)) { + List machineList = applicationReleaseMachineDAO.selectStatusByIdList(machineIdList); + for (ApplicationReleaseStatusVO status : statusList) { + List machineStatus = machineList.stream() + .filter(s -> s.getReleaseId().equals(status.getId())) + .collect(Collectors.toList()); + status.setMachines(Converts.toList(machineStatus, ApplicationReleaseMachineStatusVO.class)); + } + } + return statusList; + } + + @Override + public ApplicationReleaseStatusVO getReleaseStatus(Long id) { + // 查询发布状态 + ApplicationReleaseDO release = applicationReleaseDAO.selectStatusById(id); + Valid.notNull(release, MessageConst.RELEASE_ABSENT); + ApplicationReleaseStatusVO status = Converts.to(release, ApplicationReleaseStatusVO.class); + // 查询机器状态 + List machines = applicationReleaseMachineDAO.selectStatusByReleaseId(id); + List machineStatus = Converts.toList(machines, ApplicationReleaseMachineStatusVO.class); + status.setMachines(machineStatus); + return status; + } + + @Override + public List getReleaseMachineStatusList(List releaseMachineIdList) { + // 查询机器状态 + List machines = applicationReleaseMachineDAO.selectStatusByIdList(releaseMachineIdList); + List machinesStatus = Converts.toList(machines, ApplicationReleaseMachineStatusVO.class); + if (machinesStatus.isEmpty()) { + return machinesStatus; + } + // 查询操作状态 + List actions = applicationActionLogDAO.selectStatusInfoByRelIdList(releaseMachineIdList, StageType.RELEASE.getType()); + for (ApplicationReleaseMachineStatusVO machineStatus : machinesStatus) { + List machineActions = actions.stream() + .filter(s -> s.getRelId().equals(machineStatus.getId())) + .collect(Collectors.toList()); + machineStatus.setActions(Converts.toList(machineActions, ApplicationActionStatusVO.class)); + } + return machinesStatus; + } + + @Override + public ApplicationReleaseMachineStatusVO getReleaseMachineStatus(Long releaseMachineId) { + // 查询机器状态 + ApplicationReleaseMachineDO machine = applicationReleaseMachineDAO.selectStatusById(releaseMachineId); + Valid.notNull(machine, MessageConst.UNKNOWN_DATA); + ApplicationReleaseMachineStatusVO status = Converts.to(machine, ApplicationReleaseMachineStatusVO.class); + // 查询操作状态 + List actions = applicationActionLogDAO.selectStatusInfoByRelId(releaseMachineId, StageType.RELEASE.getType()); + status.setActions(Converts.toList(actions, ApplicationActionStatusVO.class)); + return status; + } + + /** + * 检查发布机器 + * + * @param request request + */ + private void checkReleaseMachine(ApplicationReleaseRequest request) { + // 获取发布配置机器 + List machineIdList = applicationMachineService.getAppProfileMachineIdList(request.getAppId(), request.getProfileId(), false); + Valid.notEmpty(machineIdList, MessageConst.UNABLE_CONFIG_RELEASE_MACHINE); + for (Long machineId : request.getMachineIdList()) { + // 检查发布机器是否在配置中 + if (machineIdList.stream().noneMatch(machineId::equals)) { + throw Exceptions.argument(MessageConst.UNKNOWN_RELEASE_MACHINE); + } + } + } + + /** + * 查询发布机器信息 + * + * @param request request + * @return machine + */ + private Map getReleaseMachineInfo(ApplicationReleaseRequest request) { + List machineIdList = request.getMachineIdList(); + List machines = machineInfoDAO.selectBatchIds(machineIdList); + Valid.isTrue(machineIdList.size() == machines.size()); + return machines.stream().collect(Collectors.toMap(MachineInfoDO::getId, Function.identity())); + } + + /** + * 设置发布参数 + * + * @param request request + * @param app app + * @param profile profile + * @param actions actions + * @return releaseInfo + */ + private ApplicationReleaseDO setReleaseInfo(ApplicationReleaseRequest request, + ApplicationInfoDO app, + ApplicationProfileDO profile, + ApplicationBuildDO build, + List actions) { + UserDTO user = Currents.getUser(); + String buildBundlePath = applicationBuildService.checkBuildBundlePath(build); + // 查询产物传输路径 + String transferPath = applicationEnvService.getAppEnvValue(app.getId(), profile.getId(), ApplicationEnvAttr.TRANSFER_PATH.getKey()); + // 查询产物传输方式 + String transferMode = applicationEnvService.getAppEnvValue(app.getId(), profile.getId(), ApplicationEnvAttr.TRANSFER_MODE.getKey()); + // 查询发布序列 + String releaseSerial = applicationEnvService.getAppEnvValue(app.getId(), profile.getId(), ApplicationEnvAttr.RELEASE_SERIAL.getKey()); + // 查询异常处理 + String exceptionHandler = applicationEnvService.getAppEnvValue(app.getId(), profile.getId(), ApplicationEnvAttr.EXCEPTION_HANDLER.getKey()); + // 设置参数 + ApplicationReleaseDO release = new ApplicationReleaseDO(); + release.setReleaseTitle(request.getTitle()); + release.setReleaseDescription(request.getDescription()); + release.setBuildId(build.getId()); + release.setBuildSeq(build.getBuildSeq()); + release.setAppId(app.getId()); + release.setAppName(app.getAppName()); + release.setAppTag(app.getAppTag()); + release.setProfileId(profile.getId()); + release.setProfileName(profile.getProfileName()); + release.setProfileTag(profile.getProfileTag()); + release.setReleaseType(ReleaseType.NORMAL.getType()); + release.setReleaseSerialize(SerialType.of(releaseSerial).getType()); + release.setExceptionHandler(ExceptionHandlerType.of(exceptionHandler).getType()); + release.setBundlePath(buildBundlePath); + release.setTransferPath(transferPath); + release.setTransferMode(transferMode); + release.setTimedRelease(request.getTimedRelease()); + release.setTimedReleaseTime(request.getTimedReleaseTime()); + release.setCreateUserId(user.getId()); + release.setCreateUserName(user.getUsername()); + release.setActionConfig(JSON.toJSONString(actions)); + // 设置审核信息 + this.setCreateAuditInfo(release, user, Const.ENABLE.equals(profile.getReleaseAudit())); + // 设置状态 + request.setStatus(release.getReleaseStatus()); + return release; + } + + /** + * 设置回滚发布信息 + * + * @param rollback rollback + * @param profile profile + */ + private void setRollbackReleaseInfo(ApplicationReleaseDO rollback, ApplicationProfileDO profile) { + UserDTO user = Currents.getUser(); + rollback.setId(null); + rollback.setReleaseTitle(rollback.getReleaseTitle()); + rollback.setReleaseType(ReleaseType.ROLLBACK.getType()); + rollback.setRollbackReleaseId(rollback.getId()); + rollback.setTimedRelease(TimedType.NORMAL.getType()); + rollback.setTimedReleaseTime(null); + rollback.setCreateUserId(user.getId()); + rollback.setCreateUserName(user.getUsername()); + rollback.setAuditUserId(null); + rollback.setAuditUserName(null); + rollback.setAuditTime(null); + rollback.setAuditReason(null); + rollback.setReleaseStartTime(null); + rollback.setReleaseEndTime(null); + rollback.setReleaseUserId(null); + rollback.setReleaseUserName(null); + rollback.setCreateTime(null); + rollback.setUpdateTime(null); + this.setCreateAuditInfo(rollback, user, Const.ENABLE.equals(profile.getReleaseAudit())); + } + + /** + * 设置发布机器参数 + * + * @param request request + * @param machines machines + * @param releaseId releaseId + * @return machine + */ + private List setReleaseMachineInfo(ApplicationReleaseRequest request, Map machines, Long releaseId) { + return request.getMachineIdList().stream().map(machineId -> { + ApplicationReleaseMachineDO machine = new ApplicationReleaseMachineDO(); + machine.setReleaseId(releaseId); + machine.setMachineId(machineId); + MachineInfoDO machineInfo = machines.get(machineId); + machine.setMachineName(machineInfo.getMachineName()); + machine.setMachineTag(machineInfo.getMachineTag()); + machine.setMachineHost(machineInfo.getMachineHost()); + machine.setRunStatus(ActionStatus.WAIT.getStatus()); + machine.setLogPath(PathBuilders.getReleaseMachineLogPath(releaseId, machineId)); + return machine; + }).collect(Collectors.toList()); + } + + /** + * 设置发布部署操作 + * + * @param actions actions + * @param releaseMachines 发布机器 + * @param releaseEnv 发布环境变量 + * @param hasEnvCommand hasEnvCommand + * @return actions + */ + private List setReleaseActions(List actions, List releaseMachines, + Map releaseEnv, boolean hasEnvCommand) { + List releaseActions = Lists.newList(); + for (ApplicationReleaseMachineDO releaseMachine : releaseMachines) { + for (ApplicationActionDO action : actions) { + ActionType actionType = ActionType.of(action.getActionType()); + Long machineId = releaseMachine.getMachineId(); + Long actionId = action.getId(); + // 设置机器操作 + ApplicationActionLogDO releaseAction = new ApplicationActionLogDO(); + releaseAction.setRelId(releaseMachine.getId()); + releaseAction.setStageType(StageType.RELEASE.getType()); + releaseAction.setMachineId(releaseMachine.getMachineId()); + releaseAction.setActionId(actionId); + releaseAction.setActionName(action.getActionName()); + releaseAction.setActionType(action.getActionType()); + // 设置命令 + String command = action.getActionCommand(); + if (ActionType.RELEASE_COMMAND.equals(actionType)) { + if (hasEnvCommand) { + // 替换发布命令 + command = Strings.format(command, EnvConst.SYMBOL, releaseEnv); + // 替换机器命令 + Map machineEnv = machineEnvService.getFullMachineEnv(machineId); + command = Strings.format(command, EnvConst.SYMBOL, machineEnv); + } + } + releaseAction.setActionCommand(command); + releaseAction.setLogPath(PathBuilders.getReleaseActionLogPath(releaseMachine.getReleaseId(), machineId, actionId)); + releaseAction.setRunStatus(ActionStatus.WAIT.getStatus()); + releaseActions.add(releaseAction); + } + } + return releaseActions; + } + + /** + * 创建时设置是否需要审核 + * + * @param release release + * @param user user + * @param needAudit needAudit + */ + private void setCreateAuditInfo(ApplicationReleaseDO release, UserDTO user, boolean needAudit) { + boolean isAdmin = RoleType.isAdministrator(user.getRoleType()); + // 需要审核 & 不是管理员 + if (needAudit && !isAdmin) { + release.setReleaseStatus(ReleaseStatus.WAIT_AUDIT.getStatus()); + } else { + if (TimedType.TIMED.getType().equals(release.getTimedRelease())) { + release.setReleaseStatus(ReleaseStatus.WAIT_SCHEDULE.getStatus()); + } else { + release.setReleaseStatus(ReleaseStatus.WAIT_RUNNABLE.getStatus()); + } + release.setAuditUserId(user.getId()); + release.setAuditUserName(user.getUsername()); + release.setAuditTime(new Date()); + if (isAdmin) { + release.setAuditReason(MessageConst.AUTO_AUDIT_RESOLVE); + } else { + release.setAuditReason(MessageConst.AUDIT_NOT_REQUIRED); + } + } + } + + /** + * 获取发布环境变量 + * + * @param build build + * @param release release + * @return env + */ + private Map getReleaseEnv(ApplicationBuildDO build, ApplicationReleaseDO release) { + // 设置变量 + MutableLinkedHashMap env = Maps.newMutableLinkedMap(); + env.put(EnvConst.RELEASE_PREFIX + EnvConst.BUILD_ID, build.getId() + Const.EMPTY); + env.put(EnvConst.RELEASE_PREFIX + EnvConst.BUILD_SEQ, build.getBuildSeq() + Const.EMPTY); + env.put(EnvConst.RELEASE_PREFIX + EnvConst.BRANCH, build.getBranchName()); + env.put(EnvConst.RELEASE_PREFIX + EnvConst.COMMIT, build.getCommitId()); + env.put(EnvConst.RELEASE_PREFIX + EnvConst.BUNDLE_PATH, Files1.getPath(SystemEnvAttr.DIST_PATH.getValue(), release.getBundlePath())); + env.put(EnvConst.RELEASE_PREFIX + EnvConst.RELEASE_ID, release.getId() + Const.EMPTY); + env.put(EnvConst.RELEASE_PREFIX + EnvConst.RELEASE_TITLE, release.getReleaseTitle()); + env.put(EnvConst.RELEASE_PREFIX + EnvConst.TRANSFER_PATH, release.getTransferPath()); + return env; + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationRepositoryServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationRepositoryServiceImpl.java new file mode 100644 index 0000000..03dd2a8 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/ApplicationRepositoryServiceImpl.java @@ -0,0 +1,437 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.ext.vcs.git.Gits; +import cn.orionsec.kit.ext.vcs.git.info.BranchInfo; +import cn.orionsec.kit.ext.vcs.git.info.LogInfo; +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Arrays1; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.Threads; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.lang.utils.io.Streams; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.app.RepositoryAuthType; +import cn.orionsec.ops.constant.app.RepositoryStatus; +import cn.orionsec.ops.constant.app.RepositoryTokenType; +import cn.orionsec.ops.constant.app.RepositoryType; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.message.MessageType; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.dao.ApplicationBuildDAO; +import cn.orionsec.ops.dao.ApplicationInfoDAO; +import cn.orionsec.ops.dao.ApplicationRepositoryDAO; +import cn.orionsec.ops.entity.domain.ApplicationRepositoryDO; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.entity.request.app.ApplicationRepositoryRequest; +import cn.orionsec.ops.entity.vo.app.ApplicationRepositoryBranchVO; +import cn.orionsec.ops.entity.vo.app.ApplicationRepositoryCommitVO; +import cn.orionsec.ops.entity.vo.app.ApplicationRepositoryInfoVO; +import cn.orionsec.ops.entity.vo.app.ApplicationRepositoryVO; +import cn.orionsec.ops.service.api.ApplicationRepositoryService; +import cn.orionsec.ops.service.api.WebSideMessageService; +import cn.orionsec.ops.utils.*; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.io.File; +import java.util.*; + +@Slf4j +@Service("applicationRepositoryService") +public class ApplicationRepositoryServiceImpl implements ApplicationRepositoryService { + + @Resource + private ApplicationRepositoryDAO applicationRepositoryDAO; + + @Resource + private ApplicationBuildDAO applicationBuildDAO; + + @Resource + private ApplicationInfoDAO applicationInfoDAO; + + @Resource + private WebSideMessageService webSideMessageService; + + @Override + public Long addRepository(ApplicationRepositoryRequest request) { + // 检查名称是否存在 + this.checkNamePresent(null, request.getName()); + // 插入 + ApplicationRepositoryDO insert = new ApplicationRepositoryDO(); + insert.setRepoName(request.getName()); + insert.setRepoDescription(request.getDescription()); + insert.setRepoType(RepositoryType.GIT.getType()); + insert.setRepoUrl(request.getUrl()); + insert.setRepoUsername(request.getUsername()); + insert.setRepoAuthType(request.getAuthType()); + insert.setRepoTokenType(request.getTokenType()); + // 加密密码 + String password = request.getPassword(); + if (!Strings.isBlank(password)) { + password = ValueMix.encrypt(password); + insert.setRepoPassword(password); + } + // 加密token + String token = request.getPrivateToken(); + if (!Strings.isBlank(token)) { + token = ValueMix.encrypt(token); + insert.setRepoPrivateToken(token); + } + insert.setRepoStatus(RepositoryStatus.UNINITIALIZED.getStatus()); + applicationRepositoryDAO.insert(insert); + // 设置日志参数 + EventParamsHolder.addParams(insert); + return insert.getId(); + } + + @Override + public Integer updateRepository(ApplicationRepositoryRequest request) { + Long id = request.getId(); + // 检查名称是否存在 + this.checkNamePresent(id, request.getName()); + // 查询修改前的数据 + ApplicationRepositoryDO beforeRepo = applicationRepositoryDAO.selectById(id); + Valid.notNull(beforeRepo, MessageConst.UNKNOWN_DATA); + // 更新 + ApplicationRepositoryDO update = new ApplicationRepositoryDO(); + update.setId(id); + update.setRepoName(request.getName()); + update.setRepoDescription(request.getDescription()); + update.setRepoUrl(request.getUrl()); + update.setRepoUsername(request.getUsername()); + update.setRepoAuthType(request.getAuthType()); + update.setRepoTokenType(request.getTokenType()); + // 加密密码 + String password = request.getPassword(); + if (!Strings.isBlank(password)) { + password = ValueMix.encrypt(password); + update.setRepoPassword(password); + } + // 加密token + String token = request.getPrivateToken(); + if (!Strings.isBlank(token)) { + token = ValueMix.encrypt(token); + update.setRepoPrivateToken(token); + } + if (!beforeRepo.getRepoUrl().equals(update.getRepoUrl())) { + // 如果修改了url则状态改为未初始化 + update.setRepoStatus(RepositoryStatus.UNINITIALIZED.getStatus()); + // 删除 event 目录 + File clonePath = new File(Utils.getRepositoryEventDir(id)); + Files1.delete(clonePath); + } + int effect = applicationRepositoryDAO.updateById(update); + // 设置日志参数 + EventParamsHolder.addParams(update); + EventParamsHolder.addParam(EventKeys.NAME, beforeRepo.getRepoName()); + return effect; + } + + @Override + public Integer deleteRepository(Long id) { + ApplicationRepositoryDO beforeRepo = applicationRepositoryDAO.selectById(id); + Valid.notNull(beforeRepo, MessageConst.UNKNOWN_DATA); + // 清空app引用 + applicationInfoDAO.cleanRepoCount(id); + // 删除 + int effect = applicationRepositoryDAO.deleteById(id); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.NAME, beforeRepo.getRepoName()); + return effect; + } + + @Override + public DataGrid listRepository(ApplicationRepositoryRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .like(Objects.nonNull(request.getName()), ApplicationRepositoryDO::getRepoName, request.getName()) + .like(Objects.nonNull(request.getDescription()), ApplicationRepositoryDO::getRepoDescription, request.getDescription()) + .like(Objects.nonNull(request.getUrl()), ApplicationRepositoryDO::getRepoUrl, request.getUrl()) + .like(Objects.nonNull(request.getUsername()), ApplicationRepositoryDO::getRepoUsername, request.getUsername()) + .eq(Objects.nonNull(request.getId()), ApplicationRepositoryDO::getId, request.getId()) + .eq(Objects.nonNull(request.getType()), ApplicationRepositoryDO::getRepoType, request.getType()) + .eq(Objects.nonNull(request.getStatus()), ApplicationRepositoryDO::getRepoStatus, request.getStatus()) + .orderByAsc(ApplicationRepositoryDO::getId); + return DataQuery.of(applicationRepositoryDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(ApplicationRepositoryVO.class); + } + + @Override + public ApplicationRepositoryVO getRepositoryDetail(Long id) { + ApplicationRepositoryDO repo = applicationRepositoryDAO.selectById(id); + Valid.notNull(repo, MessageConst.UNKNOWN_DATA); + return Converts.to(repo, ApplicationRepositoryVO.class); + } + + @Override + public void initEventRepository(Long id, boolean isReInit) { + // 查询数据 + ApplicationRepositoryDO repo = applicationRepositoryDAO.selectById(id); + Valid.notNull(repo, MessageConst.UNKNOWN_DATA); + // 判断状态 + if (RepositoryStatus.INITIALIZING.getStatus().equals(repo.getRepoStatus())) { + throw Exceptions.runtime(MessageConst.REPO_INITIALIZING); + } else if (RepositoryStatus.OK.getStatus().equals(repo.getRepoStatus()) && !isReInit) { + throw Exceptions.runtime(MessageConst.REPO_INITIALIZED); + } else if (!RepositoryStatus.OK.getStatus().equals(repo.getRepoStatus()) && isReInit) { + throw Exceptions.runtime(MessageConst.REPO_UNINITIALIZED); + } + // 获取当前用户 + UserDTO user = Currents.getUser(); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.NAME, repo.getRepoName()); + // 修改状态 + ApplicationRepositoryDO update = new ApplicationRepositoryDO(); + update.setId(id); + update.setRepoStatus(RepositoryStatus.INITIALIZING.getStatus()); + applicationRepositoryDAO.updateById(update); + // 提交线程异步处理 + log.info("开始初始化应用仓库 id: {}", id); + Threads.start(() -> { + // 删除 + File clonePath = new File(Utils.getRepositoryEventDir(id)); + Files1.delete(clonePath); + // 初始化 + Exception ex = null; + Gits gits = null; + try { + // clone + String[] pair = this.getRepositoryUsernamePassword(repo); + String username = pair[0]; + String password = pair[1]; + if (username == null) { + gits = Gits.clone(repo.getRepoUrl(), clonePath); + } else { + gits = Gits.clone(repo.getRepoUrl(), clonePath, username, password); + } + } catch (Exception e) { + ex = e; + } finally { + Streams.close(gits); + } + MessageType message; + // 修改状态 + if (ex == null) { + message = MessageType.REPOSITORY_INIT_SUCCESS; + update.setRepoStatus(RepositoryStatus.OK.getStatus()); + } else { + Files1.delete(clonePath); + message = MessageType.REPOSITORY_INIT_FAILURE; + update.setRepoStatus(RepositoryStatus.ERROR.getStatus()); + } + applicationRepositoryDAO.updateById(update); + // 发送站内信 + Map params = Maps.newMap(); + params.put(EventKeys.ID, repo.getId()); + params.put(EventKeys.NAME, repo.getRepoName()); + webSideMessageService.addMessage(message, repo.getId(), user.getId(), user.getUsername(), params); + if (ex == null) { + log.info("应用仓库初始化成功 id: {}", id); + } else { + log.error("应用仓库初始化失败 id: {}", id, ex); + throw Exceptions.argument(MessageConst.REPO_INIT_ERROR, ex); + } + }); + } + + @Override + public ApplicationRepositoryInfoVO getRepositoryInfo(ApplicationRepositoryRequest request) { + Long id = request.getId(); + // 打开git + try (Gits gits = this.openEventGit(id)) { + gits.fetch(); + ApplicationRepositoryInfoVO repsInfo = new ApplicationRepositoryInfoVO(); + ApplicationRepositoryBranchVO defaultBranch; + // 获取分支列表 + List branches = Converts.toList(gits.branchList(), ApplicationRepositoryBranchVO.class); + // 获取当前环境上次构建分支 + String lastBranchName = applicationBuildDAO.selectLastBuildBranch(request.getAppId(), request.getProfileId(), id); + if (lastBranchName != null) { + defaultBranch = branches.stream() + .filter(s -> s.getName().equals(lastBranchName)) + .findFirst() + .orElseGet(() -> branches.get(branches.size() - 1)); + } else { + defaultBranch = branches.get(branches.size() - 1); + } + defaultBranch.setIsDefault(Const.IS_DEFAULT); + repsInfo.setBranches(branches); + // 获取commit + try { + List logList = gits.logList(defaultBranch.getName(), Const.COMMIT_LIMIT); + repsInfo.setCommits(Converts.toList(logList, ApplicationRepositoryCommitVO.class)); + } catch (Exception e) { + log.error("获取repo-commit列表失败: id: {}, branch: {}, e: {}", id, defaultBranch.getName(), e); + throw e; + } + return repsInfo; + } + } + + @Override + public List getRepositoryBranchList(Long id) { + // 打开git + try (Gits gits = this.openEventGit(id)) { + gits.fetch(); + // 查询分支信息 + List branchList = gits.branchList(); + return Converts.toList(branchList, ApplicationRepositoryBranchVO.class); + } + } + + @Override + public List getRepositoryCommitList(Long id, String branchName) { + // 打开git + try (Gits gits = this.openEventGit(id)) { + gits.fetch(branchName.split("/")[0]); + // 查询提交信息 + List logList = gits.logList(branchName, Const.COMMIT_LIMIT); + return Converts.toList(logList, ApplicationRepositoryCommitVO.class); + } catch (Exception e) { + log.error("获取repo-commit列表失败: id: {}, branch: {}", id, branchName, e); + throw e; + } + } + + @Override + public Gits openEventGit(Long id) { + // 查询数据 + ApplicationRepositoryDO repo = applicationRepositoryDAO.selectById(id); + Valid.notNull(repo, MessageConst.UNKNOWN_DATA); + Valid.isTrue(RepositoryStatus.OK.getStatus().equals(repo.getRepoStatus()), MessageConst.REPO_UNINITIALIZED); + // 获取仓库位置 + File repoPath = new File(Utils.getRepositoryEventDir(id)); + if (!repoPath.isDirectory()) { + // 修改状态为未初始化 + ApplicationRepositoryDO entity = new ApplicationRepositoryDO(); + entity.setId(id); + entity.setRepoStatus(RepositoryStatus.UNINITIALIZED.getStatus()); + applicationRepositoryDAO.updateById(entity); + throw Exceptions.argument(MessageConst.REPO_PATH_ABSENT, Exceptions.runtime(repoPath.getAbsolutePath())); + } + // 打开git + try { + Gits gits = Gits.of(repoPath); + String[] pair = this.getRepositoryUsernamePassword(repo); + String username = pair[0]; + String password = pair[1]; + if (username != null) { + gits.auth(username, password); + } + return gits; + } catch (Exception e) { + throw Exceptions.runtime(MessageConst.REPO_UNABLE_CONNECT, e); + } + } + + @Override + public void cleanBuildRepository(Long id) { + // 查询数据 + ApplicationRepositoryDO repo = applicationRepositoryDAO.selectById(id); + Valid.notNull(repo, MessageConst.UNKNOWN_DATA); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.NAME, repo.getRepoName()); + File rootPath = new File(Files1.getPath(SystemEnvAttr.REPO_PATH.getValue(), id + Const.EMPTY)); + if (!Files1.isDirectory(rootPath)) { + return; + } + // 查询文件夹 + File[] files = rootPath.listFiles(e -> !e.getName().equals(Const.EVENT) + && e.isDirectory() + && Strings.isInteger(e.getName())); + if (Arrays1.isEmpty(files)) { + return; + } + // 保留两个版本 防止清空正在进行中的构建任务 + int length = files.length; + if (length <= 2) { + return; + } + Arrays.sort(files, Comparator.comparing(s -> Integer.parseInt(s.getName()))); + for (int i = 0; i < length - 2; i++) { + Files1.delete(files[i]); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void syncRepositoryStatus() { + List repoList = applicationRepositoryDAO.selectList(new LambdaQueryWrapper<>()); + for (ApplicationRepositoryDO repo : repoList) { + Long id = repo.getId(); + File repoPath = new File(Utils.getRepositoryEventDir(id)); + boolean isDir = Files1.isDirectory(repoPath); + // 更新状态 + ApplicationRepositoryDO update = new ApplicationRepositoryDO(); + update.setId(id); + update.setRepoStatus(isDir ? RepositoryStatus.OK.getStatus() : RepositoryStatus.UNINITIALIZED.getStatus()); + update.setUpdateTime(new Date()); + applicationRepositoryDAO.updateById(update); + } + } + + @Override + public ApplicationRepositoryDO selectById(Long id) { + return applicationRepositoryDAO.selectById(id); + } + + @Override + public String[] getRepositoryUsernamePassword(ApplicationRepositoryDO repository) { + String username = null; + String password = null; + RepositoryAuthType authType = RepositoryAuthType.of(repository.getRepoAuthType()); + if (RepositoryAuthType.PASSWORD.equals(authType)) { + // 用户名 + String repoUsername = repository.getRepoUsername(); + if (!Strings.isBlank(repoUsername)) { + username = repoUsername; + password = ValueMix.decrypt(repository.getRepoPassword()); + } + } else { + // token + RepositoryTokenType tokenType = RepositoryTokenType.of(repository.getRepoTokenType()); + switch (tokenType) { + case GITHUB: + username = Const.EMPTY; + break; + case GITEE: + username = repository.getRepoUsername(); + break; + case GITLAB: + username = Const.OAUTH2; + break; + default: + break; + } + password = ValueMix.decrypt(repository.getRepoPrivateToken()); + } + return new String[]{username, password}; + } + + /** + * 检查是否存在 + * + * @param id id + * @param name name + */ + private void checkNamePresent(Long id, String name) { + LambdaQueryWrapper presentWrapper = new LambdaQueryWrapper() + .ne(id != null, ApplicationRepositoryDO::getId, id) + .eq(ApplicationRepositoryDO::getRepoName, name); + boolean present = DataQuery.of(applicationRepositoryDAO).wrapper(presentWrapper).present(); + Valid.isTrue(!present, MessageConst.NAME_PRESENT); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/BatchUploadServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/BatchUploadServiceImpl.java new file mode 100644 index 0000000..cb7e27c --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/BatchUploadServiceImpl.java @@ -0,0 +1,200 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.id.UUIds; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.net.host.sftp.SftpExecutor; +import cn.orionsec.ops.constant.KeyConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.sftp.SftpTransferStatus; +import cn.orionsec.ops.constant.sftp.SftpTransferType; +import cn.orionsec.ops.dao.FileTransferLogDAO; +import cn.orionsec.ops.entity.domain.FileTransferLogDO; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.dto.sftp.SftpSessionTokenDTO; +import cn.orionsec.ops.entity.dto.sftp.SftpUploadInfoDTO; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.entity.request.sftp.FileUploadRequest; +import cn.orionsec.ops.entity.request.upload.BatchUploadRequest; +import cn.orionsec.ops.entity.vo.upload.BatchUploadCheckFileVO; +import cn.orionsec.ops.entity.vo.upload.BatchUploadCheckMachineVO; +import cn.orionsec.ops.entity.vo.upload.BatchUploadCheckVO; +import cn.orionsec.ops.entity.vo.upload.BatchUploadTokenVO; +import cn.orionsec.ops.handler.sftp.IFileTransferProcessor; +import cn.orionsec.ops.handler.sftp.SftpBasicExecutorHolder; +import cn.orionsec.ops.handler.sftp.TransferProcessorManager; +import cn.orionsec.ops.handler.sftp.hint.FileTransferHint; +import cn.orionsec.ops.service.api.BatchUploadService; +import cn.orionsec.ops.service.api.MachineInfoService; +import cn.orionsec.ops.utils.Currents; +import cn.orionsec.ops.utils.EventParamsHolder; +import com.alibaba.fastjson.JSON; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +@Service("batchUploadService") +public class BatchUploadServiceImpl implements BatchUploadService { + + @Resource + private MachineInfoService machineInfoService; + + @Resource + private SftpBasicExecutorHolder sftpBasicExecutorHolder; + + @Resource + private TransferProcessorManager transferProcessorManager; + + @Resource + private FileTransferLogDAO fileTransferLogDAO; + + @Resource + private RedisTemplate redisTemplate; + + @Override + public BatchUploadCheckVO checkMachineFiles(BatchUploadRequest request) { + // 机器信息 + Map machineMap = Maps.newMap(); + // sftp 连接 + Map connectedExecutors = Maps.newLinkedMap(); + // 不可连接的机器 + List notConnectMachineId = Lists.newList(); + for (Long machineId : request.getMachineIds()) { + // 查询机器 + MachineInfoDO machine = machineInfoService.selectById(machineId); + machineMap.put(machineId, machine); + // 获取连接 + try { + SftpExecutor executor = sftpBasicExecutorHolder.getBasicExecutor(machineId, machine); + connectedExecutors.put(machineId, executor); + } catch (Exception e) { + notConnectMachineId.add(machineId); + } + } + + // 检查可连接机器文件是否存在 + List machinePresentFiles = Lists.newList(); + connectedExecutors.forEach((k, v) -> { + synchronized (v) { + List presentFiles = request.getNames().stream() + .filter(Strings::isNotBlank) + .filter(s -> v.getFile(Files1.getPath(request.getRemotePath(), s)) != null) + .collect(Collectors.toList()); + if (!presentFiles.isEmpty()) { + // 记录重复文件 + BatchUploadCheckFileVO checkFile = Converts.to(machineMap.get(k), BatchUploadCheckFileVO.class); + checkFile.setPresentFiles(presentFiles); + machinePresentFiles.add(checkFile); + } + } + }); + + // 返回 + BatchUploadCheckVO checkResult = new BatchUploadCheckVO(); + checkResult.setMachinePresentFiles(machinePresentFiles); + // 可连接的机器 + Set connectedMachineIdList = connectedExecutors.keySet(); + checkResult.setConnectedMachineIdList(connectedMachineIdList); + List connectedMachines = connectedMachineIdList.stream() + .map(machineMap::get) + .filter(Objects::nonNull) + .map(s -> Converts.to(s, BatchUploadCheckMachineVO.class)) + .collect(Collectors.toList()); + checkResult.setConnectedMachines(connectedMachines); + // 无法连接的机器 + List notConnectMachines = notConnectMachineId.stream() + .map(machineMap::get) + .filter(Objects::nonNull) + .map(s -> Converts.to(s, BatchUploadCheckMachineVO.class)) + .collect(Collectors.toList()); + checkResult.setNotConnectedMachines(notConnectMachines); + return checkResult; + } + + @Override + public BatchUploadTokenVO getUploadAccessToken(BatchUploadRequest request) { + Long userId = Currents.getUserId(); + // 生成 accessToken + String accessToken = UUIds.random32(); + String accessKey = Strings.format(KeyConst.SFTP_UPLOAD_ACCESS_TOKEN, accessToken); + // 设置缓存信息 + SftpUploadInfoDTO uploadInfo = Converts.to(request, SftpUploadInfoDTO.class); + uploadInfo.setUserId(userId); + redisTemplate.opsForValue().set(accessKey, JSON.toJSONString(uploadInfo), + KeyConst.SFTP_UPLOAD_ACCESS_EXPIRE, TimeUnit.SECONDS); + + // 生成 notifyToken + String notifyToken = UUIds.random15(); + // 设置缓存信息 + String notifyKey = Strings.format(KeyConst.SFTP_SESSION_TOKEN, notifyToken); + SftpSessionTokenDTO info = new SftpSessionTokenDTO(); + info.setUserId(userId); + info.setMachineIdList(uploadInfo.getMachineIdList()); + redisTemplate.opsForValue().set(notifyKey, JSON.toJSONString(info), KeyConst.SFTP_SESSION_EXPIRE, TimeUnit.SECONDS); + + // 返回 + BatchUploadTokenVO token = new BatchUploadTokenVO(); + token.setAccessToken(accessToken); + token.setNotifyToken(notifyToken); + return token; + } + + @Override + public List batchUpload(List requestFiles) { + UserDTO user = Currents.getUser(); + Long userId = user.getId(); + // 初始化上传信息 + List uploadFiles = Lists.newList(); + for (FileUploadRequest requestFile : requestFiles) { + // 插入明细 + FileTransferLogDO upload = new FileTransferLogDO(); + upload.setUserId(userId); + upload.setUserName(user.getUsername()); + upload.setMachineId(requestFile.getMachineId()); + upload.setFileToken(requestFile.getFileToken()); + upload.setTransferType(SftpTransferType.UPLOAD.getType()); + upload.setRemoteFile(requestFile.getRemotePath()); + upload.setLocalFile(requestFile.getLocalPath()); + upload.setNowProgress(0D); + upload.setCurrentSize(0L); + upload.setFileSize(requestFile.getSize()); + upload.setTransferStatus(SftpTransferStatus.WAIT.getStatus()); + uploadFiles.add(upload); + fileTransferLogDAO.insert(upload); + // 通知添加 + transferProcessorManager.notifySessionAddEvent(userId, requestFile.getMachineId(), upload); + } + // 执行上传 + for (FileTransferLogDO uploadFile : uploadFiles) { + IFileTransferProcessor.of(FileTransferHint.transfer(uploadFile)).exec(); + } + // 设置日志参数 + List machineIdList = requestFiles.stream() + .map(FileUploadRequest::getMachineId) + .distinct() + .collect(Collectors.toList()); + List remoteFiles = requestFiles.stream() + .map(FileUploadRequest::getRemotePath) + .distinct() + .collect(Collectors.toList()); + EventParamsHolder.addParam(EventKeys.MACHINE_ID_LIST, machineIdList); + EventParamsHolder.addParam(EventKeys.PATHS, remoteFiles); + EventParamsHolder.addParam(EventKeys.COUNT, requestFiles.size()); + // 返回 + return requestFiles.stream() + .map(FileUploadRequest::getFileToken) + .collect(Collectors.toList()); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/CommandExecServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/CommandExecServiceImpl.java new file mode 100644 index 0000000..319f61f --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/CommandExecServiceImpl.java @@ -0,0 +1,265 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.command.ExecStatus; +import cn.orionsec.ops.constant.command.ExecType; +import cn.orionsec.ops.constant.env.EnvConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.dao.CommandExecDAO; +import cn.orionsec.ops.dao.MachineInfoDAO; +import cn.orionsec.ops.entity.domain.CommandExecDO; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.entity.request.exec.CommandExecRequest; +import cn.orionsec.ops.entity.vo.exec.CommandExecStatusVO; +import cn.orionsec.ops.entity.vo.exec.CommandExecVO; +import cn.orionsec.ops.entity.vo.exec.CommandTaskSubmitVO; +import cn.orionsec.ops.handler.exec.ExecSessionHolder; +import cn.orionsec.ops.handler.exec.IExecHandler; +import cn.orionsec.ops.service.api.CommandExecService; +import cn.orionsec.ops.service.api.MachineEnvService; +import cn.orionsec.ops.service.api.MachineInfoService; +import cn.orionsec.ops.service.api.SystemEnvService; +import cn.orionsec.ops.utils.*; +import com.baomidou.mybatisplus.core.conditions.Wrapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.*; +import java.util.stream.Collectors; + +@Service("commandExecService") +public class CommandExecServiceImpl implements CommandExecService { + + @Resource + private CommandExecDAO commandExecDAO; + + @Resource + private MachineInfoDAO machineInfoDAO; + + @Resource + private MachineInfoService machineInfoService; + + @Resource + private MachineEnvService machineEnvService; + + @Resource + private SystemEnvService systemEnvService; + + @Resource + private ExecSessionHolder execSessionHolder; + + @Override + public List batchSubmitTask(CommandExecRequest request) { + UserDTO user = Currents.getUser(); + List machineIdList = request.getMachineIdList(); + // 查询机器信息 + Map machineStore = Maps.newMap(); + for (Long mid : machineIdList) { + MachineInfoDO machine = machineInfoService.selectById(mid); + Valid.notNull(machine, MessageConst.INVALID_MACHINE); + machineStore.put(machine.getId(), machine); + } + // 设置命令 + String command = request.getCommand(); + final boolean containsEnv = command.contains(EnvConst.SYMBOL); + if (containsEnv) { + // 查询系统环境变量 + Map systemEnv = systemEnvService.getFullSystemEnv(); + command = Strings.format(command, EnvConst.SYMBOL, systemEnv); + } + // 批量执行命令 + List list = Lists.newList(); + for (Long mid : machineIdList) { + MachineInfoDO machine = machineStore.get(mid); + // 插入执行命令 + CommandExecDO record = new CommandExecDO(); + record.setUserId(user.getId()); + record.setUserName(user.getUsername()); + record.setMachineId(mid); + record.setMachineName(machine.getMachineName()); + record.setMachineHost(machine.getMachineHost()); + record.setMachineTag(machine.getMachineTag()); + record.setExecType(ExecType.BATCH_EXEC.getType()); + record.setExecStatus(ExecStatus.WAITING.getStatus()); + record.setDescription(request.getDescription()); + if (containsEnv) { + // 查询机器环境变量 + Map machineEnv = machineEnvService.getFullMachineEnv(mid); + record.setExecCommand(Strings.format(command, EnvConst.SYMBOL, machineEnv)); + } else { + record.setExecCommand(command); + } + commandExecDAO.insert(record); + // 设置日志路径 + Long execId = record.getId(); + String logPath = PathBuilders.getExecLogPath(Const.COMMAND_DIR, execId, machine.getId()); + CommandExecDO update = new CommandExecDO(); + update.setId(execId); + update.setLogPath(logPath); + record.setLogPath(logPath); + commandExecDAO.updateById(update); + // 提交执行任务 + IExecHandler.with(execId).exec(); + // 返回 + CommandTaskSubmitVO submitVO = new CommandTaskSubmitVO(); + submitVO.setExecId(execId); + submitVO.setMachineId(mid); + submitVO.setMachineName(machine.getMachineName()); + submitVO.setMachineHost(machine.getMachineHost()); + list.add(submitVO); + } + // 设置日志参数 + List idList = list.stream() + .map(CommandTaskSubmitVO::getExecId) + .collect(Collectors.toList()); + EventParamsHolder.addParams(request); + EventParamsHolder.addParam(EventKeys.ID_LIST, idList); + EventParamsHolder.addParam(EventKeys.COUNT, list.size()); + return list; + } + + @Override + public DataGrid execList(CommandExecRequest request) { + if (!Currents.isAdministrator()) { + request.setUserId(Currents.getUserId()); + } + if (!Strings.isBlank(request.getHost())) { + List machineIds = machineInfoDAO.selectIdByHost(request.getHost()); + if (machineIds.isEmpty()) { + return new DataGrid<>(); + } + request.setMachineIdList(machineIds); + } + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(Objects.nonNull(request.getId()), CommandExecDO::getId, request.getId()) + .eq(Objects.nonNull(request.getUserId()), CommandExecDO::getUserId, request.getUserId()) + .eq(Objects.nonNull(request.getStatus()), CommandExecDO::getExecStatus, request.getStatus()) + .eq(Objects.nonNull(request.getType()), CommandExecDO::getExecType, request.getType()) + .eq(Objects.nonNull(request.getExitCode()), CommandExecDO::getExitCode, request.getExitCode()) + .eq(Objects.nonNull(request.getMachineId()), CommandExecDO::getMachineId, request.getMachineId()) + .like(Strings.isNotBlank(request.getMachineName()), CommandExecDO::getMachineName, request.getMachineName()) + .like(Strings.isNotBlank(request.getCommand()), CommandExecDO::getExecCommand, request.getCommand()) + .like(Strings.isNotBlank(request.getUsername()), CommandExecDO::getUserName, request.getUsername()) + .like(Strings.isNotBlank(request.getDescription()), CommandExecDO::getDescription, request.getDescription()) + .in(Lists.isNotEmpty(request.getMachineIdList()), CommandExecDO::getMachineId, request.getMachineIdList()) + .orderByDesc(CommandExecDO::getId); + DataGrid dataGrid = DataQuery.of(commandExecDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(CommandExecVO.class); + if (!dataGrid.isEmpty()) { + this.assembleExecData(dataGrid.getRows(), request.isOmitCommand()); + } + return dataGrid; + } + + @Override + public CommandExecVO execDetail(Long id) { + CommandExecDO execDO = this.selectById(id); + Valid.notNull(execDO, MessageConst.EXEC_TASK_ABSENT); + CommandExecVO execVO = Converts.to(execDO, CommandExecVO.class); + this.assembleExecData(Collections.singletonList(execVO), false); + return execVO; + } + + @Override + public void writeCommand(Long id, String command) { + CommandExecDO execDO = this.selectById(id); + Valid.notNull(execDO, MessageConst.EXEC_TASK_ABSENT); + Valid.isTrue(ExecStatus.RUNNABLE.getStatus().equals(execDO.getExecStatus()), MessageConst.ILLEGAL_STATUS); + // 获取任务信息 + IExecHandler session = execSessionHolder.getSession(id); + Valid.notNull(session, MessageConst.EXEC_TASK_THREAD_ABSENT); + session.write(command); + } + + @Override + public void terminateExec(Long id) { + CommandExecDO execDO = this.selectById(id); + Valid.notNull(execDO, MessageConst.EXEC_TASK_ABSENT); + Valid.isTrue(ExecStatus.RUNNABLE.getStatus().equals(execDO.getExecStatus()), MessageConst.ILLEGAL_STATUS); + // 获取任务并停止 + IExecHandler session = execSessionHolder.getSession(id); + Valid.notNull(session, MessageConst.SESSION_PRESENT); + session.terminate(); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + } + + @Override + public Integer deleteTask(List idList) { + List execList = commandExecDAO.selectBatchIds(idList); + Valid.notEmpty(execList, MessageConst.EXEC_TASK_ABSENT); + // 检查是否可删除 + boolean canDelete = execList.stream() + .map(CommandExecDO::getExecStatus) + .noneMatch(s -> ExecStatus.WAITING.getStatus().equals(s) || ExecStatus.RUNNABLE.getStatus().equals(s)); + Valid.isTrue(canDelete, MessageConst.ILLEGAL_STATUS); + int effect = commandExecDAO.deleteBatchIds(idList); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID_LIST, idList); + EventParamsHolder.addParam(EventKeys.COUNT, idList.size()); + return effect; + } + + @Override + public List getExecStatusList(List execIdList) { + return execIdList.stream() + .map(commandExecDAO::selectStatusById) + .filter(Objects::nonNull) + .map(s -> Converts.to(s, CommandExecStatusVO.class)) + .collect(Collectors.toList()); + } + + @Override + public CommandExecDO selectById(Long id) { + if (Currents.isAdministrator()) { + return commandExecDAO.selectById(id); + } else { + Wrapper wrapper = new LambdaQueryWrapper() + .eq(CommandExecDO::getUserId, Currents.getUserId()) + .eq(CommandExecDO::getId, id) + .last(Const.LIMIT_1); + return commandExecDAO.selectOne(wrapper); + } + } + + @Override + public String getExecLogFilePath(Long id) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(!Currents.isAdministrator(), CommandExecDO::getUserId, Currents.getUserId()) + .eq(CommandExecDO::getId, id); + return Optional.ofNullable(commandExecDAO.selectOne(wrapper)) + .map(CommandExecDO::getLogPath) + .filter(Strings::isNotBlank) + .map(s -> Files1.getPath(SystemEnvAttr.LOG_PATH.getValue(), s)) + .orElse(null); + } + + /** + * 填充组装数据 + * + * @param execList execList + * @param omitCommand 省略命令 + */ + private void assembleExecData(List execList, boolean omitCommand) { + for (CommandExecVO exec : execList) { + if (omitCommand) { + // 命令省略 + exec.setCommand(Strings.omit(exec.getCommand(), Const.EXEC_COMMAND_OMIT)); + } + } + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/CommandTemplateServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/CommandTemplateServiceImpl.java new file mode 100644 index 0000000..1a7abf5 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/CommandTemplateServiceImpl.java @@ -0,0 +1,132 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.dao.CommandTemplateDAO; +import cn.orionsec.ops.entity.domain.CommandTemplateDO; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.entity.request.template.CommandTemplateRequest; +import cn.orionsec.ops.entity.vo.template.CommandTemplateVO; +import cn.orionsec.ops.service.api.CommandTemplateService; +import cn.orionsec.ops.utils.Currents; +import cn.orionsec.ops.utils.DataQuery; +import cn.orionsec.ops.utils.EventParamsHolder; +import cn.orionsec.ops.utils.Valid; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.List; +import java.util.Objects; + +@Service("commandTemplateService") +public class CommandTemplateServiceImpl implements CommandTemplateService { + + @Resource + private CommandTemplateDAO commandTemplateDAO; + + @Override + public Long addTemplate(CommandTemplateRequest request) { + // 名称重复校验 + String name = request.getName(); + this.checkNamePresent(null, name); + // 插入 + UserDTO user = Currents.getUser(); + CommandTemplateDO entity = new CommandTemplateDO(); + entity.setTemplateName(name); + entity.setTemplateValue(request.getValue()); + entity.setCreateUserId(user.getId()); + entity.setCreateUserName(user.getUsername()); + entity.setUpdateUserId(user.getId()); + entity.setUpdateUserName(user.getUsername()); + entity.setDescription(request.getDescription()); + commandTemplateDAO.insert(entity); + // 设置日志参数 + EventParamsHolder.addParams(entity); + return entity.getId(); + } + + @Override + public Integer updateTemplate(CommandTemplateRequest request) { + // 查询模板信息 + Long id = request.getId(); + String name = request.getName(); + // 名称重复校验 + this.checkNamePresent(id, name); + CommandTemplateDO beforeTemplate = commandTemplateDAO.selectById(id); + Valid.notNull(beforeTemplate, MessageConst.TEMPLATE_ABSENT); + // 更新 + UserDTO user = Currents.getUser(); + CommandTemplateDO update = new CommandTemplateDO(); + update.setId(id); + update.setTemplateName(name); + update.setTemplateValue(request.getValue()); + update.setDescription(request.getDescription()); + update.setUpdateUserId(user.getId()); + update.setUpdateUserName(user.getUsername()); + update.setUpdateTime(new Date()); + int effect = commandTemplateDAO.updateById(update); + // 设置日志参数 + EventParamsHolder.addParams(update); + EventParamsHolder.addParam(EventKeys.NAME, beforeTemplate.getTemplateName()); + return effect; + } + + @Override + public DataGrid listTemplate(CommandTemplateRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(Objects.nonNull(request.getId()), CommandTemplateDO::getId, request.getId()) + .like(Strings.isNotBlank(request.getName()), CommandTemplateDO::getTemplateName, request.getName()) + .like(Strings.isNotBlank(request.getValue()), CommandTemplateDO::getTemplateValue, request.getValue()) + .like(Strings.isNotBlank(request.getDescription()), CommandTemplateDO::getDescription, request.getDescription()) + .orderByDesc(CommandTemplateDO::getId); + // 查询列表 + DataGrid dataGrid = DataQuery.of(commandTemplateDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(CommandTemplateVO.class); + // 省略模板 + if (request.isOmitValue()) { + dataGrid.forEach(t -> t.setValue(Strings.omit(t.getValue(), Const.TEMPLATE_OMIT))); + } + return dataGrid; + } + + @Override + public CommandTemplateVO templateDetail(Long id) { + CommandTemplateDO template = commandTemplateDAO.selectById(id); + Valid.notNull(template, MessageConst.TEMPLATE_ABSENT); + return Converts.to(template, CommandTemplateVO.class); + } + + @Override + public Integer deleteTemplate(List idList) { + // 删除 + int effect = commandTemplateDAO.deleteBatchIds(idList); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID_LIST, idList); + EventParamsHolder.addParam(EventKeys.COUNT, idList.size()); + return effect; + } + + /** + * 检查名称是否存在 + * + * @param id id + * @param name name + */ + private void checkNamePresent(Long id, String name) { + LambdaQueryWrapper presentWrapper = new LambdaQueryWrapper() + .ne(id != null, CommandTemplateDO::getId, id) + .eq(CommandTemplateDO::getTemplateName, name); + boolean present = DataQuery.of(commandTemplateDAO).wrapper(presentWrapper).present(); + Valid.isTrue(!present, MessageConst.NAME_PRESENT); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/CommonServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/CommonServiceImpl.java new file mode 100644 index 0000000..0e0989e --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/CommonServiceImpl.java @@ -0,0 +1,11 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.ops.service.api.CommonService; +import org.springframework.stereotype.Service; + +@Service("commonService") +public class CommonServiceImpl implements CommonService { + + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/FileDownloadServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/FileDownloadServiceImpl.java new file mode 100644 index 0000000..5c6a163 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/FileDownloadServiceImpl.java @@ -0,0 +1,224 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.kit.lang.id.UUIds; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.lang.utils.io.Streams; +import cn.orionsec.kit.web.servlet.web.Servlets; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.KeyConst; +import cn.orionsec.ops.constant.ResultCode; +import cn.orionsec.ops.constant.download.FileDownloadType; +import cn.orionsec.ops.dao.FileTailListDAO; +import cn.orionsec.ops.dao.MachineSecretKeyDAO; +import cn.orionsec.ops.entity.domain.FileTailListDO; +import cn.orionsec.ops.entity.domain.FileTransferLogDO; +import cn.orionsec.ops.entity.domain.MachineSecretKeyDO; +import cn.orionsec.ops.entity.dto.file.FileDownloadDTO; +import cn.orionsec.ops.handler.sftp.direct.DirectDownloader; +import cn.orionsec.ops.service.api.*; +import cn.orionsec.ops.utils.Currents; +import com.alibaba.fastjson.JSON; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +@Service("fileDownloadService") +public class FileDownloadServiceImpl implements FileDownloadService { + + @Resource + private MachineSecretKeyDAO machineSecretKeyDAO; + + @Resource + private CommandExecService commandExecService; + + @Resource + private MachineTerminalService machineTerminalService; + + @Resource + private FileTailListDAO fileTailListDAO; + + @Resource + private ApplicationActionLogService applicationActionLogService; + + @Resource + private ApplicationBuildService applicationBuildService; + + @Resource + private ApplicationReleaseMachineService applicationReleaseMachineService; + + @Resource + private SchedulerTaskMachineRecordService schedulerTaskMachineRecordService; + + @Resource + private SftpService sftpService; + + @Resource + private RedisTemplate redisTemplate; + + @Override + public String getDownloadToken(Long id, FileDownloadType type) { + String path = null; + String name = null; + Long machineId = Const.HOST_MACHINE_ID; + // 获取日志绝对路径 + switch (type) { + case SECRET_KEY: + // 密钥 + path = this.getDownloadSecretKeyFilePath(id); + name = Optional.ofNullable(path).map(Files1::getFileName).orElse(null); + break; + case TERMINAL_SCREEN: + // terminal 录屏 + path = machineTerminalService.getTerminalScreenFilePath(id); + name = Optional.ofNullable(path).map(Files1::getFileName).orElse(null); + break; + case EXEC_LOG: + // 执行日志 + path = commandExecService.getExecLogFilePath(id); + name = Optional.ofNullable(path).map(Files1::getFileName).orElse(null); + break; + case SFTP_DOWNLOAD: + // sftp 下载文件 + FileTransferLogDO transferLog = sftpService.getDownloadFilePath(id); + if (transferLog != null) { + path = transferLog.getLocalFile(); + name = Files1.getFileName(transferLog.getRemoteFile()); + } + break; + case TAIL_LIST_FILE: + // tail 列表文件 + FileTailListDO tailFile = fileTailListDAO.selectById(id); + if (tailFile != null) { + path = tailFile.getFilePath(); + name = Files1.getFileName(path); + machineId = tailFile.getMachineId(); + } + break; + case APP_BUILD_LOG: + // 应用构建日志 + path = applicationBuildService.getBuildLogPath(id); + name = Optional.ofNullable(path).map(Files1::getFileName).orElse(null); + break; + case APP_ACTION_LOG: + // 应用构建操作日志 + path = applicationActionLogService.getActionLogPath(id); + name = Optional.ofNullable(path).map(Files1::getFileName).orElse(null); + break; + case APP_BUILD_BUNDLE: + // 应用构建 产物文件 + path = applicationBuildService.getBuildBundlePath(id); + // 如果是文件夹则获取压缩文件 + if (path != null && Files1.isDirectory(path)) { + path += "." + Const.SUFFIX_ZIP; + } + name = Optional.ofNullable(path).map(Files1::getFileName).orElse(null); + break; + case APP_RELEASE_MACHINE_LOG: + // 应用发布机器日志 + path = applicationReleaseMachineService.getReleaseMachineLogPath(id); + name = Optional.ofNullable(path).map(Files1::getFileName).orElse(null); + break; + case SCHEDULER_TASK_MACHINE_LOG: + // 调度任务机器日志 + path = schedulerTaskMachineRecordService.getTaskMachineLogPath(id); + name = Optional.ofNullable(path).map(Files1::getFileName).orElse(null); + break; + default: + break; + } + // 检查文件是否存在 + if (path == null || (Const.HOST_MACHINE_ID.equals(machineId) && !Files1.isFile(path))) { + throw Exceptions.httpWrapper(HttpWrapper.of(ResultCode.FILE_MISSING)); + } + // 设置缓存 + FileDownloadDTO download = new FileDownloadDTO(); + download.setFilePath(path); + download.setFileName(Strings.def(name, Const.UNKNOWN)); + download.setUserId(Currents.getUserId()); + download.setType(type.getType()); + download.setMachineId(machineId); + String token = UUIds.random19(); + String key = Strings.format(KeyConst.FILE_DOWNLOAD_TOKEN, token); + redisTemplate.opsForValue().set(key, JSON.toJSONString(download), + KeyConst.FILE_DOWNLOAD_EXPIRE, TimeUnit.SECONDS); + return token; + } + + @Override + public FileDownloadDTO getPathByDownloadToken(String token) { + if (Strings.isBlank(token)) { + return null; + } + String key = Strings.format(KeyConst.FILE_DOWNLOAD_TOKEN, token); + String value = redisTemplate.opsForValue().get(key); + if (Strings.isBlank(value)) { + return null; + } + FileDownloadDTO download = JSON.parseObject(value, FileDownloadDTO.class); + if (download == null) { + return null; + } + redisTemplate.delete(key); + return download; + } + + @Override + public void execDownload(String token, HttpServletResponse response) throws IOException { + // 获取token信息 + FileDownloadDTO downloadFile = this.getPathByDownloadToken(token); + if (downloadFile == null) { + throw Exceptions.notFound(); + } + Long machineId = downloadFile.getMachineId(); + InputStream inputStream = null; + DirectDownloader downloader = null; + try { + if (Const.HOST_MACHINE_ID.equals(machineId)) { + // 本地文件 + File file = Optional.of(downloadFile) + .map(FileDownloadDTO::getFilePath) + .filter(Strings::isNotBlank) + .map(File::new) + .filter(Files1::isFile) + .orElseThrow(Exceptions::notFound); + inputStream = Files1.openInputStreamFastSafe(file); + } else { + // 远程文件 + downloader = new DirectDownloader(machineId); + inputStream = downloader.open().getFile(downloadFile.getFilePath()); + } + // 返回 + Servlets.transfer(response, inputStream, downloadFile.getFileName()); + } finally { + Streams.close(inputStream); + Streams.close(downloader); + } + } + + /** + * 获取下载 密钥路径 + * + * @param id id + * @return path + * @see FileDownloadType#SECRET_KEY + */ + private String getDownloadSecretKeyFilePath(Long id) { + return Optional.ofNullable(machineSecretKeyDAO.selectById(id)) + .map(MachineSecretKeyDO::getSecretKeyPath) + .map(MachineKeyService::getKeyPath) + .filter(Strings::isNotBlank) + .orElse(null); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/FileTailServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/FileTailServiceImpl.java new file mode 100644 index 0000000..a15a5a0 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/FileTailServiceImpl.java @@ -0,0 +1,362 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.kit.lang.id.UUIds; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.KeyConst; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.ResultCode; +import cn.orionsec.ops.constant.env.EnvConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.tail.FileTailMode; +import cn.orionsec.ops.constant.tail.FileTailType; +import cn.orionsec.ops.dao.FileTailListDAO; +import cn.orionsec.ops.entity.domain.FileTailListDO; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.dto.file.FileTailDTO; +import cn.orionsec.ops.entity.request.file.FileTailRequest; +import cn.orionsec.ops.entity.vo.tail.FileTailConfigVO; +import cn.orionsec.ops.entity.vo.tail.FileTailVO; +import cn.orionsec.ops.handler.tail.ITailHandler; +import cn.orionsec.ops.handler.tail.TailSessionHolder; +import cn.orionsec.ops.service.api.*; +import cn.orionsec.ops.utils.*; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.io.File; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +@Service("fileTailService") +public class FileTailServiceImpl implements FileTailService { + + @Resource + private MachineEnvService machineEnvService; + + @Resource + private MachineInfoService machineInfoService; + + @Resource + private CommandExecService commandExecService; + + @Resource + private ApplicationBuildService applicationBuildService; + + @Resource + private ApplicationReleaseMachineService applicationReleaseMachineService; + + @Resource + private SchedulerTaskMachineRecordService schedulerTaskMachineRecordService; + + @Resource + private ApplicationActionLogService applicationActionLogService; + + @Resource + private FileTailListDAO fileTailListDAO; + + @Resource + private TailSessionHolder tailSessionHolder; + + @Resource + private RedisTemplate redisTemplate; + + @Override + public FileTailVO getTailToken(FileTailType type, Long relId) { + FileTailVO res; + // 获取日志路径 + final boolean isLocal = type.isLocal(); + if (isLocal) { + String path = this.getTailFilePath(type, relId); + res = new FileTailVO(); + res.setPath(path); + res.setOffset(machineEnvService.getTailOffset(Const.HOST_MACHINE_ID)); + res.setCharset(machineEnvService.getTailCharset(Const.HOST_MACHINE_ID)); + res.setCommand(machineEnvService.getTailDefaultCommand(Const.HOST_MACHINE_ID)); + } else { + // tail list + FileTailListDO fileTail = fileTailListDAO.selectById(relId); + Valid.notNull(fileTail, MessageConst.UNKNOWN_DATA); + res = Converts.to(fileTail, FileTailVO.class); + } + // 设置命令 + this.replaceTailCommand(res); + // 设置机器信息 + MachineInfoDO machine = machineInfoService.selectById(isLocal ? Const.HOST_MACHINE_ID : res.getMachineId()); + Valid.notNull(machine, MessageConst.INVALID_MACHINE); + res.setMachineId(machine.getId()); + res.setMachineName(machine.getMachineName()); + res.setMachineHost(machine.getMachineHost()); + res.setMachineStatus(machine.getMachineStatus()); + // 设置token + String token = UUIds.random19(); + res.setToken(token); + // 设置缓存 + FileTailDTO tail = Converts.to(res, FileTailDTO.class); + tail.setUserId(Currents.getUserId()); + // 追踪模式 + String tailMode = isLocal ? FileTailMode.getHostTailMode() : res.getTailMode(); + tail.setMode(tailMode); + String key = Strings.format(KeyConst.FILE_TAIL_ACCESS_TOKEN, token); + redisTemplate.opsForValue().set(key, JSON.toJSONString(tail), KeyConst.FILE_TAIL_ACCESS_EXPIRE, TimeUnit.SECONDS); + // 本地则不返回命令 + if (isLocal) { + res.setCommand(null); + } + res.setTailMode(tailMode); + return res; + } + + @Override + public Long insertTailFile(FileTailRequest request) { + // 名称重复校验 + Long machineId = request.getMachineId(); + String name = request.getName(); + this.checkNamePresent(null, name); + // 插入 + FileTailListDO insert = new FileTailListDO(); + insert.setAliasName(name); + insert.setMachineId(machineId); + insert.setFilePath(request.getPath()); + insert.setFileCharset(request.getCharset()); + insert.setFileOffset(request.getOffset()); + insert.setTailCommand(request.getCommand()); + insert.setTailMode(FileTailMode.of(request.getTailMode(), Const.HOST_MACHINE_ID.equals(machineId)).getMode()); + fileTailListDAO.insert(insert); + // 设置日志参数 + EventParamsHolder.addParams(insert); + return insert.getId(); + } + + @Override + public Integer updateTailFile(FileTailRequest request) { + // 名称重复校验 + Long id = request.getId(); + String name = request.getName(); + this.checkNamePresent(id, name); + // 查询文件 + FileTailListDO beforeTail = fileTailListDAO.selectById(id); + Valid.notNull(beforeTail, MessageConst.UNKNOWN_DATA); + Long machineId = request.getMachineId(); + // 修改 + FileTailListDO update = new FileTailListDO(); + update.setId(id); + update.setAliasName(name); + update.setMachineId(machineId); + update.setFilePath(request.getPath()); + update.setFileOffset(request.getOffset()); + update.setFileCharset(request.getCharset()); + update.setTailCommand(request.getCommand()); + update.setTailMode(FileTailMode.of(request.getTailMode(), Const.HOST_MACHINE_ID.equals(machineId)).getMode()); + update.setUpdateTime(new Date()); + int effect = fileTailListDAO.updateById(update); + // 设置日志参数 + EventParamsHolder.addParams(update); + EventParamsHolder.addParam(EventKeys.NAME, beforeTail.getAliasName()); + return effect; + } + + @Override + public void uploadTailFiles(List files) { + // 获取宿主机配置 + FileTailConfigVO config = this.getMachineConfig(Const.HOST_MACHINE_ID); + // 设置插入数据 + List insertFiles = Lists.newList(); + for (FileTailRequest file : files) { + // 名称重复校验 + String name = file.getName(); + if (name.length() > 32) { + name = name.substring(0, 32); + } + try { + this.checkNamePresent(Const.HOST_MACHINE_ID, name); + } catch (Exception e) { + name = Utils.getRandomSuffix() + Const.DASHED + name; + } + // 插入 + FileTailListDO insert = new FileTailListDO(); + insert.setAliasName(name); + insert.setMachineId(Const.HOST_MACHINE_ID); + insert.setFilePath(file.getPath()); + insert.setFileCharset(config.getCharset()); + insert.setFileOffset(config.getOffset()); + insert.setTailCommand(config.getCommand()); + insert.setTailMode(FileTailMode.TRACKER.getMode()); + insertFiles.add(insert); + } + insertFiles.forEach(fileTailListDAO::insert); + // 设置日志参数 + EventParamsHolder.addParams(insertFiles); + EventParamsHolder.addParam(EventKeys.COUNT, files.size()); + } + + @Override + public Integer deleteTailFile(List idList) { + // 删除 + int effect = fileTailListDAO.deleteBatchIds(idList); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID_LIST, idList); + EventParamsHolder.addParam(EventKeys.COUNT, effect); + return effect; + } + + @Override + public Integer deleteByMachineIdList(List machineIdList) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .in(FileTailListDO::getMachineId, machineIdList); + return fileTailListDAO.delete(wrapper); + } + + @Override + public DataGrid tailFileList(FileTailRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(Objects.nonNull(request.getMachineId()), FileTailListDO::getMachineId, request.getMachineId()) + .like(!Strings.isEmpty(request.getName()), FileTailListDO::getAliasName, request.getName()) + .like(!Strings.isEmpty(request.getPath()), FileTailListDO::getFilePath, request.getPath()) + .like(!Strings.isEmpty(request.getCommand()), FileTailListDO::getTailCommand, request.getCommand()) + .orderByDesc(FileTailListDO::getUpdateTime); + DataGrid dataGrid = DataQuery.of(fileTailListDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(FileTailVO.class); + // 设置机器信息 + Map machineCache = Maps.newMap(); + dataGrid.forEach(p -> { + MachineInfoDO machine = machineCache.computeIfAbsent(p.getMachineId(), + mid -> machineInfoService.selectById(p.getMachineId())); + if (machine != null) { + p.setMachineName(machine.getMachineName()); + p.setMachineHost(machine.getMachineHost()); + p.setMachineStatus(machine.getMachineStatus()); + } + }); + return dataGrid; + } + + @Override + public FileTailVO tailFileDetail(Long id) { + FileTailListDO tail = fileTailListDAO.selectById(id); + Valid.notNull(tail, MessageConst.UNKNOWN_DATA); + FileTailVO vo = Converts.to(tail, FileTailVO.class); + // 设置机器信息 + MachineInfoDO machine = machineInfoService.selectById(tail.getMachineId()); + Valid.notNull(machine, MessageConst.INVALID_MACHINE); + vo.setMachineName(machine.getMachineName()); + vo.setMachineHost(machine.getMachineHost()); + vo.setMachineStatus(machine.getMachineStatus()); + return vo; + } + + /** + * 获取本机 tail 路径 + * + * @param type type + * @param relId relId + * @return path + */ + private String getTailFilePath(FileTailType type, Long relId) { + String path; + switch (type) { + case EXEC_LOG: + // 执行日志 + path = commandExecService.getExecLogFilePath(relId); + break; + case APP_BUILD_LOG: + // 应用构建日志 + path = applicationBuildService.getBuildLogPath(relId); + break; + case APP_RELEASE_LOG: + // 应用发布日志 + path = applicationReleaseMachineService.getReleaseMachineLogPath(relId); + break; + case SCHEDULER_TASK_MACHINE_LOG: + // 调度任务机器日志 + path = schedulerTaskMachineRecordService.getTaskMachineLogPath(relId); + break; + case APP_ACTION_LOG: + // 应用操作日志 + path = applicationActionLogService.getActionLogPath(relId); + break; + default: + path = null; + } + // 检查文件是否存在 + File file; + if (path == null || !Files1.isFile(file = new File(path))) { + throw Exceptions.httpWrapper(HttpWrapper.of(ResultCode.FILE_MISSING)); + } + return file.getAbsolutePath(); + } + + /** + * 替换 tail 命令 + * + * @param res res + */ + private void replaceTailCommand(FileTailVO res) { + Map env = Maps.newMap(4); + env.put(EnvConst.FILE, Files1.getPath(res.getPath())); + env.put(EnvConst.OFFSET, res.getOffset()); + res.setCommand(Strings.format(res.getCommand(), EnvConst.SYMBOL, env)); + } + + @Override + public Integer updateFileUpdateTime(Long id) { + FileTailListDO update = new FileTailListDO(); + update.setId(id); + update.setUpdateTime(new Date()); + return fileTailListDAO.updateById(update); + } + + @Override + public FileTailConfigVO getMachineConfig(Long machineId) { + Valid.notNull(machineInfoService.selectById(machineId), MessageConst.INVALID_MACHINE); + FileTailConfigVO config = new FileTailConfigVO(); + // offset + Integer offset = machineEnvService.getTailOffset(machineId); + config.setOffset(offset); + // charset + String charset = machineEnvService.getTailCharset(machineId); + config.setCharset(charset); + // command + config.setCommand(machineEnvService.getTailDefaultCommand(machineId)); + return config; + } + + @Override + public void writeCommand(String token, String command) { + // 获取任务信息 + ITailHandler session = tailSessionHolder.getSession(token); + Valid.notNull(session, MessageConst.EXEC_TASK_THREAD_ABSENT); + session.write(command); + } + + /** + * 检查名称是否存在 + * + * @param id id + * @param name name + */ + private void checkNamePresent(Long id, String name) { + LambdaQueryWrapper presentWrapper = new LambdaQueryWrapper() + .ne(id != null, FileTailListDO::getId, id) + .eq(FileTailListDO::getAliasName, name); + boolean present = DataQuery.of(fileTailListDAO).wrapper(presentWrapper).present(); + Valid.isTrue(!present, MessageConst.NAME_PRESENT); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/HistoryValueServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/HistoryValueServiceImpl.java new file mode 100644 index 0000000..41e8c6f --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/HistoryValueServiceImpl.java @@ -0,0 +1,116 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.history.HistoryOperator; +import cn.orionsec.ops.constant.history.HistoryValueType; +import cn.orionsec.ops.dao.HistoryValueSnapshotDAO; +import cn.orionsec.ops.entity.domain.HistoryValueSnapshotDO; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.entity.request.app.ApplicationEnvRequest; +import cn.orionsec.ops.entity.request.history.HistoryValueRequest; +import cn.orionsec.ops.entity.request.machine.MachineEnvRequest; +import cn.orionsec.ops.entity.request.system.SystemEnvRequest; +import cn.orionsec.ops.entity.vo.history.HistoryValueVO; +import cn.orionsec.ops.service.api.ApplicationEnvService; +import cn.orionsec.ops.service.api.HistoryValueService; +import cn.orionsec.ops.service.api.MachineEnvService; +import cn.orionsec.ops.service.api.SystemEnvService; +import cn.orionsec.ops.utils.Currents; +import cn.orionsec.ops.utils.DataQuery; +import cn.orionsec.ops.utils.Valid; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Date; + +@Service("historyValueService") +public class HistoryValueServiceImpl implements HistoryValueService { + + @Resource + private HistoryValueSnapshotDAO historyValueSnapshotDAO; + + @Resource + private MachineEnvService machineEnvService; + + @Resource + private ApplicationEnvService applicationEnvService; + + @Resource + private SystemEnvService systemEnvService; + + @Override + public void addHistory(Long valueId, HistoryValueType valueType, HistoryOperator operatorType, String beforeValue, String afterValue) { + UserDTO user = Currents.getUser(); + HistoryValueSnapshotDO insert = new HistoryValueSnapshotDO(); + insert.setValueId(valueId); + insert.setOperatorType(operatorType.getType()); + insert.setValueType(valueType.getType()); + insert.setBeforeValue(beforeValue); + insert.setAfterValue(afterValue); + if (user != null) { + insert.setUpdateUserId(user.getId()); + insert.setUpdateUserName(user.getUsername()); + } + insert.setCreateTime(new Date()); + insert.setUpdateTime(new Date()); + historyValueSnapshotDAO.insert(insert); + } + + @Override + public DataGrid list(HistoryValueRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(HistoryValueSnapshotDO::getValueId, request.getValueId()) + .eq(HistoryValueSnapshotDO::getValueType, request.getValueType()) + .orderByDesc(HistoryValueSnapshotDO::getId); + return DataQuery.of(historyValueSnapshotDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(HistoryValueVO.class); + } + + @Override + public void rollback(Long id) { + HistoryValueSnapshotDO historyValue = historyValueSnapshotDAO.selectById(id); + Valid.notNull(historyValue, MessageConst.HISTORY_VALUE_ABSENT); + // 设置修改值 + HistoryOperator operator = HistoryOperator.of(historyValue.getOperatorType()); + String updateValue; + switch (operator) { + case ADD: + updateValue = historyValue.getAfterValue(); + break; + case UPDATE: + case DELETE: + default: + updateValue = historyValue.getBeforeValue(); + } + // 修改值 + Long valueId = historyValue.getValueId(); + HistoryValueType valueType = HistoryValueType.of(historyValue.getValueType()); + switch (valueType) { + case MACHINE_ENV: + MachineEnvRequest machineEnvRequest = new MachineEnvRequest(); + machineEnvRequest.setId(valueId); + machineEnvRequest.setValue(updateValue); + machineEnvService.updateEnv(machineEnvRequest); + return; + case APP_ENV: + ApplicationEnvRequest appEnvRequest = new ApplicationEnvRequest(); + appEnvRequest.setId(valueId); + appEnvRequest.setValue(updateValue); + applicationEnvService.updateAppEnv(appEnvRequest); + return; + case SYSTEM_ENV: + SystemEnvRequest systemRequest = new SystemEnvRequest(); + systemRequest.setId(valueId); + systemRequest.setValue(updateValue); + systemEnvService.updateEnv(systemRequest); + return; + default: + } + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineAlarmConfigServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineAlarmConfigServiceImpl.java new file mode 100644 index 0000000..ef3faef --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineAlarmConfigServiceImpl.java @@ -0,0 +1,155 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.machine.MachineAlarmType; +import cn.orionsec.ops.dao.MachineAlarmConfigDAO; +import cn.orionsec.ops.dao.MachineAlarmGroupDAO; +import cn.orionsec.ops.entity.domain.MachineAlarmConfigDO; +import cn.orionsec.ops.entity.domain.MachineAlarmGroupDO; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.domain.MachineMonitorDO; +import cn.orionsec.ops.entity.request.machine.MachineAlarmConfigRequest; +import cn.orionsec.ops.entity.vo.machine.MachineAlarmConfigVO; +import cn.orionsec.ops.entity.vo.machine.MachineAlarmConfigWrapperVO; +import cn.orionsec.ops.service.api.MachineAlarmConfigService; +import cn.orionsec.ops.service.api.MachineAlarmGroupService; +import cn.orionsec.ops.service.api.MachineInfoService; +import cn.orionsec.ops.service.api.MachineMonitorService; +import cn.orionsec.ops.utils.EventParamsHolder; +import cn.orionsec.ops.utils.Valid; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.List; +import java.util.stream.Collectors; + +@Service("machineAlarmConfigService") +public class MachineAlarmConfigServiceImpl implements MachineAlarmConfigService { + + @Resource + private MachineAlarmConfigDAO machineAlarmConfigDAO; + + @Resource + private MachineAlarmGroupDAO machineAlarmGroupDAO; + + @Resource + private MachineAlarmGroupService machineAlarmGroupService; + + @Resource + private MachineInfoService machineInfoService; + + @Resource + private MachineMonitorService machineMonitorService; + + @Override + public MachineAlarmConfigWrapperVO getAlarmConfigInfo(Long machineId) { + MachineAlarmConfigWrapperVO wrapper = new MachineAlarmConfigWrapperVO(); + // 查询配置 + List config = this.selectByMachineId(machineId); + List alarmConfig = Converts.toList(config, MachineAlarmConfigVO.class); + // 查询报警组 + List group = machineAlarmGroupService.selectByMachineId(machineId); + List groupIdList = group.stream() + .map(MachineAlarmGroupDO::getGroupId) + .collect(Collectors.toList()); + wrapper.setConfig(alarmConfig); + wrapper.setGroupIdList(groupIdList); + return wrapper; + } + + @Override + public List getAlarmConfig(Long machineId) { + // 查询配置 + List config = this.selectByMachineId(machineId); + return Converts.toList(config, MachineAlarmConfigVO.class); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void setAlarmConfig(MachineAlarmConfigRequest request) { + // 查询机器信息 + Long machineId = request.getMachineId(); + MachineInfoDO machine = machineInfoService.selectById(machineId); + Valid.notNull(machine, MessageConst.INVALID_MACHINE); + // 删除配置 + Integer type = request.getType(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(MachineAlarmConfigDO::getMachineId, machineId) + .eq(MachineAlarmConfigDO::getAlarmType, type); + machineAlarmConfigDAO.delete(wrapper); + // 插入配置 + MachineAlarmConfigDO config = new MachineAlarmConfigDO(); + config.setMachineId(machineId); + config.setAlarmType(type); + config.setAlarmThreshold(request.getAlarmThreshold()); + config.setTriggerThreshold(request.getTriggerThreshold()); + config.setNotifySilence(request.getNotifySilence()); + machineAlarmConfigDAO.insert(config); + // 同步报警配置 + MachineMonitorDO machineMonitor = machineMonitorService.selectByMachineId(machineId); + if (machineMonitor != null) { + machineMonitorService.syncMonitorAgent(machineId, machineMonitor.getMonitorUrl(), machineMonitor.getAccessToken()); + } + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.NAME, machine.getMachineName()); + EventParamsHolder.addParam(EventKeys.MACHINE_ID, machineId); + EventParamsHolder.addParam(EventKeys.LABEL, MachineAlarmType.of(type).getLabel()); + EventParamsHolder.addParams(request); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void setAlarmGroup(Long machineId, List groupIdList) { + // 查询机器信息 + MachineInfoDO machine = machineInfoService.selectById(machineId); + Valid.notNull(machine, MessageConst.INVALID_MACHINE); + // 删除报警组 + machineAlarmGroupService.deleteByMachineId(machineId); + // 插入报警组 + groupIdList.stream() + .map(g -> { + MachineAlarmGroupDO group = new MachineAlarmGroupDO(); + group.setMachineId(machineId); + group.setGroupId(g); + return group; + }).forEach(machineAlarmGroupDAO::insert); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.NAME, machine.getMachineName()); + EventParamsHolder.addParam(EventKeys.MACHINE_ID, machineId); + EventParamsHolder.addParam(EventKeys.ID_LIST, groupIdList); + } + + @Override + public List selectByMachineId(Long machineId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(MachineAlarmConfigDO::getMachineId, machineId); + return machineAlarmConfigDAO.selectList(wrapper); + } + + @Override + public Integer selectCountByMachineId(Long machineId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(MachineAlarmConfigDO::getMachineId, machineId); + return machineAlarmConfigDAO.selectCount(wrapper); + } + + @Override + public Integer deleteByMachineId(Long machineId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(MachineAlarmConfigDO::getMachineId, machineId); + return machineAlarmConfigDAO.delete(wrapper); + } + + @Override + public Integer deleteByMachineIdList(List machineIdList) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .in(MachineAlarmConfigDO::getMachineId, machineIdList); + return machineAlarmConfigDAO.delete(wrapper); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineAlarmGroupServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineAlarmGroupServiceImpl.java new file mode 100644 index 0000000..6c96870 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineAlarmGroupServiceImpl.java @@ -0,0 +1,47 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.ops.dao.MachineAlarmGroupDAO; +import cn.orionsec.ops.entity.domain.MachineAlarmGroupDO; +import cn.orionsec.ops.service.api.MachineAlarmGroupService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; + +@Service("machineAlarmGroupService") +public class MachineAlarmGroupServiceImpl implements MachineAlarmGroupService { + + @Resource + private MachineAlarmGroupDAO machineAlarmGroupDAO; + + @Override + public List selectByMachineId(Long machineId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(MachineAlarmGroupDO::getMachineId, machineId); + return machineAlarmGroupDAO.selectList(wrapper); + } + + @Override + public Integer deleteByMachineId(Long machineId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(MachineAlarmGroupDO::getMachineId, machineId); + return machineAlarmGroupDAO.delete(wrapper); + } + + @Override + public Integer deleteByMachineIdList(List machineIdList) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .in(MachineAlarmGroupDO::getMachineId, machineIdList); + return machineAlarmGroupDAO.delete(wrapper); + } + + @Override + public Integer deleteByGroupId(Long groupId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(MachineAlarmGroupDO::getGroupId, groupId); + return machineAlarmGroupDAO.delete(wrapper); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineAlarmHistoryServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineAlarmHistoryServiceImpl.java new file mode 100644 index 0000000..549b49d --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineAlarmHistoryServiceImpl.java @@ -0,0 +1,42 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Objects1; +import cn.orionsec.ops.dao.MachineAlarmHistoryDAO; +import cn.orionsec.ops.entity.domain.MachineAlarmHistoryDO; +import cn.orionsec.ops.entity.request.machine.MachineAlarmHistoryRequest; +import cn.orionsec.ops.entity.vo.machine.MachineAlarmHistoryVO; +import cn.orionsec.ops.service.api.MachineAlarmHistoryService; +import cn.orionsec.ops.utils.DataQuery; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Objects; + +@Service("machineAlarmHistoryService") +public class MachineAlarmHistoryServiceImpl implements MachineAlarmHistoryService { + + // TODO 最近7天的报警记录 + + @Resource + private MachineAlarmHistoryDAO machineAlarmHistoryDAO; + + @Override + public DataGrid getAlarmHistory(MachineAlarmHistoryRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(MachineAlarmHistoryDO::getMachineId, request.getMachineId()) + .eq(Objects.nonNull(request.getType()), MachineAlarmHistoryDO::getAlarmType, request.getType()) + .between(Objects1.isNoneNull(request.getAlarmValueStart(), request.getAlarmValueEnd()), + MachineAlarmHistoryDO::getAlarmValue, request.getAlarmValueStart(), request.getAlarmValueEnd()) + .between(Objects1.isNoneNull(request.getAlarmTimeStart(), request.getAlarmTimeEnd()), + MachineAlarmHistoryDO::getAlarmTime, request.getAlarmTimeStart(), request.getAlarmTimeEnd()) + .orderByDesc(MachineAlarmHistoryDO::getAlarmTime); + return DataQuery.of(machineAlarmHistoryDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(MachineAlarmHistoryVO.class); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineAlarmServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineAlarmServiceImpl.java new file mode 100644 index 0000000..581783d --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineAlarmServiceImpl.java @@ -0,0 +1,77 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.dao.MachineAlarmHistoryDAO; +import cn.orionsec.ops.dao.MachineInfoDAO; +import cn.orionsec.ops.entity.domain.MachineAlarmHistoryDO; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.request.machine.MachineAlarmRequest; +import cn.orionsec.ops.handler.alarm.MachineAlarmContext; +import cn.orionsec.ops.handler.alarm.MachineAlarmExecutor; +import cn.orionsec.ops.service.api.MachineAlarmService; +import cn.orionsec.ops.utils.EventParamsHolder; +import cn.orionsec.ops.utils.Valid; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +@Service("machineAlarmService") +public class MachineAlarmServiceImpl implements MachineAlarmService { + + @Resource + private MachineInfoDAO machineInfoDAO; + + @Resource + private MachineAlarmHistoryDAO machineAlarmHistoryDAO; + + @Override + public void triggerMachineAlarm(Long alarmHistoryId) { + // 查询数据 + MachineAlarmHistoryDO history = machineAlarmHistoryDAO.selectById(alarmHistoryId); + Valid.notNull(history, MessageConst.UNKNOWN_DATA); + // 查询机器信息 + Long machineId = history.getMachineId(); + MachineInfoDO machine = machineInfoDAO.selectById(machineId); + Valid.notNull(machine, MessageConst.INVALID_MACHINE); + // 执行通知操作 + MachineAlarmContext context = new MachineAlarmContext(); + context.setMachineId(machineId); + context.setMachineName(machine.getMachineName()); + context.setMachineHost(machine.getMachineHost()); + context.setAlarmType(history.getAlarmType()); + context.setAlarmValue(history.getAlarmValue()); + context.setAlarmTime(history.getAlarmTime()); + new MachineAlarmExecutor(context).exec(); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, alarmHistoryId); + EventParamsHolder.addParam(EventKeys.MACHINE_ID, machineId); + EventParamsHolder.addParam(EventKeys.NAME, machine.getMachineName()); + } + + @Override + public void triggerMachineAlarm(MachineAlarmRequest request) { + // 查询机器信息 + Long machineId = request.getMachineId(); + MachineInfoDO machine = machineInfoDAO.selectById(machineId); + Valid.notNull(machine, MessageConst.INVALID_MACHINE); + // 历史通知 + MachineAlarmHistoryDO history = new MachineAlarmHistoryDO(); + history.setMachineId(machineId); + history.setAlarmType(request.getType()); + history.setAlarmValue(request.getAlarmValue()); + history.setAlarmTime(request.getAlarmTime()); + machineAlarmHistoryDAO.insert(history); + // 执行通知操作 + MachineAlarmContext context = new MachineAlarmContext(); + context.setMachineId(machineId); + context.setMachineName(machine.getMachineName()); + context.setMachineHost(machine.getMachineHost()); + context.setAlarmType(request.getType()); + context.setAlarmValue(request.getAlarmValue()); + context.setAlarmTime(request.getAlarmTime()); + new MachineAlarmExecutor(context).exec(); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineEnvServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineEnvServiceImpl.java new file mode 100644 index 0000000..00f8436 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineEnvServiceImpl.java @@ -0,0 +1,368 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.collect.MutableLinkedHashMap; +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Charsets; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.command.CommandConst; +import cn.orionsec.ops.constant.env.EnvConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.history.HistoryOperator; +import cn.orionsec.ops.constant.history.HistoryValueType; +import cn.orionsec.ops.constant.machine.MachineConst; +import cn.orionsec.ops.constant.machine.MachineEnvAttr; +import cn.orionsec.ops.dao.MachineEnvDAO; +import cn.orionsec.ops.dao.MachineInfoDAO; +import cn.orionsec.ops.entity.domain.MachineEnvDO; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.request.machine.MachineEnvRequest; +import cn.orionsec.ops.entity.vo.machine.MachineEnvVO; +import cn.orionsec.ops.service.api.HistoryValueService; +import cn.orionsec.ops.service.api.MachineEnvService; +import cn.orionsec.ops.utils.DataQuery; +import cn.orionsec.ops.utils.EventParamsHolder; +import cn.orionsec.ops.utils.Valid; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@Service("machineEnvService") +public class MachineEnvServiceImpl implements MachineEnvService { + + @Resource + private MachineEnvDAO machineEnvDAO; + + @Resource + private MachineInfoDAO machineInfoDAO; + + @Resource + private HistoryValueService historyValueService; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long addEnv(MachineEnvRequest request) { + // 查询 + Long machineId = request.getMachineId(); + String key = request.getKey(); + // 重复检查 + MachineEnvDO env = machineEnvDAO.selectOneRel(machineId, key); + // 修改 + if (env != null) { + SpringHolder.getBean(MachineEnvService.class).updateEnv(env, request); + return env.getId(); + } + // 新增 + MachineEnvDO insert = new MachineEnvDO(); + insert.setMachineId(machineId); + insert.setAttrKey(key); + insert.setAttrValue(request.getValue()); + insert.setDescription(request.getDescription()); + machineEnvDAO.insert(insert); + // 插入历史值 + Long id = insert.getId(); + historyValueService.addHistory(id, HistoryValueType.MACHINE_ENV, HistoryOperator.ADD, null, request.getValue()); + return id; + } + + @Override + public Integer updateEnv(MachineEnvRequest request) { + // 查询 + Long id = request.getId(); + MachineEnvDO before = machineEnvDAO.selectById(id); + Valid.notNull(before, MessageConst.ENV_ABSENT); + return SpringHolder.getBean(MachineEnvService.class).updateEnv(before, request); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer updateEnv(MachineEnvDO before, MachineEnvRequest request) { + // 检查是否修改了值 + Long id = before.getId(); + String beforeValue = before.getAttrValue(); + String afterValue = request.getValue(); + if (Const.IS_DELETED.equals(before.getDeleted())) { + // 设置新增历史值 + historyValueService.addHistory(id, HistoryValueType.MACHINE_ENV, HistoryOperator.ADD, null, afterValue); + // 恢复 + machineEnvDAO.setDeleted(id, Const.NOT_DELETED); + } else if (afterValue != null && !afterValue.equals(beforeValue)) { + // 检查是否修改了值 增加历史值 + historyValueService.addHistory(id, HistoryValueType.MACHINE_ENV, HistoryOperator.UPDATE, beforeValue, afterValue); + } + // 修改 + MachineEnvDO update = new MachineEnvDO(); + update.setId(id); + update.setAttrValue(afterValue); + update.setDescription(request.getDescription()); + update.setUpdateTime(new Date()); + return machineEnvDAO.updateById(update); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer deleteEnv(List idList) { + int effect = 0; + for (Long id : idList) { + // 获取元数据 + MachineEnvDO env = machineEnvDAO.selectById(id); + Valid.notNull(env, MessageConst.ENV_ABSENT); + String key = env.getAttrKey(); + Valid.isTrue(MachineEnvAttr.of(key) == null, "{} " + MessageConst.FORBID_DELETE, key); + // 删除 + effect += machineEnvDAO.deleteById(id); + // 插入历史值 + historyValueService.addHistory(id, HistoryValueType.MACHINE_ENV, HistoryOperator.DELETE, env.getAttrValue(), null); + } + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID_LIST, idList); + EventParamsHolder.addParam(EventKeys.COUNT, effect); + return effect; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveEnv(Long machineId, Map env) { + MachineEnvService self = SpringHolder.getBean(MachineEnvService.class); + // 倒排 + List> entries = Lists.newList(env.entrySet()); + for (int i = entries.size() - 1; i >= 0; i--) { + // 更新 + Map.Entry entry = entries.get(i); + MachineEnvRequest request = new MachineEnvRequest(); + request.setMachineId(machineId); + request.setKey(entry.getKey()); + request.setValue(entry.getValue()); + self.addEnv(request); + } + } + + @Override + public Integer mergeEnv(Long sourceMachineId, Long targetMachineId) { + LambdaQueryWrapper sourceWrapper = new LambdaQueryWrapper() + .eq(MachineEnvDO::getMachineId, sourceMachineId); + LambdaQueryWrapper targetWrapper = new LambdaQueryWrapper() + .eq(MachineEnvDO::getMachineId, targetMachineId); + List sourceEnvList = machineEnvDAO.selectList(sourceWrapper); + List targetEnvList = machineEnvDAO.selectList(targetWrapper); + Valid.notEmpty(sourceEnvList); + Valid.notEmpty(targetEnvList); + int effect = 0; + for (MachineEnvDO sourceEnv : sourceEnvList) { + Optional targetOption = targetEnvList.stream() + .filter(t -> t.getAttrKey().equals(sourceEnv.getAttrKey())) + .findFirst(); + if (targetOption.isPresent()) { + // 更新 + MachineEnvRequest update = new MachineEnvRequest(); + update.setId(targetOption.get().getId()); + update.setValue(sourceEnv.getAttrValue()); + update.setDescription(sourceEnv.getDescription()); + effect += this.updateEnv(update); + } else { + // 插入 + MachineEnvDO insertEnv = new MachineEnvDO(); + insertEnv.setMachineId(targetMachineId); + insertEnv.setAttrKey(sourceEnv.getAttrKey()); + insertEnv.setAttrValue(sourceEnv.getAttrValue()); + insertEnv.setDescription(sourceEnv.getDescription()); + effect += machineEnvDAO.insert(insertEnv); + } + } + return effect; + } + + @Override + public DataGrid listEnv(MachineEnvRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .like(Strings.isNotBlank(request.getKey()), MachineEnvDO::getAttrKey, request.getKey()) + .like(Strings.isNotBlank(request.getValue()), MachineEnvDO::getAttrValue, request.getValue()) + .like(Strings.isNotBlank(request.getDescription()), MachineEnvDO::getDescription, request.getDescription()) + .eq(MachineEnvDO::getMachineId, request.getMachineId()) + .orderByDesc(MachineEnvDO::getUpdateTime); + return DataQuery.of(machineEnvDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(MachineEnvVO.class); + } + + @Override + public MachineEnvVO getEnvDetail(Long id) { + MachineEnvDO env = machineEnvDAO.selectById(id); + Valid.notNull(env, MessageConst.UNKNOWN_DATA); + return Converts.to(env, MachineEnvVO.class); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void syncMachineEnv(MachineEnvRequest request) { + Long id = request.getId(); + Long machineId = request.getMachineId(); + List targetMachineIdList = request.getTargetMachineIdList(); + // 获取self + MachineEnvService self = SpringHolder.getBean(MachineEnvService.class); + List envList; + if (Const.L_N_1.equals(id)) { + // 全量同步 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(MachineEnvDO::getMachineId, machineId) + .orderByAsc(MachineEnvDO::getUpdateTime); + envList = machineEnvDAO.selectList(wrapper); + } else { + // 查询数据 + MachineEnvDO env = machineEnvDAO.selectById(id); + Valid.notNull(env, MessageConst.UNKNOWN_DATA); + envList = Lists.singleton(env); + } + // 同步 + for (Long targetMachineId : targetMachineIdList) { + for (MachineEnvDO syncEnv : envList) { + MachineEnvRequest addRequest = new MachineEnvRequest(); + addRequest.setMachineId(targetMachineId); + addRequest.setKey(syncEnv.getAttrKey()); + addRequest.setValue(syncEnv.getAttrValue()); + addRequest.setDescription(syncEnv.getDescription()); + self.addEnv(addRequest); + } + } + // 设置日志参数 + EventParamsHolder.addParams(request); + EventParamsHolder.addParam(EventKeys.ENV_COUNT, envList.size()); + EventParamsHolder.addParam(EventKeys.MACHINE_COUNT, targetMachineIdList.size()); + } + + @Override + public String getMachineEnv(Long machineId, String env) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(MachineEnvDO::getMachineId, machineId) + .eq(MachineEnvDO::getAttrKey, env) + .last(Const.LIMIT_1); + return Optional.ofNullable(machineEnvDAO.selectOne(wrapper)) + .map(MachineEnvDO::getAttrValue) + .orElse(null); + } + + @Override + public MutableLinkedHashMap getMachineEnv(Long machineId) { + MutableLinkedHashMap env = Maps.newMutableLinkedMap(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(MachineEnvDO::getMachineId, machineId) + .orderByAsc(MachineEnvDO::getId); + machineEnvDAO.selectList(wrapper).forEach(e -> env.put(e.getAttrKey(), e.getAttrValue())); + return env; + } + + @Override + public MutableLinkedHashMap getFullMachineEnv(Long machineId) { + // 查询机器 + MachineInfoDO machine = machineInfoDAO.selectById(machineId); + Valid.notNull(machine, MessageConst.INVALID_MACHINE); + MutableLinkedHashMap env = Maps.newMutableLinkedMap(); + env.put(EnvConst.MACHINE_PREFIX + EnvConst.MACHINE_ID, machine.getId() + Strings.EMPTY); + env.put(EnvConst.MACHINE_PREFIX + EnvConst.MACHINE_NAME, machine.getMachineName()); + env.put(EnvConst.MACHINE_PREFIX + EnvConst.MACHINE_TAG, machine.getMachineTag()); + env.put(EnvConst.MACHINE_PREFIX + EnvConst.MACHINE_HOST, machine.getMachineHost()); + env.put(EnvConst.MACHINE_PREFIX + EnvConst.MACHINE_PORT, machine.getSshPort() + Strings.EMPTY); + env.put(EnvConst.MACHINE_PREFIX + EnvConst.MACHINE_USERNAME, machine.getUsername()); + // 查询环境变量 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(MachineEnvDO::getMachineId, machineId) + .orderByAsc(MachineEnvDO::getId); + machineEnvDAO.selectList(wrapper).forEach(e -> env.put(EnvConst.MACHINE_PREFIX + e.getAttrKey(), e.getAttrValue())); + return env; + } + + @Override + public void initEnv(Long machineId) { + List keys = MachineEnvAttr.getKeys(); + for (String key : keys) { + MachineEnvDO env = new MachineEnvDO(); + MachineEnvAttr attr = MachineEnvAttr.of(key); + env.setMachineId(machineId); + env.setDescription(attr.getDescription()); + env.setAttrKey(attr.getKey()); + switch (attr) { + case TAIL_OFFSET: + env.setAttrValue(Const.TAIL_OFFSET_LINE + Strings.EMPTY); + break; + case TAIL_CHARSET: + case SFTP_CHARSET: + env.setAttrValue(Const.UTF_8); + break; + case TAIL_DEFAULT_COMMAND: + env.setAttrValue(CommandConst.TAIL_FILE_DEFAULT); + break; + case CONNECT_TIMEOUT: + env.setAttrValue(MachineConst.CONNECT_TIMEOUT + Strings.EMPTY); + break; + case CONNECT_RETRY_TIMES: + env.setAttrValue(MachineConst.CONNECT_RETRY_TIMES + Strings.EMPTY); + break; + default: + break; + } + machineEnvDAO.insert(env); + // 插入历史记录 + historyValueService.addHistory(env.getId(), HistoryValueType.MACHINE_ENV, HistoryOperator.ADD, null, env.getAttrValue()); + } + } + + @Override + public Integer deleteEnvByMachineIdList(List machineIdList) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .in(MachineEnvDO::getMachineId, machineIdList); + return machineEnvDAO.delete(wrapper); + } + + @Override + public String getSftpCharset(Long machineId) { + String charset = this.getMachineEnv(machineId, MachineEnvAttr.SFTP_CHARSET.getKey()); + return Charsets.isSupported(charset) ? charset : Const.UTF_8; + } + + @Override + public Integer getTailOffset(Long machineId) { + String value = this.getMachineEnv(machineId, MachineEnvAttr.TAIL_OFFSET.getKey()); + int offset = Strings.isInteger(value) ? Integer.parseInt(value) : Const.TAIL_OFFSET_LINE; + return Math.max(offset, 0); + } + + @Override + public String getTailCharset(Long machineId) { + String charset = this.getMachineEnv(machineId, MachineEnvAttr.TAIL_CHARSET.getKey()); + return Charsets.isSupported(charset) ? charset : Const.UTF_8; + } + + @Override + public String getTailDefaultCommand(Long machineId) { + String command = this.getMachineEnv(machineId, MachineEnvAttr.TAIL_DEFAULT_COMMAND.getKey()); + return Strings.isBlank(command) ? CommandConst.TAIL_FILE_DEFAULT : command; + } + + @Override + public Integer getConnectTimeout(Long machineId) { + String value = this.getMachineEnv(machineId, MachineEnvAttr.CONNECT_TIMEOUT.getKey()); + int timeout = Strings.isInteger(value) ? Integer.parseInt(value) : MachineConst.CONNECT_TIMEOUT; + return Math.max(timeout, 0); + } + + @Override + public Integer getConnectRetryTimes(Long machineId) { + String value = this.getMachineEnv(machineId, MachineEnvAttr.CONNECT_RETRY_TIMES.getKey()); + int times = Strings.isInteger(value) ? Integer.parseInt(value) : MachineConst.CONNECT_RETRY_TIMES; + return Math.max(times, 0); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineGroupRelServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineGroupRelServiceImpl.java new file mode 100644 index 0000000..28d3b50 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineGroupRelServiceImpl.java @@ -0,0 +1,133 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.ops.constant.KeyConst; +import cn.orionsec.ops.dao.MachineGroupRelDAO; +import cn.orionsec.ops.entity.domain.MachineGroupRelDO; +import cn.orionsec.ops.service.api.MachineGroupRelService; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +@Service("machineGroupRelService") +public class MachineGroupRelServiceImpl extends ServiceImpl implements MachineGroupRelService { + + @Resource + private MachineGroupRelDAO machineGroupRelDAO; + + @Resource + private RedisTemplate redisTemplate; + + @Override + @Transactional(rollbackFor = Exception.class) + public void addMachineRelByGroup(Long groupId, List machineIdList) { + // 排重 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(MachineGroupRelDO::getGroupId, groupId); + machineGroupRelDAO.selectList(wrapper) + .forEach(m -> machineIdList.removeIf(m.getMachineId()::equals)); + if (machineIdList.isEmpty()) { + return; + } + // 批量插入 + List relList = machineIdList.stream() + .map(s -> { + MachineGroupRelDO rel = new MachineGroupRelDO(); + rel.setGroupId(groupId); + rel.setMachineId(s); + return rel; + }).collect(Collectors.toList()); + this.saveBatch(relList); + // 清理缓存 + this.clearGroupRelCache(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateMachineGroup(Long machineId, List groupIdList) { + // 删除 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(MachineGroupRelDO::getMachineId, machineId); + machineGroupRelDAO.delete(wrapper); + // 批量插入 + List relList = groupIdList.stream() + .map(s -> { + MachineGroupRelDO rel = new MachineGroupRelDO(); + rel.setMachineId(machineId); + rel.setGroupId(s); + return rel; + }).collect(Collectors.toList()); + this.saveBatch(relList); + // 清理缓存 + this.clearGroupRelCache(); + } + + @Override + public Integer deleteByGroupMachineId(List groupIdList, List machineIdList) { + // 删除数据库 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .in(MachineGroupRelDO::getGroupId, groupIdList) + .in(MachineGroupRelDO::getMachineId, machineIdList); + int effect = machineGroupRelDAO.delete(wrapper); + // 清理缓存 + this.clearGroupRelCache(); + return effect; + } + + @Override + public Integer deleteByMachineIdList(List machineIdList) { + // 删除数据库 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .in(MachineGroupRelDO::getMachineId, machineIdList); + int effect = machineGroupRelDAO.delete(wrapper); + // 清理缓存 + this.clearGroupRelCache(); + return effect; + } + + @Override + public Integer deleteByGroupIdList(List groupIdList) { + // 删除数据库 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .in(MachineGroupRelDO::getGroupId, groupIdList); + int effect = machineGroupRelDAO.delete(wrapper); + // 清理缓存 + this.clearGroupRelCache(); + return effect; + } + + @Override + public Map> getMachineRelByCache() { + // 查询缓存 + // String cacheData = redisTemplate.opsForValue().get(KeyConst.MACHINE_GROUP_REL_KEY); + // if (!Strings.isBlank(cacheData)) { + // return JSON.parseObject(cacheData, new TypeReference>>() { + // }); + // } + // 查询数据 + Map> groupMachines = machineGroupRelDAO.selectList(null) + .stream().collect(Collectors.groupingBy(MachineGroupRelDO::getMachineId, + Collectors.mapping(MachineGroupRelDO::getGroupId, Collectors.toList()))); + // 设置缓存 + redisTemplate.opsForValue().set(KeyConst.MACHINE_GROUP_REL_KEY, + JSON.toJSONString(groupMachines), + KeyConst.MACHINE_GROUP_REL_EXPIRE, + TimeUnit.SECONDS); + return groupMachines; + } + + @Override + public void clearGroupRelCache() { + redisTemplate.delete(KeyConst.MACHINE_GROUP_REL_KEY); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineGroupServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineGroupServiceImpl.java new file mode 100644 index 0000000..616f6fd --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineGroupServiceImpl.java @@ -0,0 +1,235 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.utils.Objects1; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.KeyConst; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.common.TreeMoveType; +import cn.orionsec.ops.dao.MachineGroupDAO; +import cn.orionsec.ops.entity.domain.MachineGroupDO; +import cn.orionsec.ops.entity.request.machine.MachineGroupRequest; +import cn.orionsec.ops.entity.vo.machine.MachineGroupTreeVO; +import cn.orionsec.ops.service.api.MachineGroupRelService; +import cn.orionsec.ops.service.api.MachineGroupService; +import cn.orionsec.ops.utils.DataQuery; +import cn.orionsec.ops.utils.Valid; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +@Service("machineGroupService") +public class MachineGroupServiceImpl implements MachineGroupService { + + @Resource + private MachineGroupDAO machineGroupDAO; + + @Resource + private MachineGroupRelService machineGroupRelService; + + @Resource + private RedisTemplate redisTemplate; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long addGroup(MachineGroupRequest request) { + Long parentId = request.getParentId(); + String name = request.getName(); + // 检查同级是否重名 + this.checkNamePresent(null, parentId, name); + // 将同级sort增大 + machineGroupDAO.incrementSort(parentId, null); + // 插入 + MachineGroupDO entity = new MachineGroupDO(); + entity.setParentId(parentId); + entity.setGroupName(name); + entity.setSort(Const.DEFAULT_TREE_SORT); + machineGroupDAO.insert(entity); + // 清除缓存 + this.clearGroupCache(false); + return entity.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer deleteGroup(List idList) { + // 删除分组 + int effect = machineGroupDAO.deleteBatchIds(idList); + // 删除分组关联机器 + effect += machineGroupRelService.deleteByGroupIdList(idList); + // 清除缓存 + this.clearGroupCache(true); + return effect; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void moveGroup(MachineGroupRequest request) { + Long id = request.getId(); + Long targetId = request.getTargetId(); + // 查询分组信息 + MachineGroupDO moveGroup = machineGroupDAO.selectById(id); + MachineGroupDO targetGroup = machineGroupDAO.selectById(targetId); + Valid.notNull(moveGroup, MessageConst.UNKNOWN_DATA); + Valid.notNull(targetGroup, MessageConst.UNKNOWN_DATA); + String moveGroupName = moveGroup.getGroupName(); + Integer targetSort = targetGroup.getSort(); + Long targetParentId = targetGroup.getParentId(); + switch (TreeMoveType.of(request.getMoveType())) { + case IN_TOP: { + // 移动到内层 上面 检查重名 + this.checkNamePresent(id, targetId, moveGroupName); + // 将所有sort往下移动 + machineGroupDAO.incrementSort(targetId, null); + // 修改parentId + MachineGroupDO update = new MachineGroupDO(); + update.setId(id); + update.setParentId(targetId); + update.setSort(Const.DEFAULT_TREE_SORT); + machineGroupDAO.updateById(update); + break; + } + case IN_BOTTOM: { + // 移动到内层 下面 检查重名 + this.checkNamePresent(id, targetId, moveGroupName); + // 获取最大sort + Integer maxSort = Objects1.def(machineGroupDAO.getMaxSort(targetId), Const.N_0); + // 修改parentId + MachineGroupDO update = new MachineGroupDO(); + update.setId(id); + update.setParentId(targetId); + update.setSort(maxSort + 1); + machineGroupDAO.updateById(update); + break; + } + case PREV: { + // 移动到节点 上面 检查重名 + this.checkNamePresent(id, targetParentId, moveGroupName); + // 将以下的sort往下移动 + machineGroupDAO.incrementSort(targetParentId, targetSort - 1); + // 修改parentId + MachineGroupDO update = new MachineGroupDO(); + update.setId(id); + update.setParentId(targetParentId); + update.setSort(targetSort); + machineGroupDAO.updateById(update); + break; + } + case NEXT: { + // 移动到节点 下面 检查重名 + this.checkNamePresent(id, targetParentId, moveGroupName); + // 将自己及以下的sort往下移动 + machineGroupDAO.incrementSort(targetParentId, targetSort); + // 修改parentId + MachineGroupDO update = new MachineGroupDO(); + update.setId(id); + update.setParentId(targetParentId); + update.setSort(targetSort + 1); + machineGroupDAO.updateById(update); + break; + } + default: + break; + } + // 清除缓存 + this.clearGroupCache(false); + } + + @Override + public Integer renameGroup(Long id, String name) { + // 查询分组信息 + MachineGroupDO group = machineGroupDAO.selectById(id); + Valid.notNull(group, MessageConst.UNKNOWN_DATA); + // 检查名称重复 + this.checkNamePresent(id, group.getParentId(), name); + // 更新 + MachineGroupDO update = new MachineGroupDO(); + update.setId(id); + update.setGroupName(name); + int effect = machineGroupDAO.updateById(update); + // 清除缓存 + this.clearGroupCache(false); + return effect; + } + + @Override + public List getRootTree() { + // 查询缓存 + // String cacheData = redisTemplate.opsForValue().get(KeyConst.MACHINE_GROUP_DATA_KEY); + // if (!Strings.isBlank(cacheData)) { + // return JSON.parseArray(cacheData, MachineGroupTreeVO.class); + // } + // 查询所有节点 + List groups = DataQuery.of(machineGroupDAO).list(MachineGroupTreeVO.class); + if (groups.isEmpty()) { + return null; + } + // 构建树 + MachineGroupTreeVO root = new MachineGroupTreeVO(); + root.setId(Const.ROOT_TREE_ID); + this.setTreeChildrenNode(root, groups); + List children = root.getChildren(); + // 设置缓存 + redisTemplate.opsForValue().set(KeyConst.MACHINE_GROUP_DATA_KEY, + JSON.toJSONString(children), + KeyConst.MACHINE_GROUP_DATA_EXPIRE, + TimeUnit.SECONDS); + return children; + } + + /** + * 获取tree子节点 + * + * @param parent parent + * @param groups groups + */ + public void setTreeChildrenNode(MachineGroupTreeVO parent, List groups) { + // 获取子节点 + Long parentId = parent.getId(); + List child = groups.stream() + .filter(g -> g.getParentId().equals(parentId)) + .sorted(Comparator.comparing(MachineGroupTreeVO::getSort)) + .collect(Collectors.toList()); + // 移除 + groups.removeIf(g -> g.getParentId().equals(parentId)); + parent.setChildren(child); + child.forEach(g -> this.setTreeChildrenNode(g, groups)); + } + + /** + * 检查是否存在 + * + * @param id id + * @param parentId parentId + * @param name name + */ + private void checkNamePresent(Long id, Long parentId, String name) { + LambdaQueryWrapper presentWrapper = new LambdaQueryWrapper() + .ne(id != null, MachineGroupDO::getId, id) + .eq(MachineGroupDO::getParentId, parentId) + .eq(MachineGroupDO::getGroupName, name); + boolean present = DataQuery.of(machineGroupDAO).wrapper(presentWrapper).present(); + Valid.isTrue(!present, MessageConst.NAME_PRESENT); + } + + /** + * 清理分组缓存 + * + * @param clearRel 是否清除关联 + */ + private void clearGroupCache(boolean clearRel) { + redisTemplate.delete(KeyConst.MACHINE_GROUP_DATA_KEY); + if (clearRel) { + machineGroupRelService.clearGroupRelCache(); + } + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineInfoServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineInfoServiceImpl.java new file mode 100644 index 0000000..5b85080 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineInfoServiceImpl.java @@ -0,0 +1,590 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.ext.process.Processes; +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.exception.AuthenticationException; +import cn.orionsec.kit.lang.exception.ConnectionRuntimeException; +import cn.orionsec.kit.lang.utils.Booleans; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.Valid; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.lang.utils.io.Streams; +import cn.orionsec.kit.lang.utils.net.IPs; +import cn.orionsec.kit.net.host.SessionHolder; +import cn.orionsec.kit.net.host.SessionStore; +import cn.orionsec.kit.net.host.ssh.command.CommandExecutor; +import cn.orionsec.kit.net.host.ssh.command.CommandExecutors; +import cn.orionsec.ops.constant.CnConst; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.history.HistoryOperator; +import cn.orionsec.ops.constant.history.HistoryValueType; +import cn.orionsec.ops.constant.machine.MachineAuthType; +import cn.orionsec.ops.constant.machine.MachineConst; +import cn.orionsec.ops.constant.machine.ProxyType; +import cn.orionsec.ops.dao.MachineEnvDAO; +import cn.orionsec.ops.dao.MachineInfoDAO; +import cn.orionsec.ops.dao.MachineProxyDAO; +import cn.orionsec.ops.dao.MachineSecretKeyDAO; +import cn.orionsec.ops.entity.domain.MachineEnvDO; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.domain.MachineProxyDO; +import cn.orionsec.ops.entity.domain.MachineSecretKeyDO; +import cn.orionsec.ops.entity.request.machine.MachineInfoRequest; +import cn.orionsec.ops.entity.vo.machine.MachineInfoVO; +import cn.orionsec.ops.service.api.*; +import cn.orionsec.ops.utils.DataQuery; +import cn.orionsec.ops.utils.EventParamsHolder; +import cn.orionsec.ops.utils.Utils; +import cn.orionsec.ops.utils.ValueMix; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.io.IOException; +import java.util.*; + +@Slf4j +@Service("machineInfoService") +public class MachineInfoServiceImpl implements MachineInfoService { + + @Resource + private MachineInfoDAO machineInfoDAO; + + @Resource + private MachineProxyDAO machineProxyDAO; + + @Resource + private MachineSecretKeyDAO machineSecretKeyDAO; + + @Resource + private MachineEnvDAO machineEnvDAO; + + @Resource + private MachineEnvService machineEnvService; + + @Resource + private MachineTerminalService machineTerminalService; + + @Resource + private ApplicationMachineService applicationMachineService; + + @Resource + private FileTailService fileTailService; + + @Resource + private SchedulerTaskMachineService schedulerTaskMachineService; + + @Resource + private MachineMonitorService machineMonitorService; + + @Resource + private MachineAlarmConfigService machineAlarmConfigService; + + @Resource + private MachineAlarmGroupServiceImpl machineAlarmGroupService; + + @Resource + private MachineGroupRelService machineGroupRelService; + + @Resource + private HistoryValueService historyValueService; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long addMachine(MachineInfoRequest request) { + // 检查proxyId + this.checkProxy(request.getProxyId()); + // 检查名称 + this.checkNamePresent(null, request.getName()); + // 检查唯一标识 + this.checkTagPresent(null, request.getTag()); + MachineInfoDO entity = Converts.to(request, MachineInfoDO.class); + // 添加机器 + entity.setMachineStatus(Const.ENABLE); + String password = request.getPassword(); + if (Strings.isNotBlank(password)) { + entity.setPassword(ValueMix.encrypt(password)); + } + machineInfoDAO.insert(entity); + Long id = entity.getId(); + // 初始化环境变量 + machineEnvService.initEnv(id); + // 设置分组 + List groupIdList = request.getGroupIdList(); + if (!Lists.isEmpty(groupIdList)) { + machineGroupRelService.updateMachineGroup(id, groupIdList); + } + // 设置日志参数 + EventParamsHolder.addParams(entity); + return id; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer updateMachine(MachineInfoRequest request) { + Long id = request.getId(); + // 检查proxyId + this.checkProxy(request.getProxyId()); + // 检查名称 + this.checkNamePresent(id, request.getName()); + // 检查唯一标识 + this.checkTagPresent(id, request.getTag()); + MachineInfoDO entity = Converts.to(request, MachineInfoDO.class); + String password = request.getPassword(); + if (Strings.isNotBlank(password)) { + entity.setPassword(ValueMix.encrypt(password)); + } + // 修改 + int effect = machineInfoDAO.updateById(entity); + // 设置分组 + List groupIdList = request.getGroupIdList(); + if (!Lists.isEmpty(groupIdList)) { + machineGroupRelService.updateMachineGroup(id, groupIdList); + } + // 设置日志参数 + EventParamsHolder.addParams(entity); + return effect; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer deleteMachine(List idList) { + // 删除机器 + int effect = machineInfoDAO.deleteBatchIds(idList); + // 删除环境变量 + effect += machineEnvService.deleteEnvByMachineIdList(idList); + // 删除终端配置 + effect += machineTerminalService.deleteTerminalByMachineIdList(idList); + // 删除应用机器 + effect += applicationMachineService.deleteAppMachineByMachineIdList(idList); + // 删除日志文件 + effect += fileTailService.deleteByMachineIdList(idList); + // 删除调度任务 + effect += schedulerTaskMachineService.deleteByMachineIdList(idList); + // 删除监控配置 + effect += machineMonitorService.deleteByMachineIdList(idList); + // 删除报警配置 + effect += machineAlarmConfigService.deleteByMachineIdList(idList); + // 删除报警配置组 + effect += machineAlarmGroupService.deleteByMachineIdList(idList); + // 删除机器分组 + effect += machineGroupRelService.deleteByMachineIdList(idList); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID_LIST, idList); + EventParamsHolder.addParam(EventKeys.COUNT, idList.size()); + return effect; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer updateStatus(List idList, Integer status) { + int effect = 0; + for (Long id : idList) { + MachineInfoDO entity = new MachineInfoDO(); + entity.setId(id); + entity.setMachineStatus(status); + entity.setUpdateTime(new Date()); + effect += machineInfoDAO.updateById(entity); + } + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID_LIST, idList); + EventParamsHolder.addParam(EventKeys.COUNT, effect); + EventParamsHolder.addParam(EventKeys.OPERATOR, Const.ENABLE.equals(status) ? CnConst.ENABLE : CnConst.DISABLE); + return effect; + } + + @Override + public DataGrid listMachine(MachineInfoRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .in(Lists.isNotEmpty(request.getIdList()), MachineInfoDO::getId, request.getIdList()) + .like(Strings.isNotBlank(request.getHost()), MachineInfoDO::getMachineHost, request.getHost()) + .like(Strings.isNotBlank(request.getName()), MachineInfoDO::getMachineName, request.getName()) + .like(Strings.isNotBlank(request.getTag()), MachineInfoDO::getMachineTag, request.getTag()) + .like(Strings.isNotBlank(request.getDescription()), MachineInfoDO::getDescription, request.getDescription()) + .like(Strings.isNotBlank(request.getUsername()), MachineInfoDO::getUsername, request.getUsername()) + .eq(Objects.nonNull(request.getStatus()), MachineInfoDO::getMachineStatus, request.getStatus()) + .eq(Objects.nonNull(request.getId()), MachineInfoDO::getId, request.getId()) + .orderByAsc(MachineInfoDO::getId); + // 查询数据 + DataGrid dataGrid = DataQuery.of(machineInfoDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(MachineInfoVO.class); + // 查询分组 + if (Booleans.isTrue(request.getQueryGroup())) { + Map> rel = machineGroupRelService.getMachineRelByCache(); + dataGrid.forEach(s -> s.setGroupIdList(rel.get(s.getId()))); + } + return dataGrid; + } + + @Override + public MachineInfoVO machineDetail(Long id) { + MachineInfoDO machine = machineInfoDAO.selectById(id); + Valid.notNull(machine, MessageConst.INVALID_MACHINE); + MachineInfoVO vo = Converts.to(machine, MachineInfoVO.class); + // 查询代理信息 + Optional.ofNullable(machine.getProxyId()) + .map(machineProxyDAO::selectById) + .ifPresent(p -> { + vo.setProxyHost(p.getProxyHost()); + vo.setProxyPort(p.getProxyPort()); + vo.setProxyType(p.getProxyType()); + }); + // 查询密钥信息 + Optional.ofNullable(machine.getKeyId()) + .filter(s -> MachineAuthType.SECRET_KEY.getType().equals(machine.getAuthType())) + .map(machineSecretKeyDAO::selectById) + .map(MachineSecretKeyDO::getKeyName) + .ifPresent(vo::setKeyName); + // 查询分组 + List groupIdList = machineGroupRelService.getMachineRelByCache().get(id); + vo.setGroupIdList(groupIdList); + return vo; + } + + @Override + public Long copyMachine(Long id) { + MachineInfoDO machine = machineInfoDAO.selectById(id); + Valid.notNull(machine, MessageConst.INVALID_MACHINE); + String sourceMachineName = machine.getMachineName(); + String sourceMachineTag = machine.getMachineTag(); + String copySuffix = Utils.getRandomSuffix(); + String targetMachineName = sourceMachineName + copySuffix; + String targetMachineTag = sourceMachineTag + copySuffix; + machine.setId(null); + machine.setCreateTime(null); + machine.setUpdateTime(null); + machine.setMachineStatus(Const.ENABLE); + machine.setMachineName(targetMachineName); + machine.setMachineTag(targetMachineTag); + machineInfoDAO.insert(machine); + Long insertId = machine.getId(); + // 复制环境变量 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(MachineEnvDO::getMachineId, id) + .orderByAsc(MachineEnvDO::getUpdateTime); + machineEnvDAO.selectList(wrapper).forEach(e -> { + // 插入环境变量 + e.setMachineId(insertId); + e.setCreateTime(null); + e.setUpdateTime(null); + machineEnvDAO.insert(e); + // 插入历史值 + historyValueService.addHistory(e.getId(), HistoryValueType.MACHINE_ENV, HistoryOperator.ADD, null, e.getAttrValue()); + }); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.SOURCE, sourceMachineName); + EventParamsHolder.addParam(EventKeys.TARGET, targetMachineName); + return insertId; + } + + @Override + public MachineInfoDO selectById(Long id) { + return machineInfoDAO.selectById(id); + } + + @Override + public void testPing(Long id) { + MachineInfoDO machine = machineInfoDAO.selectById(id); + Valid.notNull(machine, MessageConst.INVALID_MACHINE); + // 查询超时时间 + Integer connectTimeout = machineEnvService.getConnectTimeout(id); + if (!IPs.ping(machine.getMachineHost(), connectTimeout)) { + throw Exceptions.app(MessageConst.TIMEOUT_EXCEPTION_MESSAGE); + } + } + + @Override + public void testPing(String host) { + if (!IPs.ping(host, MachineConst.CONNECT_TIMEOUT)) { + throw Exceptions.app(MessageConst.TIMEOUT_EXCEPTION_MESSAGE); + } + } + + @Override + public void testConnect(Long id) { + // 查询机器 + MachineInfoDO machine = Valid.notNull(machineInfoDAO.selectById(id), MessageConst.INVALID_MACHINE); + // 测试连接 + this.testConnectMachine(machine); + } + + @Override + public void testConnect(MachineInfoRequest request) { + MachineInfoDO machine = new MachineInfoDO(); + machine.setProxyId(request.getProxyId()); + machine.setKeyId(request.getKeyId()); + machine.setMachineHost(request.getHost()); + machine.setSshPort(request.getSshPort()); + machine.setUsername(request.getUsername()); + machine.setAuthType(request.getAuthType()); + Optional.ofNullable(request.getPassword()) + .map(ValueMix::encrypt) + .ifPresent(machine::setPassword); + // 测试连接 + this.testConnectMachine(machine); + } + + /** + * 测试连接机器 + * + * @param machine machine + */ + private void testConnectMachine(MachineInfoDO machine) { + SessionStore s = null; + try { + // 查询密钥 + MachineSecretKeyDO key = Optional.ofNullable(machine.getKeyId()) + .map(machineSecretKeyDAO::selectById) + .orElse(null); + // 查询代理 + MachineProxyDO proxy = Optional.ofNullable(machine.getProxyId()) + .map(machineProxyDAO::selectById) + .orElse(null); + // 查询超时时间 + Integer timeout = Optional.ofNullable(machine.getId()) + .map(machineEnvService::getConnectTimeout) + .orElse(MachineConst.CONNECT_TIMEOUT); + s = this.connectSessionStore(machine, key, proxy, timeout); + } catch (Exception e) { + String message = e.getMessage(); + if (Strings.contains(message, Const.TIMEOUT)) { + throw Exceptions.app(MessageConst.TIMEOUT_EXCEPTION_MESSAGE); + } else if (e instanceof AuthenticationException) { + throw Exceptions.app(MessageConst.AUTH_EXCEPTION_MESSAGE); + } else { + throw Exceptions.app(MessageConst.CONNECT_ERROR); + } + } finally { + Streams.close(s); + } + } + + @Override + public SessionStore openSessionStore(Long id) { + return this.openSessionStore(Valid.notNull(machineInfoDAO.selectById(id), MessageConst.INVALID_MACHINE)); + } + + @Override + public SessionStore openSessionStore(MachineInfoDO machine) { + Valid.notNull(machine, MessageConst.INVALID_MACHINE); + // 检查状态 + if (!Const.ENABLE.equals(machine.getMachineStatus())) { + throw Exceptions.disabled(MessageConst.MACHINE_DISABLE); + } + Long id = machine.getId(); + // 查询超时间 + Integer connectTimeout = machineEnvService.getConnectTimeout(id); + // 重试次数 + Integer retryTimes = machineEnvService.getConnectRetryTimes(id); + // 查询密钥 + MachineSecretKeyDO key = Optional.ofNullable(machine.getKeyId()) + .map(machineSecretKeyDAO::selectById) + .orElse(null); + // 查询代理 + MachineProxyDO proxy = Optional.ofNullable(machine.getProxyId()) + .map(machineProxyDAO::selectById) + .orElse(null); + Exception ex = null; + String msg = MessageConst.CONNECT_ERROR; + for (int i = 0, t = retryTimes + 1; i < t; i++) { + log.info("远程机器建立连接-尝试连接远程服务器 第{}次尝试 machineId: {}, host: {}", (i + 1), id, machine.getMachineHost()); + try { + return this.connectSessionStore(machine, key, proxy, connectTimeout); + } catch (Exception e) { + ex = e; + String message = e.getMessage(); + if (Strings.contains(message, Const.TIMEOUT)) { + log.info("远程机器建立连接-连接超时"); + msg = MessageConst.TIMEOUT_EXCEPTION_MESSAGE; + ex = Exceptions.timeout(message, e); + } else if (e instanceof ConnectionRuntimeException) { + log.info("远程机器建立连接-连接失败"); + } else if (e instanceof AuthenticationException) { + msg = MessageConst.AUTH_EXCEPTION_MESSAGE; + break; + } else { + break; + } + } + } + String errorMessage = "机器 " + machine.getMachineHost() + " " + msg; + log.error(errorMessage, ex); + throw Exceptions.app(errorMessage, ex); + } + + /** + * 打开 sessionStore + * + * @param machine machine + * @param key key + * @param proxy proxy + * @param timeout timeout + * @return SessionStore + */ + private SessionStore connectSessionStore(MachineInfoDO machine, MachineSecretKeyDO key, + MachineProxyDO proxy, int timeout) { + Valid.notNull(machine, MessageConst.INVALID_MACHINE); + SessionHolder sessionHolder = new SessionHolder(); + SessionStore session; + try { + // 加载密钥 + if (MachineAuthType.SECRET_KEY.getType().equals(machine.getAuthType())) { + String keyPath = MachineKeyService.getKeyPath(key.getSecretKeyPath()); + String password = key.getPassword(); + if (Strings.isEmpty(password)) { + sessionHolder.addIdentity(keyPath); + } else { + sessionHolder.addIdentity(keyPath, ValueMix.decrypt(password)); + } + } + // 获取会话 + session = sessionHolder.getSession(machine.getMachineHost(), machine.getSshPort(), machine.getUsername()); + // 密码验证 + if (MachineAuthType.PASSWORD.getType().equals(machine.getAuthType())) { + String password = machine.getPassword(); + if (Strings.isNotBlank(password)) { + session.password(ValueMix.decrypt(password)); + } + } + // 加载代理 + if (proxy != null) { + ProxyType proxyType = ProxyType.of(proxy.getProxyType()); + String proxyPassword = proxy.getProxyPassword(); + if (!Strings.isBlank(proxyPassword)) { + proxyPassword = ValueMix.decrypt(proxyPassword); + } + if (ProxyType.HTTP.equals(proxyType)) { + session.httpProxy(proxy.getProxyHost(), proxy.getProxyPort(), proxy.getProxyUsername(), proxyPassword); + } else if (ProxyType.SOCKS4.equals(proxyType)) { + session.socks4Proxy(proxy.getProxyHost(), proxy.getProxyPort(), proxy.getProxyUsername(), proxyPassword); + } else if (ProxyType.SOCKS5.equals(proxyType)) { + session.socks5Proxy(proxy.getProxyHost(), proxy.getProxyPort(), proxy.getProxyUsername(), proxyPassword); + } + } + // 连接 + session.connect(timeout); + log.info("远程机器建立连接-成功 {}@{}:{}", machine.getUsername(), machine.getMachineHost(), machine.getSshPort()); + return session; + } catch (Exception e) { + log.error("远程机器建立连接-失败 {}@{}:{}", machine.getUsername(), machine.getMachineHost(), machine.getSshPort(), e); + throw e; + } + } + + @Override + public String getCommandResultSync(Long id, String command) { + String res; + if (id.equals(Const.HOST_MACHINE_ID)) { + // 本机 + res = this.runHostCommand(command); + } else { + // 远程 + res = this.runRemoteCommand(id, command); + } + if (res == null) { + return null; + } + if (res.endsWith(Const.LF)) { + return res.substring(0, res.length() - 1); + } + return res; + } + + @Override + public String getMachineName(Long id) { + return machineInfoDAO.selectMachineName(id); + } + + /** + * 执行本地命令 + */ + private String runHostCommand(String command) { + try { + return Processes.getOutputResultString(command); + } catch (Exception e) { + log.error("执行本机命令-失败 {} {}", command, e); + return null; + } + } + + /** + * 执行远程机器命令 + */ + private String runRemoteCommand(Long id, String command) { + SessionStore session = null; + CommandExecutor executor = null; + try { + session = this.openSessionStore(id); + executor = session.getCommandExecutor(Strings.replaceCRLF(command)); + executor.connect(); + String res = CommandExecutors.getCommandOutputResultString(executor); + log.info("执行机器命令-成功 {} {} {}", id, command, res); + return res; + } catch (Exception e) { + log.error("执行机器命令-失败 {} {} {}", id, command, e); + if (e instanceof IOException) { + throw Exceptions.ioRuntime(e); + } else if (e instanceof ConnectionRuntimeException) { + throw (ConnectionRuntimeException) e; + } else if (e instanceof AuthenticationException) { + throw (AuthenticationException) e; + } + return null; + } finally { + Streams.close(executor); + Streams.close(session); + } + } + + /** + * 检查代理 + */ + private void checkProxy(Long proxyId) { + if (proxyId == null) { + return; + } + MachineProxyDO proxy = machineProxyDAO.selectById(proxyId); + Valid.notNull(proxy, MessageConst.INVALID_PROXY); + } + + + /** + * 检查 name 是否存在 + * + * @param id id + * @param name name + */ + private void checkNamePresent(Long id, String name) { + LambdaQueryWrapper presentWrapper = new LambdaQueryWrapper() + .ne(id != null, MachineInfoDO::getId, id) + .eq(MachineInfoDO::getMachineName, name); + boolean present = DataQuery.of(machineInfoDAO).wrapper(presentWrapper).present(); + Valid.isTrue(!present, MessageConst.NAME_PRESENT); + } + + + /** + * 检查 tag 是否存在 + * + * @param id id + * @param tag tag + */ + private void checkTagPresent(Long id, String tag) { + LambdaQueryWrapper presentWrapper = new LambdaQueryWrapper() + .ne(id != null, MachineInfoDO::getId, id) + .eq(MachineInfoDO::getMachineTag, tag); + boolean present = DataQuery.of(machineInfoDAO).wrapper(presentWrapper).present(); + cn.orionsec.ops.utils.Valid.isTrue(!present, MessageConst.TAG_PRESENT); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineKeyServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineKeyServiceImpl.java new file mode 100644 index 0000000..f5ca29e --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineKeyServiceImpl.java @@ -0,0 +1,189 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.codec.Base64s; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.lang.utils.io.FileWriters; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.net.host.SessionHolder; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.dao.MachineInfoDAO; +import cn.orionsec.ops.dao.MachineSecretKeyDAO; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.domain.MachineSecretKeyDO; +import cn.orionsec.ops.entity.request.machine.MachineKeyRequest; +import cn.orionsec.ops.entity.vo.machine.MachineSecretKeyVO; +import cn.orionsec.ops.service.api.MachineKeyService; +import cn.orionsec.ops.utils.*; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.List; + +@Slf4j +@Service("machineKeyService") +public class MachineKeyServiceImpl implements MachineKeyService { + + @Resource + private MachineSecretKeyDAO machineSecretKeyDAO; + + @Resource + private MachineInfoDAO machineInfoDAO; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long addSecretKey(MachineKeyRequest request) { + MachineSecretKeyDO key = new MachineSecretKeyDO(); + key.setKeyName(request.getName()); + key.setDescription(request.getDescription()); + String file = PathBuilders.getSecretKeyPath(); + String path = MachineKeyService.getKeyPath(file); + key.setSecretKeyPath(file); + Files1.touch(path); + byte[] keyFileData = Base64s.decode(Strings.bytes(request.getFile())); + FileWriters.writeFast(path, keyFileData); + String password = request.getPassword(); + if (Strings.isEmpty(password)) { + key.setPassword(Const.EMPTY); + } else { + key.setPassword(ValueMix.encrypt(password)); + } + // 检查密钥 + this.checkLoadKey(path, password); + // 插入 + machineSecretKeyDAO.insert(key); + // 设置日志参数 + EventParamsHolder.addParams(key); + return key.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer updateSecretKey(MachineKeyRequest request) { + // 查询key + Long id = request.getId(); + MachineSecretKeyDO beforeKey = machineSecretKeyDAO.selectById(id); + // 设置修改信息 + MachineSecretKeyDO updateKey = new MachineSecretKeyDO(); + updateKey.setId(id); + updateKey.setKeyName(request.getName()); + updateKey.setDescription(request.getDescription()); + updateKey.setUpdateTime(new Date()); + String password = request.getPassword(); + String fileBase64 = request.getFile(); + // 修改文件 + final boolean updateFile = !Strings.isBlank(fileBase64); + if (updateFile) { + // 修改密钥文件 将新密钥保存到本地 + String keyFile = PathBuilders.getSecretKeyPath(); + String keyPath = MachineKeyService.getKeyPath(keyFile); + Files1.touch(keyPath); + byte[] keyFileData = Base64s.decode(Strings.bytes(fileBase64)); + FileWriters.writeFast(keyPath, keyFileData); + updateKey.setSecretKeyPath(keyFile); + } + // 修改密码 + if (Strings.isBlank(password)) { + updateKey.setPassword(Const.EMPTY); + } else { + updateKey.setPassword(ValueMix.encrypt(password)); + } + // 检查密钥 + String checkPath = updateFile ? updateKey.getSecretKeyPath() : beforeKey.getSecretKeyPath(); + this.checkLoadKey(MachineKeyService.getKeyPath(checkPath), password); + // 更新 + int effect = machineSecretKeyDAO.updateById(updateKey); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.NAME, beforeKey.getKeyName()); + EventParamsHolder.addParams(updateKey); + return effect; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer removeSecretKey(List idList) { + // 删除密钥 + int effect = machineSecretKeyDAO.deleteBatchIds(idList); + // 删除机器关联 + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper() + .set(MachineInfoDO::getKeyId, null) + .in(MachineInfoDO::getKeyId, idList); + machineInfoDAO.update(null, wrapper); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID_LIST, idList); + EventParamsHolder.addParam(EventKeys.COUNT, idList.size()); + return effect; + } + + @Override + public MachineSecretKeyDO getKeyById(Long id) { + return machineSecretKeyDAO.selectById(id); + } + + @Override + public DataGrid listKeys(MachineKeyRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .like(Strings.isNotBlank(request.getName()), MachineSecretKeyDO::getKeyName, request.getName()) + .like(Strings.isNotBlank(request.getDescription()), MachineSecretKeyDO::getDescription, request.getDescription()) + .orderByDesc(MachineSecretKeyDO::getCreateTime); + return DataQuery.of(machineSecretKeyDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(MachineSecretKeyVO.class); + } + + @Override + public MachineSecretKeyVO getKeyDetail(Long id) { + MachineSecretKeyDO key = machineSecretKeyDAO.selectById(id); + Valid.notNull(key, MessageConst.UNKNOWN_DATA); + return Converts.to(key, MachineSecretKeyVO.class); + } + + @Override + public void bindMachineKey(Long id, List machineIdList) { + // 查询数据 + MachineSecretKeyDO key = machineSecretKeyDAO.selectById(id); + Valid.notNull(key, MessageConst.UNKNOWN_DATA); + // 更新到机器表 + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper() + .set(MachineInfoDO::getKeyId, id) + .in(MachineInfoDO::getId, machineIdList); + machineInfoDAO.update(null, wrapper); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.NAME, key.getKeyName()); + EventParamsHolder.addParam(EventKeys.MACHINE_ID_LIST, machineIdList); + EventParamsHolder.addParam(EventKeys.COUNT, machineIdList.size()); + } + + /** + * 检查密钥是否合法 + * + * @param path path + * @param password 密码 + */ + private void checkLoadKey(String path, String password) { + try { + SessionHolder holder = new SessionHolder(); + if (Strings.isEmpty(password)) { + holder.addIdentity(path); + } else { + holder.addIdentity(path, password); + } + holder.removeAllIdentity(); + } catch (Exception e) { + throw Exceptions.app(MessageConst.ILLEGAL_MACHINE_SECRET_KEY, e); + } + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineMonitorEndpointServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineMonitorEndpointServiceImpl.java new file mode 100644 index 0000000..d12a166 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineMonitorEndpointServiceImpl.java @@ -0,0 +1,115 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.ParamConst; +import cn.orionsec.ops.entity.domain.MachineMonitorDO; +import cn.orionsec.ops.entity.request.machine.MachineMonitorEndpointRequest; +import cn.orionsec.ops.handler.http.HttpApiRequest; +import cn.orionsec.ops.handler.http.HttpApiRequester; +import cn.orionsec.ops.handler.http.MachineMonitorHttpApi; +import cn.orionsec.ops.handler.http.MachineMonitorHttpApiRequester; +import cn.orionsec.ops.service.api.MachineMonitorEndpointService; +import cn.orionsec.ops.service.api.MachineMonitorService; +import cn.orionsec.ops.utils.Valid; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; + +@Service("machineMonitorEndpointService") +public class MachineMonitorEndpointServiceImpl implements MachineMonitorEndpointService { + + @Resource + private MachineMonitorService machineMonitorService; + + @Override + public Integer ping(Long machineId) { + HttpWrapper wrapper = this.getRequester(machineId, MachineMonitorHttpApi.ENDPOINT_PING) + .request(Integer.class); + return Valid.api(wrapper); + } + + @Override + public JSONObject getBaseMetrics(Long machineId) { + HttpApiRequest request = this.getRequester(machineId, MachineMonitorHttpApi.METRICS_BASE) + .getRequest(); + request.queryParam(ParamConst.LIMIT, Const.N_10); + HttpWrapper wrapper = request.getHttpWrapper(JSONObject.class); + return Valid.api(wrapper); + } + + @Override + public JSONObject getSystemLoad(Long machineId) { + HttpWrapper wrapper = this.getRequester(machineId, MachineMonitorHttpApi.METRICS_SYSTEM_LOAD) + .request(JSONObject.class); + return Valid.api(wrapper); + } + + @Override + public JSONArray getTopProcesses(Long machineId, String name) { + HttpApiRequest request = this.getRequester(machineId, MachineMonitorHttpApi.METRICS_TOP_PROCESSES) + .getRequest(); + request.queryParam(ParamConst.LIMIT, Const.N_10) + .queryParam(ParamConst.NAME, name); + HttpWrapper wrapper = request.getHttpWrapper(JSONArray.class); + return Valid.api(wrapper); + } + + @Override + public JSONArray getDiskName(Long machineId) { + HttpWrapper res = this.getRequester(machineId, MachineMonitorHttpApi.METRICS_DISK_NAME) + .getRequest() + .getHttpWrapper(JSONArray.class); + return Valid.api(res); + } + + @Override + public JSONObject getCpuChart(MachineMonitorEndpointRequest request) { + return getStatisticsChart(request, MachineMonitorHttpApi.MONITOR_CPU); + } + + @Override + public JSONObject getMemoryChart(MachineMonitorEndpointRequest request) { + return getStatisticsChart(request, MachineMonitorHttpApi.MONITOR_MEMORY); + } + + @Override + public JSONObject getNetChart(MachineMonitorEndpointRequest request) { + return getStatisticsChart(request, MachineMonitorHttpApi.MONITOR_NET); + } + + @Override + public JSONObject getDiskChart(MachineMonitorEndpointRequest request) { + return getStatisticsChart(request, MachineMonitorHttpApi.MONITOR_DISK); + } + + private JSONObject getStatisticsChart(MachineMonitorEndpointRequest request, MachineMonitorHttpApi api) { + HttpWrapper res = this.getRequester(request.getMachineId(), api) + .getRequest() + .jsonBody(request) + .getHttpWrapper(JSONObject.class); + return Valid.api(res); + } + + /** + * 获取 agent api 请求 + * + * @param machineId machineId + * @param api api + * @return request + */ + private HttpApiRequester getRequester(Long machineId, MachineMonitorHttpApi api) { + MachineMonitorDO monitor = machineMonitorService.selectByMachineId(machineId); + Valid.notNull(monitor, MessageConst.CONFIG_ABSENT); + return MachineMonitorHttpApiRequester.builder() + .url(monitor.getMonitorUrl()) + .accessToken(monitor.getAccessToken()) + .api(api) + .build(); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineMonitorServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineMonitorServiceImpl.java new file mode 100644 index 0000000..b24117d --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineMonitorServiceImpl.java @@ -0,0 +1,268 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.define.wrapper.Pager; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.Threads; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.ops.constant.CnConst; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.SchedulerPools; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.monitor.MonitorConst; +import cn.orionsec.ops.constant.monitor.MonitorStatus; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.dao.MachineInfoDAO; +import cn.orionsec.ops.dao.MachineMonitorDAO; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.domain.MachineMonitorDO; +import cn.orionsec.ops.entity.dto.MachineMonitorDTO; +import cn.orionsec.ops.entity.query.MachineMonitorQuery; +import cn.orionsec.ops.entity.request.machine.MachineMonitorRequest; +import cn.orionsec.ops.entity.request.machine.MachineMonitorSyncRequest; +import cn.orionsec.ops.entity.vo.machine.MachineMonitorVO; +import cn.orionsec.ops.handler.http.MachineMonitorHttpApi; +import cn.orionsec.ops.handler.http.MachineMonitorHttpApiRequester; +import cn.orionsec.ops.handler.monitor.MonitorAgentInstallTask; +import cn.orionsec.ops.service.api.MachineAlarmConfigService; +import cn.orionsec.ops.service.api.MachineMonitorService; +import cn.orionsec.ops.utils.Currents; +import cn.orionsec.ops.utils.EventParamsHolder; +import cn.orionsec.ops.utils.Valid; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; + +@Service("machineMonitorService") +public class MachineMonitorServiceImpl implements MachineMonitorService { + + @Resource + private MachineInfoDAO machineInfoDAO; + + @Resource + private MachineMonitorDAO machineMonitorDAO; + + @Resource + private MachineAlarmConfigService machineAlarmConfigService; + + @Override + public MachineMonitorDO selectByMachineId(Long machineId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(MachineMonitorDO::getMachineId, machineId); + return machineMonitorDAO.selectOne(wrapper); + } + + @Override + public DataGrid getMonitorList(MachineMonitorRequest request) { + Pager pager = new Pager<>(request); + // 参数 + MachineMonitorQuery query = new MachineMonitorQuery(); + query.setMachineId(request.getMachineId()); + query.setMachineName(request.getMachineName()); + query.setMonitorStatus(request.getStatus()); + // 查询数量 + Integer count = machineMonitorDAO.selectMonitorCount(query); + pager.setTotal(count); + if (pager.hasMoreData()) { + // 查询数据 + List rows = machineMonitorDAO.selectMonitorList(query, pager.getSql()); + pager.setRows(Converts.toList(rows, MachineMonitorVO.class)); + } else { + pager.setRows(Lists.empty()); + } + return DataGrid.of(pager); + } + + @Override + public MachineMonitorVO getMonitorConfig(Long machineId) { + // 查询机器 + MachineInfoDO machine = machineInfoDAO.selectById(machineId); + Valid.notNull(machine, MessageConst.INVALID_MACHINE); + // 查询 + MachineMonitorDO monitor = this.selectByMachineId(machineId); + if (monitor == null) { + // 不存在则插入 + monitor = new MachineMonitorDO(); + monitor.setMachineId(machineId); + monitor.setMonitorStatus(MonitorStatus.NOT_START.getStatus()); + monitor.setMonitorUrl(Strings.format(MonitorConst.DEFAULT_URL_FORMAT, machine.getMachineHost())); + monitor.setAccessToken(MonitorConst.DEFAULT_ACCESS_TOKEN); + machineMonitorDAO.insert(monitor); + } + MachineMonitorVO vo = Converts.to(monitor, MachineMonitorVO.class); + vo.setMachineName(machine.getMachineName()); + vo.setMachineHost(machine.getMachineHost()); + return vo; + } + + @Override + public MachineMonitorVO updateMonitorConfig(MachineMonitorRequest request) { + // 查询配置 + Long id = request.getId(); + String url = request.getUrl(); + String accessToken = request.getAccessToken(); + MachineMonitorDO monitor = machineMonitorDAO.selectById(id); + Valid.notNull(monitor, MessageConst.CONFIG_ABSENT); + // 查询机器 + Long machineId = monitor.getMachineId(); + MachineInfoDO machine = machineInfoDAO.selectById(machineId); + Valid.notNull(machine, MessageConst.INVALID_MACHINE); + // 更新 + MachineMonitorDO update = new MachineMonitorDO(); + update.setId(id); + update.setMonitorUrl(url); + update.setAccessToken(accessToken); + // 同步状态 + if (monitor.getMonitorStatus().equals(MonitorStatus.NOT_START.getStatus()) || + monitor.getMonitorStatus().equals(MonitorStatus.RUNNING.getStatus())) { + // 同步并且获取插件版本 + String monitorVersion = this.syncMonitorAgent(machineId, url, accessToken); + if (monitorVersion == null) { + // 未启动 + update.setMonitorStatus(MonitorStatus.NOT_START.getStatus()); + } else { + update.setAgentVersion(monitorVersion); + update.setMonitorStatus(MonitorStatus.RUNNING.getStatus()); + } + } + machineMonitorDAO.updateById(update); + // 返回 + MachineMonitorVO returnValue = new MachineMonitorVO(); + returnValue.setStatus(update.getMonitorStatus()); + returnValue.setCurrentVersion(update.getAgentVersion()); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.NAME, machine.getMachineName()); + EventParamsHolder.addParams(update); + return returnValue; + } + + @Override + public Integer updateMonitorConfigByMachineId(Long machineId, MachineMonitorDO update) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(MachineMonitorDO::getMachineId, machineId); + return machineMonitorDAO.update(update, wrapper); + } + + @Override + public Integer deleteByMachineIdList(List machineIdList) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .in(MachineMonitorDO::getMachineId, machineIdList); + return machineMonitorDAO.delete(wrapper); + } + + @Override + public MachineMonitorVO installMonitorAgent(Long machineId, boolean upgrade) { + // 查询配置 + MachineMonitorVO config = this.getMonitorConfig(machineId); + Valid.neq(config.getStatus(), MonitorStatus.STARTING.getStatus(), MessageConst.AGENT_STATUS_IS_STARTING); + boolean reinstall = upgrade; + // 修改状态 + MachineMonitorDO update = new MachineMonitorDO(); + update.setId(config.getId()); + if (!upgrade) { + // 同步并且获取插件版本 + String version = this.syncMonitorAgent(machineId, config.getUrl(), config.getAccessToken()); + if (version == null) { + // 未获取到版本则重新安装 + reinstall = true; + } else { + // 状态改为运行中 + update.setAgentVersion(version); + update.setMonitorStatus(MonitorStatus.RUNNING.getStatus()); + } + } + if (reinstall) { + // 重新安装 + String path = SystemEnvAttr.MACHINE_MONITOR_AGENT_PATH.getValue(); + Valid.isTrue(Files1.isFile(path), Strings.format(MessageConst.AGENT_FILE_NON_EXIST, path)); + // 状态改为启动中 + update.setMonitorStatus(MonitorStatus.STARTING.getStatus()); + // 创建安装任务 + Threads.start(new MonitorAgentInstallTask(machineId, Currents.getUser()), SchedulerPools.AGENT_INSTALL_SCHEDULER); + } + // 更新状态 + machineMonitorDAO.updateById(update); + // 返回 + MachineMonitorVO returnValue = new MachineMonitorVO(); + returnValue.setStatus(update.getMonitorStatus()); + returnValue.setCurrentVersion(update.getAgentVersion()); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.OPERATOR, upgrade ? CnConst.UPGRADE : CnConst.INSTALL); + EventParamsHolder.addParam(EventKeys.NAME, config.getMachineName()); + return returnValue; + } + + @Override + public MachineMonitorVO checkMonitorStatus(Long machineId) { + MachineMonitorVO returnValue = new MachineMonitorVO(); + // 获取监控配置 + MachineMonitorVO monitor = this.getMonitorConfig(machineId); + // 启动中直接返回 + if (monitor.getStatus().equals(MonitorStatus.STARTING.getStatus())) { + returnValue.setStatus(monitor.getStatus()); + return returnValue; + } + MachineMonitorDO update = new MachineMonitorDO(); + update.setId(monitor.getId()); + // 同步并且获取插件版本 + String monitorVersion = this.syncMonitorAgent(machineId, monitor.getUrl(), monitor.getAccessToken()); + if (monitorVersion == null) { + // 未启动 + update.setMonitorStatus(MonitorStatus.NOT_START.getStatus()); + } else { + // 启动中 + update.setAgentVersion(monitorVersion); + update.setMonitorStatus(MonitorStatus.RUNNING.getStatus()); + } + // 更新状态 + machineMonitorDAO.updateById(update); + // 返回 + returnValue.setStatus(update.getMonitorStatus()); + returnValue.setCurrentVersion(update.getAgentVersion()); + return returnValue; + } + + @Override + public String getMonitorVersion(String url, String accessToken) { + try { + return MachineMonitorHttpApiRequester.builder() + .url(url) + .accessToken(accessToken) + .api(MachineMonitorHttpApi.ENDPOINT_VERSION) + .build() + .request(String.class) + .getData(); + } catch (Exception e) { + return null; + } + } + + @Override + public String syncMonitorAgent(Long machineId, String url, String accessToken) { + try { + // 设置同步请求 + MachineMonitorSyncRequest syncRequest = new MachineMonitorSyncRequest(); + syncRequest.setMachineId(machineId); + // 查询报警配置 + syncRequest.setAlarmConfig(machineAlarmConfigService.getAlarmConfig(machineId)); + // 请求 + return MachineMonitorHttpApiRequester.builder() + .url(url) + .accessToken(accessToken) + .api(MachineMonitorHttpApi.ENDPOINT_SYNC) + .build() + .getRequest() + .jsonBody(syncRequest) + .getHttpWrapper(String.class) + .getData(); + } catch (Exception e) { + return null; + } + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineProxyServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineProxyServiceImpl.java new file mode 100644 index 0000000..514aa9d --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineProxyServiceImpl.java @@ -0,0 +1,116 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.dao.MachineInfoDAO; +import cn.orionsec.ops.dao.MachineProxyDAO; +import cn.orionsec.ops.entity.domain.MachineProxyDO; +import cn.orionsec.ops.entity.request.machine.MachineProxyRequest; +import cn.orionsec.ops.entity.vo.machine.MachineProxyVO; +import cn.orionsec.ops.service.api.MachineProxyService; +import cn.orionsec.ops.utils.DataQuery; +import cn.orionsec.ops.utils.EventParamsHolder; +import cn.orionsec.ops.utils.Valid; +import cn.orionsec.ops.utils.ValueMix; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Objects; + +@Service("machineProxyService") +public class MachineProxyServiceImpl implements MachineProxyService { + + @Resource + private MachineProxyDAO machineProxyDAO; + + @Resource + private MachineInfoDAO machineInfoDAO; + + @Override + public Long addProxy(MachineProxyRequest request) { + MachineProxyDO proxy = new MachineProxyDO(); + proxy.setProxyHost(request.getHost()); + proxy.setProxyPort(request.getPort()); + proxy.setProxyType(request.getType()); + proxy.setProxyUsername(request.getUsername()); + String password = request.getPassword(); + if (!Strings.isBlank(password)) { + proxy.setProxyPassword(ValueMix.encrypt(password)); + } + proxy.setDescription(request.getDescription()); + // 插入 + machineProxyDAO.insert(proxy); + // 设置日志参数 + EventParamsHolder.addParams(proxy); + return proxy.getId(); + } + + @Override + public Integer updateProxy(MachineProxyRequest request) { + // 查询 + Long id = request.getId(); + MachineProxyDO beforeProxy = machineProxyDAO.selectById(id); + Valid.notNull(beforeProxy, MessageConst.UNKNOWN_DATA); + MachineProxyDO proxy = new MachineProxyDO(); + proxy.setId(id); + proxy.setProxyHost(request.getHost()); + proxy.setProxyPort(request.getPort()); + proxy.setProxyUsername(request.getUsername()); + proxy.setProxyType(request.getType()); + String password = request.getPassword(); + if (!Strings.isBlank(password)) { + proxy.setProxyPassword(ValueMix.encrypt(password)); + } + proxy.setDescription(request.getDescription()); + // 修改 + int effect = machineProxyDAO.updateById(proxy); + // 设置日志参数 + EventParamsHolder.addParams(proxy); + EventParamsHolder.addParam(EventKeys.HOST, beforeProxy.getProxyHost()); + return effect; + } + + @Override + public DataGrid listProxy(MachineProxyRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .like(Strings.isNotBlank(request.getHost()), MachineProxyDO::getProxyHost, request.getHost()) + .like(Strings.isNotBlank(request.getUsername()), MachineProxyDO::getProxyUsername, request.getUsername()) + .like(Strings.isNotBlank(request.getDescription()), MachineProxyDO::getDescription, request.getDescription()) + .eq(Objects.nonNull(request.getPort()), MachineProxyDO::getProxyPort, request.getPort()) + .eq(Objects.nonNull(request.getType()), MachineProxyDO::getProxyType, request.getType()) + .orderByDesc(MachineProxyDO::getCreateTime); + return DataQuery.of(machineProxyDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(MachineProxyVO.class); + } + + @Override + public MachineProxyVO getProxyDetail(Long id) { + MachineProxyDO proxy = machineProxyDAO.selectById(id); + Valid.notNull(proxy, MessageConst.UNKNOWN_DATA); + return Converts.to(proxy, MachineProxyVO.class); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer deleteProxy(List idList) { + int effect = 0; + for (Long id : idList) { + machineInfoDAO.setProxyIdWithNull(id); + effect += machineProxyDAO.deleteById(id); + } + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID_LIST, idList); + EventParamsHolder.addParam(EventKeys.COUNT, effect); + return effect; + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineTerminalServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineTerminalServiceImpl.java new file mode 100644 index 0000000..1f7c2df --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/MachineTerminalServiceImpl.java @@ -0,0 +1,213 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.id.UUIds; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.net.host.ssh.TerminalType; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.KeyConst; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.constant.terminal.TerminalConst; +import cn.orionsec.ops.dao.MachineTerminalDAO; +import cn.orionsec.ops.dao.MachineTerminalLogDAO; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.domain.MachineTerminalDO; +import cn.orionsec.ops.entity.domain.MachineTerminalLogDO; +import cn.orionsec.ops.entity.request.machine.MachineTerminalLogRequest; +import cn.orionsec.ops.entity.request.machine.MachineTerminalRequest; +import cn.orionsec.ops.entity.vo.machine.MachineTerminalLogVO; +import cn.orionsec.ops.entity.vo.machine.MachineTerminalVO; +import cn.orionsec.ops.entity.vo.machine.TerminalAccessVO; +import cn.orionsec.ops.service.api.MachineInfoService; +import cn.orionsec.ops.service.api.MachineTerminalService; +import cn.orionsec.ops.utils.Currents; +import cn.orionsec.ops.utils.DataQuery; +import cn.orionsec.ops.utils.EventParamsHolder; +import cn.orionsec.ops.utils.Valid; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Service("machineTerminalService") +public class MachineTerminalServiceImpl implements MachineTerminalService { + + @Resource + private RedisTemplate redisTemplate; + + @Resource + private MachineTerminalDAO machineTerminalDAO; + + @Resource + private MachineTerminalLogDAO machineTerminalLogDAO; + + @Resource + private MachineInfoService machineInfoService; + + @Override + public TerminalAccessVO getAccessConfig(Long machineId) { + // 获取机器信息 + MachineInfoDO machine = machineInfoService.selectById(machineId); + Valid.notNull(machine, MessageConst.INVALID_MACHINE); + if (!Const.ENABLE.equals(machine.getMachineStatus())) { + throw Exceptions.disabled(MessageConst.MACHINE_DISABLE); + } + // 设置accessToken + Long userId = Currents.getUserId(); + String token = UUIds.random32(); + // 获取终端配置 + MachineTerminalVO config = this.getMachineConfig(machineId); + // 设置数据 + TerminalAccessVO access = new TerminalAccessVO(); + access.setId(config.getId()); + access.setAccessToken(token); + access.setHost(machine.getMachineHost()); + access.setPort(machine.getSshPort()); + access.setMachineName(machine.getMachineName()); + access.setMachineId(machineId); + access.setUsername(machine.getUsername()); + access.setTerminalType(config.getTerminalType()); + access.setBackgroundColor(config.getBackgroundColor()); + access.setFontSize(config.getFontSize()); + access.setFontFamily(config.getFontFamily()); + access.setFontColor(config.getFontColor()); + access.setEnableWebLink(config.getEnableWebLink()); + // 设置缓存 + String cacheKey = Strings.format(KeyConst.TERMINAL_ACCESS_TOKEN, token); + redisTemplate.opsForValue().set(cacheKey, userId + "_" + machineId, + KeyConst.TERMINAL_ACCESS_TOKEN_EXPIRE, TimeUnit.SECONDS); + log.info("用户获取terminal uid: {} machineId: {} token: {}", userId, machineId, token); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.MACHINE_NAME, machine.getMachineName()); + return access; + } + + @Override + public MachineTerminalVO getMachineConfig(Long machineId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(MachineTerminalDO::getMachineId, machineId); + MachineTerminalDO config = machineTerminalDAO.selectOne(wrapper); + if (config == null) { + // 初始化 + MachineTerminalDO insert = new MachineTerminalDO(); + insert.setMachineId(machineId); + insert.setTerminalType(TerminalType.XTERM.getType()); + insert.setBackgroundColor(TerminalConst.BACKGROUND_COLOR); + insert.setFontColor(TerminalConst.FONT_COLOR); + insert.setFontSize(TerminalConst.FONT_SIZE); + insert.setFontFamily(TerminalConst.FONT_FAMILY); + insert.setEnableWebLink(Const.DISABLE); + machineTerminalDAO.insert(insert); + config = insert; + } + return Converts.to(config, MachineTerminalVO.class); + } + + @Override + public Integer updateSetting(MachineTerminalRequest request) { + // 查询配置 + Long id = request.getId(); + MachineTerminalDO beforeConfig = machineTerminalDAO.selectById(id); + Valid.notNull(beforeConfig, MessageConst.UNKNOWN_DATA); + // 查询机器信息 + MachineInfoDO machineInfo = machineInfoService.selectById(beforeConfig.getMachineId()); + Valid.notNull(machineInfo, MessageConst.UNKNOWN_DATA); + // 设置修改信息 + MachineTerminalDO update = new MachineTerminalDO(); + update.setId(id); + update.setTerminalType(request.getTerminalType()); + update.setFontSize(request.getFontSize()); + update.setFontFamily(request.getFontFamily()); + update.setFontColor(request.getFontColor()); + update.setBackgroundColor(request.getBackgroundColor()); + update.setUpdateTime(new Date()); + update.setEnableWebLink(request.getEnableWebLink()); + // 修改 + int effect = machineTerminalDAO.updateById(update); + // 设置日志参数 + EventParamsHolder.addParams(request); + EventParamsHolder.addParam(EventKeys.NAME, machineInfo.getMachineName()); + return effect; + } + + @Override + public Long addTerminalLog(MachineTerminalLogDO entity) { + machineTerminalLogDAO.insert(entity); + return entity.getId(); + } + + @Override + public Integer updateAccessLog(String token, MachineTerminalLogDO entity) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(MachineTerminalLogDO::getAccessToken, token); + return machineTerminalLogDAO.update(entity, wrapper); + } + + @Override + public DataGrid listAccessLog(MachineTerminalLogRequest request) { + if (!Currents.isAdministrator()) { + request.setUserId(Currents.getUserId()); + } + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .like(Strings.isNotBlank(request.getAccessToken()), MachineTerminalLogDO::getAccessToken, request.getAccessToken()) + .like(Strings.isNotBlank(request.getMachineHost()), MachineTerminalLogDO::getMachineHost, request.getMachineHost()) + .like(Strings.isNotBlank(request.getMachineName()), MachineTerminalLogDO::getMachineName, request.getMachineName()) + .like(Strings.isNotBlank(request.getUsername()), MachineTerminalLogDO::getUsername, request.getUsername()) + .like(Objects.nonNull(request.getUserId()), MachineTerminalLogDO::getUserId, request.getUserId()) + .eq(Objects.nonNull(request.getMachineId()), MachineTerminalLogDO::getMachineId, request.getMachineId()) + .eq(Objects.nonNull(request.getCloseCode()), MachineTerminalLogDO::getCloseCode, request.getCloseCode()) + .between(Objects.nonNull(request.getConnectedTimeStart()) && Objects.nonNull(request.getConnectedTimeEnd()), + MachineTerminalLogDO::getConnectedTime, request.getConnectedTimeStart(), request.getConnectedTimeEnd()) + .between(Objects.nonNull(request.getDisconnectedTimeStart()) && Objects.nonNull(request.getDisconnectedTimeEnd()), + MachineTerminalLogDO::getDisconnectedTime, request.getDisconnectedTimeStart(), request.getDisconnectedTimeEnd()) + .orderByDesc(MachineTerminalLogDO::getCreateTime); + return DataQuery.of(machineTerminalLogDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(MachineTerminalLogVO.class); + } + + @Override + public Integer deleteTerminalLog(List idList) { + // 删除 + int effect = machineTerminalLogDAO.deleteBatchIds(idList); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID_LIST, idList); + EventParamsHolder.addParam(EventKeys.COUNT, idList.size()); + return effect; + } + + @Override + public Integer deleteTerminalByMachineIdList(List machineIdList) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .in(MachineTerminalLogDO::getMachineId, machineIdList); + return machineTerminalLogDAO.delete(wrapper); + } + + @Override + public String getTerminalScreenFilePath(Long id) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(!Currents.isAdministrator(), MachineTerminalLogDO::getUserId, Currents.getUserId()) + .eq(MachineTerminalLogDO::getId, id); + return Optional.ofNullable(machineTerminalLogDAO.selectOne(wrapper)) + .map(MachineTerminalLogDO::getScreenPath) + .filter(Strings::isNotBlank) + .map(s -> Files1.getPath(SystemEnvAttr.SCREEN_PATH.getValue(), s)) + .orElse(null); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/PassportServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/PassportServiceImpl.java new file mode 100644 index 0000000..74813f3 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/PassportServiceImpl.java @@ -0,0 +1,214 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.KeyConst; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.ResultCode; +import cn.orionsec.ops.constant.common.EnableType; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.dao.UserInfoDAO; +import cn.orionsec.ops.entity.domain.UserInfoDO; +import cn.orionsec.ops.entity.dto.user.LoginBindDTO; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.entity.request.user.UserLoginRequest; +import cn.orionsec.ops.entity.request.user.UserResetRequest; +import cn.orionsec.ops.entity.vo.user.UserLoginVO; +import cn.orionsec.ops.interceptor.UserActiveInterceptor; +import cn.orionsec.ops.service.api.PassportService; +import cn.orionsec.ops.utils.*; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.concurrent.TimeUnit; + +@Service("passportService") +public class PassportServiceImpl implements PassportService { + + @Resource + private UserInfoDAO userInfoDAO; + + @Resource + private RedisTemplate redisTemplate; + + @Resource + private UserActiveInterceptor userActiveInterceptor; + + @Override + public UserLoginVO login(UserLoginRequest request) { + // 查询用户 + LambdaQueryWrapper query = new LambdaQueryWrapper() + .eq(UserInfoDO::getUsername, request.getUsername()) + .last(Const.LIMIT_1); + UserInfoDO userInfo = userInfoDAO.selectOne(query); + Valid.notNull(userInfo, MessageConst.USERNAME_PASSWORD_ERROR); + Valid.isTrue(Const.ENABLE.equals(userInfo.getLockStatus()), MessageConst.USER_LOCKED); + // 检查密码 + boolean validPassword = ValueMix.validPassword(request.getPassword(), userInfo.getSalt(), userInfo.getPassword()); + if (validPassword) { + Valid.isTrue(Const.ENABLE.equals(userInfo.getUserStatus()), MessageConst.USER_DISABLED); + } else { + // 开启登录失败锁定 + if (EnableType.of(SystemEnvAttr.LOGIN_FAILURE_LOCK.getValue()).getValue()) { + // 修改登录失败次数 + UserInfoDO updateUser = new UserInfoDO(); + updateUser.setId(userInfo.getId()); + updateUser.setFailedLoginCount(userInfo.getFailedLoginCount() + 1); + updateUser.setUpdateTime(new Date()); + if (updateUser.getFailedLoginCount() >= Integer.parseInt(SystemEnvAttr.LOGIN_FAILURE_LOCK_THRESHOLD.getValue())) { + // 锁定用户 + updateUser.setLockStatus(Const.DISABLE); + } + userInfoDAO.updateById(updateUser); + } + throw Exceptions.invalidArgument(MessageConst.USERNAME_PASSWORD_ERROR); + } + // 更新用户信息 + Long userId = userInfo.getId(); + String username = userInfo.getUsername(); + String ip = request.getIp(); + UserInfoDO updateUser = new UserInfoDO(); + updateUser.setId(userId); + updateUser.setFailedLoginCount(0); + updateUser.setUpdateTime(new Date()); + updateUser.setLastLoginTime(new Date()); + userInfoDAO.updateById(updateUser); + // 设置登录信息 + long timestamp = System.currentTimeMillis(); + String loginToken = ValueMix.createLoginToken(userId, timestamp); + UserDTO userCache = new UserDTO(); + userCache.setId(userId); + userCache.setUsername(username); + userCache.setNickname(userInfo.getNickname()); + userCache.setRoleType(userInfo.getRoleType()); + userCache.setUserStatus(userInfo.getUserStatus()); + userCache.setTimestamp(timestamp); + // 设置绑定信息 + LoginBindDTO bind = new LoginBindDTO(); + bind.setTimestamp(timestamp); + bind.setLoginIp(ip); + // 设置登录缓存 + long expire = Long.parseLong(SystemEnvAttr.LOGIN_TOKEN_EXPIRE.getValue()); + String userInfoKey = Strings.format(KeyConst.LOGIN_TOKEN_KEY, userId); + redisTemplate.opsForValue().set(userInfoKey, JSON.toJSONString(userCache), expire, TimeUnit.HOURS); + // 设置绑定缓存 + String loginBindKey = Strings.format(KeyConst.LOGIN_TOKEN_BIND_KEY, userId, timestamp); + redisTemplate.opsForValue().set(loginBindKey, JSON.toJSONString(bind), expire, TimeUnit.HOURS); + // 设置活跃时间 + userActiveInterceptor.setActiveTime(userId, timestamp); + // 返回 + UserLoginVO loginInfo = new UserLoginVO(); + loginInfo.setToken(loginToken); + loginInfo.setUserId(userId); + loginInfo.setUsername(username); + loginInfo.setNickname(userInfo.getNickname()); + loginInfo.setRoleType(userInfo.getRoleType()); + // 设置操作日志参数 + EventParamsHolder.addParam(EventKeys.INNER_USER_ID, userId); + EventParamsHolder.addParam(EventKeys.INNER_USER_NAME, username); + return loginInfo; + } + + @Override + public void logout() { + UserDTO user = Currents.getUser(); + if (user == null) { + return; + } + Long id = user.getId(); + Long timestamp = user.getCurrentBindTimestamp(); + // 删除token + redisTemplate.delete(Strings.format(KeyConst.LOGIN_TOKEN_BIND_KEY, id, timestamp)); + // 删除活跃时间 + userActiveInterceptor.deleteActiveTime(id); + } + + @Override + public void resetPassword(UserResetRequest request) { + UserDTO current = Currents.getUser(); + Long updateUserId = request.getUserId(); + final boolean isAdmin = Currents.isAdministrator(); + final boolean updateCurrent = current.getId().equals(updateUserId); + // 检查权限 + if (!updateCurrent && !isAdmin) { + throw Exceptions.httpWrapper(HttpWrapper.of(ResultCode.NO_PERMISSION)); + } + // 查询更新用户 + UserInfoDO userInfo = userInfoDAO.selectById(updateUserId); + Valid.notNull(userInfo, MessageConst.UNKNOWN_USER); + // 检查原密码是否正确 + if (updateCurrent) { + String beforePassword = Valid.notBlank(request.getBeforePassword(), MessageConst.BEFORE_PASSWORD_EMPTY); + String validBeforePassword = ValueMix.encPassword(beforePassword, userInfo.getSalt()); + Valid.isTrue(validBeforePassword.equals(userInfo.getPassword()), MessageConst.BEFORE_PASSWORD_ERROR); + } + if (updateCurrent) { + // 修改自己密码不记录 + EventParamsHolder.setSave(false); + } else { + EventParamsHolder.addParam(EventKeys.USERNAME, userInfo.getUsername()); + } + // 修改密码 + String newPassword = ValueMix.encPassword(request.getPassword(), userInfo.getSalt()); + UserInfoDO updateUser = new UserInfoDO(); + Long userId = userInfo.getId(); + updateUser.setId(userId); + updateUser.setPassword(newPassword); + updateUser.setLockStatus(Const.ENABLE); + updateUser.setFailedLoginCount(0); + updateUser.setUpdateTime(new Date()); + userInfoDAO.updateById(updateUser); + // 删除token + RedisUtils.deleteLoginToken(redisTemplate, userId); + // 删除活跃时间 + userActiveInterceptor.deleteActiveTime(userId); + } + + @Override + public UserDTO getUserByToken(String token, String checkIp) { + if (Strings.isBlank(token)) { + return null; + } + // 解析 token 信息 + Long[] info = ValueMix.getLoginTokenInfo(token); + if (info == null) { + return null; + } + Long userId = info[0]; + Long timestamp = info[1]; + // 获取用户绑定信息 + String bindCache = redisTemplate.opsForValue().get(Strings.format(KeyConst.LOGIN_TOKEN_BIND_KEY, userId, timestamp)); + if (Strings.isBlank(bindCache)) { + return null; + } + // 获取用户登录信息 + String userCache = redisTemplate.opsForValue().get(Strings.format(KeyConst.LOGIN_TOKEN_KEY, userId)); + if (Strings.isBlank(userCache)) { + return null; + } + LoginBindDTO bind = JSON.parseObject(bindCache, LoginBindDTO.class); + UserDTO user = JSON.parseObject(userCache, UserDTO.class); + // 检查 ip 是否为登录时的ip + if (checkIp != null && !checkIp.equals(bind.getLoginIp())) { + return null; + } + // 检查多端登录 + if (!EnableType.of(SystemEnvAttr.ALLOW_MULTIPLE_LOGIN.getValue()).getValue()) { + // 不是登录的时间戳则证明后续有人登录 + if (!timestamp.equals(user.getTimestamp())) { + return null; + } + } + user.setCurrentBindTimestamp(bind.getTimestamp()); + return user; + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SchedulerTaskMachineRecordServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SchedulerTaskMachineRecordServiceImpl.java new file mode 100644 index 0000000..b955198 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SchedulerTaskMachineRecordServiceImpl.java @@ -0,0 +1,46 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.dao.SchedulerTaskMachineRecordDAO; +import cn.orionsec.ops.entity.domain.SchedulerTaskMachineRecordDO; +import cn.orionsec.ops.service.api.SchedulerTaskMachineRecordService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Optional; + +@Service("schedulerTaskMachineRecordService") +public class SchedulerTaskMachineRecordServiceImpl implements SchedulerTaskMachineRecordService { + + @Resource + private SchedulerTaskMachineRecordDAO schedulerTaskMachineRecordDAO; + + @Override + public Integer deleteByTaskId(Long taskId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(SchedulerTaskMachineRecordDO::getTaskId, taskId); + return schedulerTaskMachineRecordDAO.delete(wrapper); + } + + @Override + public List selectByRecordId(Long recordId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(SchedulerTaskMachineRecordDO::getRecordId, recordId); + return schedulerTaskMachineRecordDAO.selectList(wrapper); + } + + @Override + public String getTaskMachineLogPath(Long id) { + return Optional.ofNullable(schedulerTaskMachineRecordDAO.selectById(id)) + .map(SchedulerTaskMachineRecordDO::getLogPath) + .filter(Strings::isNotBlank) + .map(s -> Files1.getPath(SystemEnvAttr.LOG_PATH.getValue(), s)) + .orElse(null); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SchedulerTaskMachineServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SchedulerTaskMachineServiceImpl.java new file mode 100644 index 0000000..b46cfde --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SchedulerTaskMachineServiceImpl.java @@ -0,0 +1,40 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.ops.dao.SchedulerTaskMachineDAO; +import cn.orionsec.ops.entity.domain.SchedulerTaskMachineDO; +import cn.orionsec.ops.service.api.SchedulerTaskMachineService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; + +@Service("schedulerTaskMachineService") +public class SchedulerTaskMachineServiceImpl implements SchedulerTaskMachineService { + + @Resource + private SchedulerTaskMachineDAO schedulerTaskMachineDAO; + + @Override + public List selectByTaskId(Long taskId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(SchedulerTaskMachineDO::getTaskId, taskId); + return schedulerTaskMachineDAO.selectList(wrapper); + } + + @Override + public Integer deleteByTaskId(Long taskId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(SchedulerTaskMachineDO::getTaskId, taskId); + return schedulerTaskMachineDAO.delete(wrapper); + } + + @Override + public Integer deleteByMachineIdList(List machineIdList) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .in(SchedulerTaskMachineDO::getMachineId, machineIdList); + return schedulerTaskMachineDAO.delete(wrapper); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SchedulerTaskRecordServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SchedulerTaskRecordServiceImpl.java new file mode 100644 index 0000000..c7070b4 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SchedulerTaskRecordServiceImpl.java @@ -0,0 +1,293 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.collect.MutableLinkedHashMap; +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.env.EnvConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.scheduler.SchedulerTaskMachineStatus; +import cn.orionsec.ops.constant.scheduler.SchedulerTaskStatus; +import cn.orionsec.ops.dao.SchedulerTaskDAO; +import cn.orionsec.ops.dao.SchedulerTaskMachineRecordDAO; +import cn.orionsec.ops.dao.SchedulerTaskRecordDAO; +import cn.orionsec.ops.entity.domain.*; +import cn.orionsec.ops.entity.request.scheduler.SchedulerTaskRecordRequest; +import cn.orionsec.ops.entity.vo.scheduler.SchedulerTaskMachineRecordStatusVO; +import cn.orionsec.ops.entity.vo.scheduler.SchedulerTaskMachineRecordVO; +import cn.orionsec.ops.entity.vo.scheduler.SchedulerTaskRecordStatusVO; +import cn.orionsec.ops.entity.vo.scheduler.SchedulerTaskRecordVO; +import cn.orionsec.ops.handler.scheduler.ITaskProcessor; +import cn.orionsec.ops.handler.scheduler.TaskSessionHolder; +import cn.orionsec.ops.service.api.*; +import cn.orionsec.ops.utils.DataQuery; +import cn.orionsec.ops.utils.EventParamsHolder; +import cn.orionsec.ops.utils.PathBuilders; +import cn.orionsec.ops.utils.Valid; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +@Service("schedulerTaskRecordService") +public class SchedulerTaskRecordServiceImpl implements SchedulerTaskRecordService { + + @Resource + private SchedulerTaskDAO schedulerTaskDAO; + + @Resource + private SchedulerTaskMachineService schedulerTaskMachineService; + + @Resource + private SchedulerTaskRecordDAO schedulerTaskRecordDAO; + + @Resource + private SchedulerTaskMachineRecordDAO schedulerTaskMachineRecordDAO; + + @Resource + private SchedulerTaskMachineRecordService schedulerTaskMachineRecordService; + + @Resource + private MachineInfoService machineInfoService; + + @Resource + private MachineEnvService machineEnvService; + + @Resource + private SystemEnvService systemEnvService; + + @Resource + private TaskSessionHolder taskSessionHolder; + + @Override + public Integer deleteByTaskId(Long taskId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(SchedulerTaskRecordDO::getTaskId, taskId); + return schedulerTaskRecordDAO.delete(wrapper); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createTaskRecord(Long taskId) { + // 查询任务 + SchedulerTaskDO task = schedulerTaskDAO.selectById(taskId); + Valid.notNull(task); + List machines = schedulerTaskMachineService.selectByTaskId(taskId); + boolean emptyMachine = machines.isEmpty(); + Date now = new Date(); + // 创建明细 + SchedulerTaskRecordDO record = new SchedulerTaskRecordDO(); + record.setTaskId(taskId); + record.setTaskName(task.getTaskName()); + record.setTaskStatus(emptyMachine ? SchedulerTaskStatus.SUCCESS.getStatus() : SchedulerTaskStatus.WAIT.getStatus()); + record.setCreateTime(now); + schedulerTaskRecordDAO.insert(record); + Long recordId = record.getId(); + // 创建机器明细 + if (!emptyMachine) { + String command = task.getTaskCommand(); + final boolean containsEnv = command.contains(EnvConst.SYMBOL); + if (containsEnv) { + // 获取系统变量 + Map systemEnv = systemEnvService.getFullSystemEnv(); + command = Strings.format(command, EnvConst.SYMBOL, systemEnv); + } + for (SchedulerTaskMachineDO taskMachine : machines) { + Long machineId = taskMachine.getMachineId(); + + // 查询机器信息 + MachineInfoDO machine = machineInfoService.selectById(machineId); + if (machine == null) { + continue; + } + // 设置机器明细 + SchedulerTaskMachineRecordDO machineRecord = new SchedulerTaskMachineRecordDO(); + machineRecord.setTaskId(taskId); + machineRecord.setRecordId(recordId); + machineRecord.setTaskMachineId(taskMachine.getId()); + machineRecord.setMachineId(machineId); + machineRecord.setMachineName(machine.getMachineName()); + machineRecord.setMachineHost(machine.getMachineHost()); + machineRecord.setMachineTag(machine.getMachineTag()); + if (containsEnv) { + // 查询机器变量 + MutableLinkedHashMap machineEnv = machineEnvService.getFullMachineEnv(machineId); + machineRecord.setExecCommand(Strings.format(command, EnvConst.SYMBOL, machineEnv)); + } else { + machineRecord.setExecCommand(command); + } + machineRecord.setExecStatus(SchedulerTaskMachineStatus.WAIT.getStatus()); + machineRecord.setLogPath(PathBuilders.getSchedulerTaskLogPath(taskId, recordId, machineId)); + schedulerTaskMachineRecordDAO.insert(machineRecord); + } + } + // 更新任务状态 + SchedulerTaskDO updateTask = new SchedulerTaskDO(); + updateTask.setId(taskId); + updateTask.setLatelyStatus(record.getTaskStatus()); + updateTask.setLatelyScheduleTime(now); + updateTask.setUpdateTime(now); + schedulerTaskDAO.updateById(updateTask); + return recordId; + } + + @Override + public DataGrid listTaskRecord(SchedulerTaskRecordRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(Objects.nonNull(request.getTaskId()), SchedulerTaskRecordDO::getTaskId, request.getTaskId()) + .eq(Objects.nonNull(request.getStatus()), SchedulerTaskRecordDO::getTaskStatus, request.getStatus()) + .like(Strings.isNotBlank(request.getTaskName()), SchedulerTaskRecordDO::getTaskName, request.getTaskName()) + .orderByDesc(SchedulerTaskRecordDO::getId); + // 查询列表 + return DataQuery.of(schedulerTaskRecordDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(SchedulerTaskRecordVO.class); + } + + @Override + public SchedulerTaskRecordVO getDetailById(Long id) { + SchedulerTaskRecordDO record = schedulerTaskRecordDAO.selectById(id); + Valid.notNull(record, MessageConst.UNKNOWN_DATA); + SchedulerTaskRecordVO taskRecord = Converts.to(record, SchedulerTaskRecordVO.class); + // 查询机器 + List machines = schedulerTaskMachineRecordService.selectByRecordId(id); + List machineRecords = Converts.toList(machines, SchedulerTaskMachineRecordVO.class); + taskRecord.setMachines(machineRecords); + return taskRecord; + } + + @Override + public List listMachinesRecord(Long recordId) { + List machines = schedulerTaskMachineRecordService.selectByRecordId(recordId); + return Converts.toList(machines, SchedulerTaskMachineRecordVO.class); + } + + @Override + public List listRecordStatus(List idList, List machineRecordIdList) { + // 查询任务状态 + List recordStatus = schedulerTaskRecordDAO.selectTaskStatusByIdList(idList); + List recordStatusList = Converts.toList(recordStatus, SchedulerTaskRecordStatusVO.class); + if (!Lists.isEmpty(machineRecordIdList)) { + // 查询机器状态 + List machineRecords = schedulerTaskMachineRecordDAO.selectStatusByIdList(machineRecordIdList); + Map> machineStatusMap = machineRecords.stream() + .map(s -> Converts.to(s, SchedulerTaskMachineRecordStatusVO.class)) + .collect(Collectors.groupingBy(SchedulerTaskMachineRecordStatusVO::getRecordId)); + // 设置机器状态 + for (SchedulerTaskRecordStatusVO record : recordStatusList) { + record.setMachines(machineStatusMap.get(record.getId())); + } + } + return recordStatusList; + } + + @Override + public List listMachineRecordStatus(List idList) { + List status = schedulerTaskMachineRecordDAO.selectStatusByIdList(idList); + return Converts.toList(status, SchedulerTaskMachineRecordStatusVO.class); + } + + @Override + public Integer deleteTaskRecord(List idList) { + // 获取数据 + List taskList = schedulerTaskRecordDAO.selectBatchIds(idList); + Valid.notEmpty(taskList, MessageConst.UNKNOWN_DATA); + boolean canDelete = taskList.stream() + .map(SchedulerTaskRecordDO::getTaskStatus) + .noneMatch(s -> SchedulerTaskStatus.WAIT.getStatus().equals(s) + || SchedulerTaskStatus.RUNNABLE.getStatus().equals(s)); + Valid.isTrue(canDelete, MessageConst.ILLEGAL_STATUS); + // 删除主表 + int effect = schedulerTaskRecordDAO.deleteBatchIds(idList); + // 删除机器执行明细 + effect += schedulerTaskMachineRecordDAO.deleteByRecordIdList(idList); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID_LIST, idList); + EventParamsHolder.addParam(EventKeys.COUNT, idList.size()); + return effect; + } + + @Override + public void terminateAll(Long id) { + // 查询数据 + SchedulerTaskRecordDO record = schedulerTaskRecordDAO.selectById(id); + Valid.notNull(record, MessageConst.TASK_ABSENT); + // 检查状态 + Valid.isTrue(SchedulerTaskStatus.RUNNABLE.getStatus().equals(record.getTaskStatus()), MessageConst.ILLEGAL_STATUS); + // 停止 + ITaskProcessor session = taskSessionHolder.getSession(id); + Valid.notNull(session, MessageConst.SESSION_PRESENT); + session.terminateAll(); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.NAME, record.getTaskName()); + } + + @Override + public void terminateMachine(Long machineRecordId) { + this.skipOrTerminateTaskMachine(machineRecordId, true); + } + + @Override + public void skipMachine(Long machineRecordId) { + this.skipOrTerminateTaskMachine(machineRecordId, false); + } + + @Override + public void writeMachine(Long machineRecordId, String command) { + // 查询明细 + SchedulerTaskMachineRecordDO machine = schedulerTaskMachineRecordDAO.selectById(machineRecordId); + Valid.notNull(machine, MessageConst.TASK_ABSENT); + Valid.isTrue(SchedulerTaskMachineStatus.RUNNABLE.getStatus().equals(machine.getExecStatus()), MessageConst.ILLEGAL_STATUS); + // 获取会话 + ITaskProcessor session = taskSessionHolder.getSession(machine.getRecordId()); + Valid.notNull(session, MessageConst.SESSION_PRESENT); + session.writeMachine(machineRecordId, command); + } + + /** + * 跳过或终止任务 + * + * @param machineRecordId machineRecordId + * @param terminate 终止/跳过 + */ + private void skipOrTerminateTaskMachine(Long machineRecordId, boolean terminate) { + // 查询数据 + SchedulerTaskMachineRecordDO machine = schedulerTaskMachineRecordDAO.selectById(machineRecordId); + Valid.notNull(machine, MessageConst.TASK_ABSENT); + Long id = machine.getRecordId(); + SchedulerTaskRecordDO record = schedulerTaskRecordDAO.selectById(id); + Valid.notNull(record, MessageConst.TASK_ABSENT); + Valid.isTrue(SchedulerTaskStatus.RUNNABLE.getStatus().equals(record.getTaskStatus()), MessageConst.ILLEGAL_STATUS); + // 执行 + if (terminate) { + Valid.isTrue(SchedulerTaskMachineStatus.RUNNABLE.getStatus().equals(machine.getExecStatus()), MessageConst.ILLEGAL_STATUS); + } else { + Valid.isTrue(SchedulerTaskMachineStatus.WAIT.getStatus().equals(machine.getExecStatus()), MessageConst.ILLEGAL_STATUS); + } + // 停止 + ITaskProcessor session = taskSessionHolder.getSession(id); + Valid.notNull(session, MessageConst.SESSION_PRESENT); + if (terminate) { + session.terminateMachine(machineRecordId); + } else { + session.skipMachine(machineRecordId); + } + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.MACHINE_ID, machineRecordId); + EventParamsHolder.addParam(EventKeys.NAME, record.getTaskName()); + EventParamsHolder.addParam(EventKeys.MACHINE_NAME, machine.getMachineName()); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SchedulerTaskServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SchedulerTaskServiceImpl.java new file mode 100644 index 0000000..99dbd08 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SchedulerTaskServiceImpl.java @@ -0,0 +1,234 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.ops.constant.CnConst; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.scheduler.SchedulerTaskStatus; +import cn.orionsec.ops.dao.SchedulerTaskDAO; +import cn.orionsec.ops.dao.SchedulerTaskMachineDAO; +import cn.orionsec.ops.entity.domain.SchedulerTaskDO; +import cn.orionsec.ops.entity.domain.SchedulerTaskMachineDO; +import cn.orionsec.ops.entity.request.scheduler.SchedulerTaskRequest; +import cn.orionsec.ops.entity.vo.scheduler.SchedulerTaskVO; +import cn.orionsec.ops.service.api.SchedulerTaskMachineRecordService; +import cn.orionsec.ops.service.api.SchedulerTaskMachineService; +import cn.orionsec.ops.service.api.SchedulerTaskRecordService; +import cn.orionsec.ops.service.api.SchedulerTaskService; +import cn.orionsec.ops.task.TaskRegister; +import cn.orionsec.ops.task.TaskType; +import cn.orionsec.ops.task.impl.SchedulerTaskImpl; +import cn.orionsec.ops.utils.DataQuery; +import cn.orionsec.ops.utils.EventParamsHolder; +import cn.orionsec.ops.utils.Valid; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.*; +import java.util.stream.Collectors; + +@Service("schedulerTaskService") +public class SchedulerTaskServiceImpl implements SchedulerTaskService { + + @Resource + private SchedulerTaskDAO schedulerTaskDAO; + + @Resource + private SchedulerTaskMachineDAO schedulerTaskMachineDAO; + + @Resource + private SchedulerTaskMachineService schedulerTaskMachineService; + + @Resource + private SchedulerTaskRecordService schedulerTaskRecordService; + + @Resource + private SchedulerTaskMachineRecordService schedulerTaskMachineRecordService; + + @Resource + private TaskRegister taskRegister; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long addTask(SchedulerTaskRequest request) { + String name = request.getName(); + this.checkNamePresent(null, name); + // 设置任务表 + SchedulerTaskDO task = new SchedulerTaskDO(); + task.setTaskName(name); + task.setDescription(request.getDescription()); + task.setTaskCommand(request.getCommand()); + task.setExpression(request.getExpression().trim()); + task.setEnableStatus(Const.DISABLE); + task.setLatelyStatus(SchedulerTaskStatus.WAIT.getStatus()); + task.setSerializeType(request.getSerializeType()); + task.setExceptionHandler(request.getExceptionHandler()); + schedulerTaskDAO.insert(task); + Long taskId = task.getId(); + // 设置机器表 + request.getMachineIdList().stream().map(s -> { + SchedulerTaskMachineDO m = new SchedulerTaskMachineDO(); + m.setTaskId(taskId); + m.setMachineId(s); + return m; + }).forEach(schedulerTaskMachineDAO::insert); + // 设置日志参数 + EventParamsHolder.addParams(task); + return taskId; + } + + @Override + public Integer updateTask(SchedulerTaskRequest request) { + Long id = request.getId(); + String name = request.getName(); + this.checkNamePresent(id, name); + // 查询原始数据 + SchedulerTaskDO task = schedulerTaskDAO.selectById(id); + Valid.notNull(task, MessageConst.UNKNOWN_DATA); + // 更新 + SchedulerTaskDO update = new SchedulerTaskDO(); + update.setId(id); + update.setTaskName(name); + update.setExpression(request.getExpression().trim()); + update.setTaskCommand(request.getCommand()); + update.setDescription(request.getDescription()); + update.setEnableStatus(request.getEnableStatus()); + update.setEnableStatus(Const.DISABLE); + update.setLatelyStatus(SchedulerTaskStatus.WAIT.getStatus()); + update.setSerializeType(request.getSerializeType()); + update.setExceptionHandler(request.getExceptionHandler()); + update.setUpdateTime(new Date()); + int effect = schedulerTaskDAO.updateById(update); + // 查询机器信息 + List machines = schedulerTaskMachineService.selectByTaskId(id); + Map beforeMachineMap = machines.stream().collect(Collectors.toMap( + SchedulerTaskMachineDO::getMachineId, SchedulerTaskMachineDO::getId, (s1, s2) -> s1)); + List machineIdList = request.getMachineIdList(); + // 添加增量的机器 + machineIdList.stream() + .filter(s -> !beforeMachineMap.containsKey(s)) + .map(s -> { + SchedulerTaskMachineDO m = new SchedulerTaskMachineDO(); + m.setTaskId(id); + m.setMachineId(s); + return m; + }).forEach(schedulerTaskMachineDAO::insert); + // 包含则移除 + machineIdList.stream().filter(beforeMachineMap::containsKey).forEach(beforeMachineMap::remove); + // 移除不存在的机器 + Collection values = beforeMachineMap.values(); + if (!values.isEmpty()) { + effect += schedulerTaskMachineDAO.deleteBatchIds(values); + } + // 停止任务 + taskRegister.cancel(TaskType.SCHEDULER_TASK, id); + // 设置日志参数 + EventParamsHolder.addParams(update); + EventParamsHolder.addParam(EventKeys.NAME, task.getTaskName()); + EventParamsHolder.addParam(EventKeys.MACHINE_ID_LIST, request.getMachineIdList()); + return effect; + } + + @Override + public SchedulerTaskVO getTaskDetail(Long id) { + SchedulerTaskDO task = schedulerTaskDAO.selectById(id); + Valid.notNull(task, MessageConst.UNKNOWN_DATA); + SchedulerTaskVO vo = Converts.to(task, SchedulerTaskVO.class); + // 查询机器 + List machines = schedulerTaskMachineService.selectByTaskId(id); + List machineIdList = machines.stream() + .map(SchedulerTaskMachineDO::getMachineId) + .collect(Collectors.toList()); + vo.setMachineIdList(machineIdList); + return vo; + } + + @Override + public DataGrid getTaskList(SchedulerTaskRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .like(Strings.isNotBlank(request.getName()), SchedulerTaskDO::getTaskName, request.getName()) + .like(Objects.nonNull(request.getDescription()), SchedulerTaskDO::getDescription, request.getDescription()) + .eq(Objects.nonNull(request.getEnableStatus()), SchedulerTaskDO::getEnableStatus, request.getEnableStatus()) + .eq(Objects.nonNull(request.getLatelyStatus()), SchedulerTaskDO::getLatelyStatus, request.getLatelyStatus()); + return DataQuery.of(schedulerTaskDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(SchedulerTaskVO.class); + } + + @Override + public Integer updateTaskStatus(Long id, Integer status) { + SchedulerTaskDO task = schedulerTaskDAO.selectById(id); + Valid.notNull(task, MessageConst.UNKNOWN_DATA); + SchedulerTaskDO update = new SchedulerTaskDO(); + update.setId(id); + update.setEnableStatus(status); + update.setLatelyStatus(null); + int effect = schedulerTaskDAO.updateById(update); + boolean enable = Const.ENABLE.equals(status); + // 停止任务 + taskRegister.cancel(TaskType.SCHEDULER_TASK, id); + if (enable) { + // 启动 + taskRegister.submit(TaskType.SCHEDULER_TASK, task.getExpression(), id); + } + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.OPERATOR, enable ? CnConst.ENABLE : CnConst.DISABLE); + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.NAME, task.getTaskName()); + return effect; + } + + @Override + public Integer deleteTask(Long id) { + SchedulerTaskDO task = schedulerTaskDAO.selectById(id); + Valid.notNull(task, MessageConst.UNKNOWN_DATA); + // 停止任务 + taskRegister.cancel(TaskType.SCHEDULER_TASK, id); + // 删除任务 + int effect = schedulerTaskDAO.deleteById(id); + // 删除任务机器 + effect += schedulerTaskMachineService.deleteByTaskId(id); + // 删除任务明细 + // effect += schedulerTaskRecordService.deleteByTaskId(id); + // 删除任务明细 + // effect += schedulerTaskMachineRecordService.deleteByTaskId(id); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.NAME, task.getTaskName()); + return effect; + } + + @Override + public void manualTriggerTask(Long id) { + SchedulerTaskDO task = schedulerTaskDAO.selectById(id); + Valid.notNull(task, MessageConst.UNKNOWN_DATA); + Valid.isTrue(Const.ENABLE.equals(task.getEnableStatus()), MessageConst.TASK_NOT_ENABLED); + // 手动执行 + new SchedulerTaskImpl(id).run(); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.NAME, task.getTaskName()); + } + + /** + * 检查是否存在 + * + * @param id id + * @param name name + */ + private void checkNamePresent(Long id, String name) { + LambdaQueryWrapper presentWrapper = new LambdaQueryWrapper() + .ne(id != null, SchedulerTaskDO::getId, id) + .eq(SchedulerTaskDO::getTaskName, name); + boolean present = DataQuery.of(schedulerTaskDAO).wrapper(presentWrapper).present(); + Valid.isTrue(!present, MessageConst.NAME_PRESENT); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SftpServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SftpServiceImpl.java new file mode 100644 index 0000000..57e9074 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SftpServiceImpl.java @@ -0,0 +1,792 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.io.IgnoreOutputStream; +import cn.orionsec.kit.lang.id.ObjectIds; +import cn.orionsec.kit.lang.id.UUIds; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.net.host.SessionStore; +import cn.orionsec.kit.net.host.sftp.SftpExecutor; +import cn.orionsec.kit.net.host.sftp.SftpFile; +import cn.orionsec.kit.net.host.ssh.command.CommandExecutor; +import cn.orionsec.kit.net.host.ssh.command.CommandExecutors; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.KeyConst; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.sftp.SftpPackageType; +import cn.orionsec.ops.constant.sftp.SftpTransferStatus; +import cn.orionsec.ops.constant.sftp.SftpTransferType; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.dao.FileTransferLogDAO; +import cn.orionsec.ops.entity.domain.FileTransferLogDO; +import cn.orionsec.ops.entity.domain.MachineInfoDO; +import cn.orionsec.ops.entity.dto.sftp.FileTransferNotifyProgressDTO; +import cn.orionsec.ops.entity.dto.sftp.SftpSessionTokenDTO; +import cn.orionsec.ops.entity.dto.sftp.SftpUploadInfoDTO; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.entity.request.sftp.*; +import cn.orionsec.ops.entity.vo.sftp.FileDetailVO; +import cn.orionsec.ops.entity.vo.sftp.FileListVO; +import cn.orionsec.ops.entity.vo.sftp.FileOpenVO; +import cn.orionsec.ops.entity.vo.sftp.FileTransferLogVO; +import cn.orionsec.ops.handler.sftp.IFileTransferProcessor; +import cn.orionsec.ops.handler.sftp.SftpBasicExecutorHolder; +import cn.orionsec.ops.handler.sftp.TransferProcessorManager; +import cn.orionsec.ops.handler.sftp.hint.FileTransferHint; +import cn.orionsec.ops.service.api.MachineInfoService; +import cn.orionsec.ops.service.api.SftpService; +import cn.orionsec.ops.utils.*; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service("sftpService") +public class SftpServiceImpl implements SftpService { + + @Resource + private MachineInfoService machineInfoService; + + @Resource + private TransferProcessorManager transferProcessorManager; + + @Resource + private SftpBasicExecutorHolder sftpBasicExecutorHolder; + + @Resource + private FileTransferLogDAO fileTransferLogDAO; + + @Resource + private RedisTemplate redisTemplate; + + @Override + public FileOpenVO open(Long machineId) { + // 获取当前用户 + Long userId = Currents.getUserId(); + // 生成token + String sessionToken = this.generatorSessionToken(userId, machineId); + SftpExecutor executor = sftpBasicExecutorHolder.getBasicExecutor(sessionToken); + // 查询列表 + String path = executor.getHome(); + FileListVO list = this.list(path, false, executor); + // 返回数据 + FileOpenVO openVO = new FileOpenVO(); + openVO.setSessionToken(sessionToken); + openVO.setHome(path); + openVO.setCharset(executor.getCharset()); + openVO.setPath(list.getPath()); + openVO.setFiles(list.getFiles()); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.MACHINE_NAME, machineInfoService.getMachineName(machineId)); + return openVO; + } + + @Override + public FileListVO list(FileListRequest request) { + SftpExecutor executor = sftpBasicExecutorHolder.getBasicExecutor(request.getSessionToken()); + return this.list(request.getPath(), request.getAll(), executor); + } + + private FileListVO list(String path, Boolean all, SftpExecutor executor) { + synchronized (executor) { + List fileList; + if (all == null || !all) { + // 不查询隐藏文件 + fileList = executor.listFilesFilter(path, f -> !f.getName().startsWith("."), false, true); + } else { + // 查询隐藏文件 + fileList = executor.list(path); + } + // 返回 + FileListVO fileListVO = new FileListVO(); + List files = Converts.toList(fileList, FileDetailVO.class); + fileListVO.setPath(path); + fileListVO.setFiles(files); + return fileListVO; + } + } + + @Override + public FileListVO listDir(FileListRequest request) { + SftpExecutor executor = sftpBasicExecutorHolder.getBasicExecutor(request.getSessionToken()); + synchronized (executor) { + String path = request.getPath(); + List fileList = executor.listDirs(path); + // 返回 + FileListVO fileListVO = new FileListVO(); + List files = Converts.toList(fileList, FileDetailVO.class); + fileListVO.setPath(path); + fileListVO.setFiles(files); + return fileListVO; + } + } + + @Override + public String mkdir(FileMkdirRequest request) { + String path = Files1.getPath(request.getPath()); + SftpExecutor executor = sftpBasicExecutorHolder.getBasicExecutor(request.getSessionToken()); + synchronized (executor) { + executor.makeDirectories(path); + } + // 设置日志参数 + EventParamsHolder.addParams(request); + return path; + } + + @Override + public String touch(FileTouchRequest request) { + String path = Files1.getPath(request.getPath()); + SftpExecutor executor = sftpBasicExecutorHolder.getBasicExecutor(request.getSessionToken()); + synchronized (executor) { + executor.touch(path); + } + // 设置日志参数 + EventParamsHolder.addParams(request); + return path; + } + + @Override + public void truncate(FileTruncateRequest request) { + String path = Files1.getPath(request.getPath()); + SftpExecutor executor = sftpBasicExecutorHolder.getBasicExecutor(request.getSessionToken()); + synchronized (executor) { + executor.truncate(path); + } + // 设置日志参数 + EventParamsHolder.addParams(request); + } + + @Override + public String move(FileMoveRequest request) { + String source = Files1.getPath(request.getSource()); + String target = Files1.getPath(request.getTarget()); + SftpExecutor executor = sftpBasicExecutorHolder.getBasicExecutor(request.getSessionToken()); + synchronized (executor) { + executor.move(source, target); + } + // 设置日志参数 + EventParamsHolder.addParams(request); + return target; + } + + @Override + public void remove(FileRemoveRequest request) { + SftpExecutor executor = sftpBasicExecutorHolder.getBasicExecutor(request.getSessionToken()); + List paths = request.getPaths(); + synchronized (executor) { + paths.stream() + .map(Files1::getPath) + .forEach(executor::remove); + } + // 设置日志参数 + EventParamsHolder.addParams(request); + EventParamsHolder.addParam(EventKeys.COUNT, paths.size()); + } + + @Override + public String chmod(FileChmodRequest request) { + String path = Files1.getPath(request.getPath()); + SftpExecutor executor = sftpBasicExecutorHolder.getBasicExecutor(request.getSessionToken()); + synchronized (executor) { + executor.changeMode(path, request.getPermission()); + } + // 设置日志参数 + EventParamsHolder.addParams(request); + // 返回权限 + return Files1.permission10toString(request.getPermission()); + } + + @Override + public void chown(FileChownRequest request) { + String path = Files1.getPath(request.getPath()); + SftpExecutor executor = sftpBasicExecutorHolder.getBasicExecutor(request.getSessionToken()); + synchronized (executor) { + executor.changeOwner(path, request.getUid()); + } + // 设置日志参数 + EventParamsHolder.addParams(request); + } + + @Override + public void changeGroup(FileChangeGroupRequest request) { + String path = Files1.getPath(request.getPath()); + SftpExecutor executor = sftpBasicExecutorHolder.getBasicExecutor(request.getSessionToken()); + synchronized (executor) { + executor.changeGroup(path, request.getGid()); + } + // 设置日志参数 + EventParamsHolder.addParams(request); + } + + @Override + public List checkFilePresent(FilePresentCheckRequest request) { + SftpExecutor executor = sftpBasicExecutorHolder.getBasicExecutor(request.getSessionToken()); + synchronized (executor) { + return request.getNames().stream() + .filter(Strings::isNotBlank) + .filter(s -> executor.getFile(Files1.getPath(request.getPath(), s)) != null) + .collect(Collectors.toList()); + } + } + + @Override + public String getUploadAccessToken(FileUploadRequest request) { + SftpSessionTokenDTO tokenInfo = this.getTokenInfo(request.getSessionToken()); + Long userId = Currents.getUserId(); + Valid.isTrue(tokenInfo.getUserId().equals(userId), MessageConst.SESSION_EXPIRE); + // 设置缓存信息 + SftpUploadInfoDTO uploadInfo = Converts.to(request, SftpUploadInfoDTO.class); + uploadInfo.setUserId(userId); + uploadInfo.setMachineId(tokenInfo.getMachineId()); + // 设置缓存 + String accessToken = UUIds.random32(); + String key = Strings.format(KeyConst.SFTP_UPLOAD_ACCESS_TOKEN, accessToken); + redisTemplate.opsForValue().set(key, JSON.toJSONString(uploadInfo), + KeyConst.SFTP_UPLOAD_ACCESS_EXPIRE, TimeUnit.SECONDS); + return accessToken; + } + + @Override + public SftpUploadInfoDTO checkUploadAccessToken(String accessToken) { + // 获取缓存 + String key = Strings.format(KeyConst.SFTP_UPLOAD_ACCESS_TOKEN, accessToken); + String value = redisTemplate.opsForValue().get(key); + Valid.notBlank(value, MessageConst.TOKEN_EXPIRE); + // 解析缓存 + SftpUploadInfoDTO uploadInfo = JSON.parseObject(value, SftpUploadInfoDTO.class); + Valid.isTrue(uploadInfo.getUserId().equals(Currents.getUserId()), MessageConst.TOKEN_EXPIRE); + // 删除缓存 + redisTemplate.delete(key); + return uploadInfo; + } + + @Override + public void upload(Long machineId, List requestFiles) { + UserDTO user = Currents.getUser(); + Long userId = user.getId(); + // 初始化上传信息 + List uploadFiles = Lists.newList(); + for (FileUploadRequest requestFile : requestFiles) { + // 插入明细 + FileTransferLogDO upload = new FileTransferLogDO(); + upload.setUserId(userId); + upload.setUserName(user.getUsername()); + upload.setFileToken(requestFile.getFileToken()); + upload.setTransferType(SftpTransferType.UPLOAD.getType()); + upload.setMachineId(machineId); + upload.setRemoteFile(requestFile.getRemotePath()); + upload.setLocalFile(requestFile.getLocalPath()); + upload.setCurrentSize(0L); + upload.setFileSize(requestFile.getSize()); + upload.setNowProgress(0D); + upload.setTransferStatus(SftpTransferStatus.WAIT.getStatus()); + uploadFiles.add(upload); + fileTransferLogDAO.insert(upload); + // 通知添加 + transferProcessorManager.notifySessionAddEvent(userId, machineId, upload); + } + // 提交上传任务 + for (FileTransferLogDO uploadFile : uploadFiles) { + IFileTransferProcessor.of(FileTransferHint.transfer(uploadFile)).exec(); + } + // 设置日志参数 + List remoteFiles = requestFiles.stream() + .map(FileUploadRequest::getRemotePath) + .collect(Collectors.toList()); + EventParamsHolder.addParam(EventKeys.MACHINE_ID, machineId); + EventParamsHolder.addParam(EventKeys.PATHS, remoteFiles); + EventParamsHolder.addParam(EventKeys.COUNT, requestFiles.size()); + } + + @Override + public void download(FileDownloadRequest request) { + // 获取token信息 + Long machineId = this.getMachineId(request.getSessionToken()); + SftpExecutor executor = sftpBasicExecutorHolder.getBasicExecutor(request.getSessionToken()); + UserDTO user = Currents.getUser(); + Long userId = user.getId(); + // 定义文件转换器 + Function convert = file -> { + // 本地保存路径 + String fileToken = ObjectIds.nextId(); + // 设置传输信息 + FileTransferLogDO download = new FileTransferLogDO(); + download.setUserId(userId); + download.setUserName(user.getUsername()); + download.setFileToken(fileToken); + download.setTransferType(SftpTransferType.DOWNLOAD.getType()); + download.setMachineId(machineId); + download.setRemoteFile(file.getPath()); + download.setLocalFile(PathBuilders.getSftpDownloadFilePath(fileToken)); + download.setCurrentSize(0L); + download.setFileSize(file.getSize()); + download.setNowProgress(0D); + download.setTransferStatus(SftpTransferStatus.WAIT.getStatus()); + return download; + }; + // 初始化下载信息 + List downloadFiles = Lists.newList(); + List paths = request.getPaths(); + for (String path : paths) { + SftpFile file = executor.getFile(path); + Valid.notNull(file, Strings.format(MessageConst.FILE_NOT_FOUND, path)); + // 如果是文件夹则递归所有文件 + if (file.isDirectory()) { + List childFiles = executor.listFiles(path, true, false); + childFiles.forEach(f -> downloadFiles.add(convert.apply(f))); + } else { + downloadFiles.add(convert.apply(file)); + } + } + for (FileTransferLogDO downloadFile : downloadFiles) { + fileTransferLogDAO.insert(downloadFile); + // 通知添加 + transferProcessorManager.notifySessionAddEvent(userId, machineId, downloadFile); + } + // 提交下载任务 + for (FileTransferLogDO downloadFile : downloadFiles) { + IFileTransferProcessor.of(FileTransferHint.transfer(downloadFile)).exec(); + } + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.MACHINE_ID, machineId); + EventParamsHolder.addParam(EventKeys.PATHS, paths); + EventParamsHolder.addParam(EventKeys.COUNT, downloadFiles.size()); + } + + @Override + public void packageDownload(FileDownloadRequest request) { + // 获取token信息 + Long machineId = this.getMachineId(request.getSessionToken()); + UserDTO user = Currents.getUser(); + Long userId = user.getId(); + List paths = request.getPaths(); + // 查询机器信息 + MachineInfoDO machine = machineInfoService.selectById(machineId); + Valid.notNull(machine, MessageConst.INVALID_MACHINE); + // 执行压缩命令 + String fileToken = ObjectIds.nextId(); + String zipPath = PathBuilders.getSftpPackageTempPath(machine.getUsername(), fileToken, paths); + String command = Utils.getSftpPackageCommand(zipPath, paths); + try (SessionStore session = machineInfoService.openSessionStore(machine); + CommandExecutor executor = session.getCommandExecutor(Strings.replaceCRLF(command))) { + // 执行命令 + CommandExecutors.execCommand(executor, new IgnoreOutputStream()); + } catch (Exception e) { + throw Exceptions.app(MessageConst.EXECUTE_SFTP_ZIP_COMMAND_ERROR, e); + } + // 获取压缩文件信息 + SftpExecutor executor = sftpBasicExecutorHolder.getBasicExecutor(request.getSessionToken()); + SftpFile zipFile = executor.getFile(zipPath); + Valid.notNull(zipFile, MessageConst.SFTP_ZIP_FILE_ABSENT); + // 设置传输明细 + FileTransferLogDO download = new FileTransferLogDO(); + download.setUserId(userId); + download.setUserName(user.getUsername()); + download.setFileToken(fileToken); + download.setTransferType(SftpTransferType.DOWNLOAD.getType()); + download.setMachineId(machineId); + download.setRemoteFile(zipPath); + download.setLocalFile(PathBuilders.getSftpDownloadFilePath(fileToken)); + download.setCurrentSize(0L); + download.setFileSize(zipFile.getSize()); + download.setNowProgress(0D); + download.setTransferStatus(SftpTransferStatus.WAIT.getStatus()); + fileTransferLogDAO.insert(download); + // 通知添加 + transferProcessorManager.notifySessionAddEvent(userId, machineId, download); + // 提交任务 + IFileTransferProcessor.of(FileTransferHint.transfer(download)).exec(); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.MACHINE_ID, machineId); + EventParamsHolder.addParam(EventKeys.PATHS, paths); + EventParamsHolder.addParam(EventKeys.COUNT, paths.size()); + } + + @Override + public void transferPause(String fileToken) { + // 获取请求文件 + FileTransferLogDO transferLog = this.getTransferLogByToken(fileToken); + Valid.notNull(transferLog, MessageConst.UNSELECTED_TRANSFER_LOG); + // 判断状态是否为进行中 + Integer status = transferLog.getTransferStatus(); + Valid.isTrue(SftpTransferStatus.WAIT.getStatus().equals(status) + || SftpTransferStatus.RUNNABLE.getStatus().equals(status), MessageConst.ILLEGAL_STATUS); + // 获取执行器 + IFileTransferProcessor processor = transferProcessorManager.getProcessor(fileToken); + if (processor != null) { + // 执行器不为空则终止 + processor.stop(); + } else { + Integer changeStatus; + if (SftpTransferType.PACKAGE.getType().equals(transferLog.getTransferType())) { + changeStatus = SftpTransferStatus.CANCEL.getStatus(); + } else { + changeStatus = SftpTransferStatus.PAUSE.getStatus(); + } + // 修改状态 + FileTransferLogDO update = new FileTransferLogDO(); + update.setId(transferLog.getId()); + update.setTransferStatus(changeStatus); + fileTransferLogDAO.updateById(update); + // 通知状态 + transferProcessorManager.notifySessionStatusEvent(transferLog.getUserId(), transferLog.getMachineId(), transferLog.getFileToken(), changeStatus); + } + } + + @Override + public void transferResume(String fileToken) { + // 获取请求文件 + FileTransferLogDO transferLog = this.getTransferLogByToken(fileToken); + Valid.notNull(transferLog, MessageConst.UNSELECTED_TRANSFER_LOG); + Long machineId = transferLog.getMachineId(); + // 判断状态是否为暂停 + Valid.eq(SftpTransferStatus.PAUSE.getStatus(), transferLog.getTransferStatus(), MessageConst.ILLEGAL_STATUS); + this.transferResumeRetry(transferLog, machineId); + } + + @Override + public void transferRetry(String fileToken) { + // 获取请求文件 + FileTransferLogDO transferLog = this.getTransferLogByToken(fileToken); + Valid.notNull(transferLog, MessageConst.UNSELECTED_TRANSFER_LOG); + Long machineId = transferLog.getMachineId(); + // 判断状态是否为失败 + Valid.eq(SftpTransferStatus.ERROR.getStatus(), transferLog.getTransferStatus(), MessageConst.ILLEGAL_STATUS); + this.transferResumeRetry(transferLog, machineId); + } + + /** + * 传输恢复/传输重试 + * + * @param transferLog transferLog + * @param machineId machineId + */ + private void transferResumeRetry(FileTransferLogDO transferLog, Long machineId) { + // 修改状态为等待 + FileTransferLogDO update = new FileTransferLogDO(); + update.setId(transferLog.getId()); + update.setTransferStatus(SftpTransferStatus.WAIT.getStatus()); + fileTransferLogDAO.updateById(update); + // 通知状态 + transferProcessorManager.notifySessionStatusEvent(transferLog.getUserId(), machineId, transferLog.getFileToken(), SftpTransferStatus.WAIT.getStatus()); + // 提交下载 + IFileTransferProcessor.of(FileTransferHint.transfer(transferLog)).exec(); + } + + @Override + public void transferReUpload(String fileToken) { + this.transferReTransfer(fileToken, true); + } + + @Override + public void transferReDownload(String fileToken) { + this.transferReTransfer(fileToken, false); + } + + /** + * 重新传输 + */ + private void transferReTransfer(String fileToken, boolean isUpload) { + // 获取请求文件 + FileTransferLogDO transferLog = this.getTransferLogByToken(fileToken); + Valid.notNull(transferLog, MessageConst.UNSELECTED_TRANSFER_LOG); + Long machineId = transferLog.getMachineId(); + // 暂停 + IFileTransferProcessor processor = transferProcessorManager.getProcessor(transferLog.getFileToken()); + if (processor != null) { + processor.stop(); + } + if (isUpload) { + // 删除远程文件 + SftpExecutor executor = sftpBasicExecutorHolder.getBasicExecutor(machineId); + SftpFile file = executor.getFile(transferLog.getRemoteFile()); + if (file != null) { + executor.removeFile(transferLog.getRemoteFile()); + } + } else { + // 删除本地文件 + String localPath = Files1.getPath(SystemEnvAttr.SWAP_PATH.getValue(), transferLog.getLocalFile()); + Files1.delete(localPath); + } + // 修改进度 + FileTransferLogDO update = new FileTransferLogDO(); + update.setId(transferLog.getId()); + update.setTransferStatus(SftpTransferStatus.WAIT.getStatus()); + update.setNowProgress(0D); + update.setCurrentSize(0L); + fileTransferLogDAO.updateById(update); + // 通知进度 + FileTransferNotifyProgressDTO progress = new FileTransferNotifyProgressDTO(Strings.EMPTY, Files1.getSize(transferLog.getFileSize()), "0"); + transferProcessorManager.notifySessionProgressEvent(transferLog.getUserId(), machineId, transferLog.getFileToken(), progress); + // 通知状态 + transferProcessorManager.notifySessionStatusEvent(transferLog.getUserId(), machineId, transferLog.getFileToken(), SftpTransferStatus.WAIT.getStatus()); + // 提交下载 + IFileTransferProcessor.of(FileTransferHint.transfer(transferLog)).exec(); + } + + @Override + public void transferPauseAll(String sessionToken) { + // 获取token信息 + Long machineId = this.getMachineId(sessionToken); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(FileTransferLogDO::getUserId, Currents.getUserId()) + .eq(FileTransferLogDO::getMachineId, machineId) + .in(FileTransferLogDO::getTransferStatus, SftpTransferStatus.WAIT.getStatus(), SftpTransferStatus.RUNNABLE.getStatus()) + .in(FileTransferLogDO::getTransferType, SftpTransferType.UPLOAD.getType(), SftpTransferType.DOWNLOAD.getType()); + List transferLogs = fileTransferLogDAO.selectList(wrapper); + for (FileTransferLogDO transferLog : transferLogs) { + // 修改状态为暂停 + FileTransferLogDO update = new FileTransferLogDO(); + update.setId(transferLog.getId()); + update.setTransferStatus(SftpTransferStatus.PAUSE.getStatus()); + fileTransferLogDAO.updateById(update); + // 通知状态 + transferProcessorManager.notifySessionStatusEvent(transferLog.getUserId(), machineId, transferLog.getFileToken(), SftpTransferStatus.PAUSE.getStatus()); + } + // 获取执行器暂停 + for (FileTransferLogDO transferLog : transferLogs) { + IFileTransferProcessor processor = transferProcessorManager.getProcessor(transferLog.getFileToken()); + if (processor != null) { + processor.stop(); + } + } + } + + @Override + public void transferResumeAll(String sessionToken) { + // 获取token信息 + Long machineId = this.getMachineId(sessionToken); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(FileTransferLogDO::getUserId, Currents.getUserId()) + .eq(FileTransferLogDO::getMachineId, machineId) + .eq(FileTransferLogDO::getTransferStatus, SftpTransferStatus.PAUSE.getStatus()) + .in(FileTransferLogDO::getTransferType, SftpTransferType.UPLOAD.getType(), SftpTransferType.DOWNLOAD.getType()); + List transferLogs = fileTransferLogDAO.selectList(wrapper); + this.transferResumeRetryAll(transferLogs, machineId); + } + + @Override + public void transferRetryAll(String sessionToken) { + // 获取token信息 + Long machineId = this.getMachineId(sessionToken); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(FileTransferLogDO::getUserId, Currents.getUserId()) + .eq(FileTransferLogDO::getMachineId, machineId) + .eq(FileTransferLogDO::getTransferStatus, SftpTransferStatus.ERROR.getStatus()) + .in(FileTransferLogDO::getTransferType, SftpTransferType.UPLOAD.getType(), SftpTransferType.DOWNLOAD.getType()); + List transferLogs = fileTransferLogDAO.selectList(wrapper); + this.transferResumeRetryAll(transferLogs, machineId); + } + + /** + * 传输恢复/传输重试全部 + * + * @param transferLogs transferLogs + * @param machineId machineId + */ + private void transferResumeRetryAll(List transferLogs, Long machineId) { + for (FileTransferLogDO transferLog : transferLogs) { + // 修改状态为等待 + FileTransferLogDO update = new FileTransferLogDO(); + update.setId(transferLog.getId()); + update.setTransferStatus(SftpTransferStatus.WAIT.getStatus()); + fileTransferLogDAO.updateById(update); + // 通知状态 + transferProcessorManager.notifySessionStatusEvent(transferLog.getUserId(), machineId, transferLog.getFileToken(), SftpTransferStatus.WAIT.getStatus()); + } + // 提交传输 + for (FileTransferLogDO transferLog : transferLogs) { + IFileTransferProcessor.of(FileTransferHint.transfer(transferLog)).exec(); + } + } + + @Override + public void transferRemove(String fileToken) { + // 获取请求文件 + FileTransferLogDO transferLog = this.getTransferLogByToken(fileToken); + Valid.notNull(transferLog, MessageConst.UNSELECTED_TRANSFER_LOG); + // 如果是进行中则需要取消任务 + if (SftpTransferStatus.RUNNABLE.getStatus().equals(transferLog.getTransferStatus())) { + IFileTransferProcessor processor = transferProcessorManager.getProcessor(fileToken); + if (processor != null) { + processor.stop(); + } + } + // 逻辑删除 + fileTransferLogDAO.deleteById(transferLog.getId()); + } + + @Override + public List transferList(Long machineId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(FileTransferLogDO::getUserId, Currents.getUserId()) + .eq(FileTransferLogDO::getMachineId, machineId) + .in(FileTransferLogDO::getTransferType, + SftpTransferType.UPLOAD.getType(), + SftpTransferType.DOWNLOAD.getType(), + SftpTransferType.PACKAGE.getType()) + .orderByDesc(FileTransferLogDO::getId); + // 查询列表 + List logList = fileTransferLogDAO.selectList(wrapper); + // 设置进度 + logList.forEach(log -> { + if (!SftpTransferStatus.RUNNABLE.getStatus().equals(log.getTransferStatus())) { + return; + } + if (!Const.D_0.equals(log.getNowProgress())) { + return; + } + // 进行中 && 进度为0 需要获取进行中的进度 + Double progress = transferProcessorManager.getProgress(log.getFileToken()); + if (progress != null) { + log.setNowProgress(progress); + } + }); + return Converts.toList(logList, FileTransferLogVO.class); + } + + @Override + public Integer transferClear(Long machineId) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(FileTransferLogDO::getUserId, Currents.getUserId()) + .eq(FileTransferLogDO::getMachineId, machineId) + .ne(FileTransferLogDO::getTransferStatus, SftpTransferStatus.RUNNABLE.getStatus()); + return fileTransferLogDAO.delete(wrapper); + } + + @Override + public void transferPackage(String sessionToken, SftpPackageType packageType) { + UserDTO user = Currents.getUser(); + Long userId = user.getId(); + // 获取token信息 + Long machineId = this.getMachineId(sessionToken); + // 查询要打包的文件 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(FileTransferLogDO::getUserId, userId) + .eq(FileTransferLogDO::getMachineId, machineId) + .eq(FileTransferLogDO::getTransferStatus, SftpTransferStatus.FINISH.getStatus()); + switch (packageType) { + case UPLOAD: + wrapper.eq(FileTransferLogDO::getTransferType, SftpTransferType.UPLOAD.getType()); + break; + case DOWNLOAD: + wrapper.eq(FileTransferLogDO::getTransferType, SftpTransferType.DOWNLOAD.getType()); + break; + case ALL: + wrapper.in(FileTransferLogDO::getTransferType, + SftpTransferType.UPLOAD.getType(), + SftpTransferType.DOWNLOAD.getType(), + SftpTransferType.PACKAGE.getType()); + break; + default: + break; + } + wrapper.orderByDesc(FileTransferLogDO::getId); + List logList = fileTransferLogDAO.selectList(wrapper); + Valid.notEmpty(logList, MessageConst.TRANSFER_ITEM_EMPTY); + // 文件名称 + String fileName = Files1.getFileName(logList.get(0).getRemoteFile()) + "等" + logList.size() + "个文件.zip"; + String fileToken = ObjectIds.nextId(); + // 文件大小 + long fileSize = logList.stream().mapToLong(FileTransferLogDO::getFileSize).sum(); + // 插入传输记录 + FileTransferLogDO packageRecord = new FileTransferLogDO(); + packageRecord.setUserId(userId); + packageRecord.setUserName(user.getUsername()); + packageRecord.setFileToken(fileToken); + packageRecord.setTransferType(SftpTransferType.PACKAGE.getType()); + packageRecord.setMachineId(machineId); + packageRecord.setRemoteFile(fileName); + packageRecord.setLocalFile(PathBuilders.getSftpPackageFilePath(fileToken)); + packageRecord.setCurrentSize(0L); + packageRecord.setFileSize(fileSize); + packageRecord.setNowProgress(0D); + packageRecord.setTransferStatus(SftpTransferStatus.WAIT.getStatus()); + fileTransferLogDAO.insert(packageRecord); + // 通知添加 + transferProcessorManager.notifySessionAddEvent(userId, machineId, packageRecord); + // 提交打包任务 + IFileTransferProcessor.of(FileTransferHint.packaged(packageRecord, logList)).exec(); + // 设置日志参数 + List paths = logList.stream() + .map(FileTransferLogDO::getRemoteFile) + .collect(Collectors.toList()); + EventParamsHolder.addParam(EventKeys.MACHINE_ID, machineId); + EventParamsHolder.addParam(EventKeys.PATHS, paths); + EventParamsHolder.addParam(EventKeys.COUNT, logList.size()); + EventParamsHolder.addParam(EventKeys.TYPE, packageType); + } + + @Override + public FileTransferLogDO getDownloadFilePath(Long id) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(!Currents.isAdministrator(), FileTransferLogDO::getUserId, Currents.getUserId()) + .eq(FileTransferLogDO::getTransferStatus, SftpTransferStatus.FINISH.getStatus()) + .eq(FileTransferLogDO::getId, id); + FileTransferLogDO transferLog = fileTransferLogDAO.selectOne(wrapper); + if (transferLog == null) { + return null; + } + transferLog.setLocalFile(Files1.getPath(SystemEnvAttr.SWAP_PATH.getValue(), transferLog.getLocalFile())); + return transferLog; + } + + @Override + public Long getMachineId(String sessionToken) { + return this.getTokenInfo(sessionToken).getMachineId(); + } + + @Override + public SftpSessionTokenDTO getTokenInfo(String sessionToken) { + Valid.notBlank(sessionToken, MessageConst.TOKEN_EMPTY); + String key = Strings.format(KeyConst.SFTP_SESSION_TOKEN, sessionToken); + String value = redisTemplate.opsForValue().get(key); + Valid.notBlank(value, MessageConst.SESSION_EXPIRE); + return JSON.parseObject(value, SftpSessionTokenDTO.class); + } + + /** + * 生成 sessionToken + * + * @param userId userId + * @param machineId 机器id + * @return sessionToken + */ + private String generatorSessionToken(Long userId, Long machineId) { + // 生成token + String sessionToken = UUIds.random15(); + // 设置缓存 + String key = Strings.format(KeyConst.SFTP_SESSION_TOKEN, sessionToken); + SftpSessionTokenDTO info = new SftpSessionTokenDTO(); + info.setUserId(userId); + info.setMachineId(machineId); + redisTemplate.opsForValue().set(key, JSON.toJSONString(info), + KeyConst.SFTP_SESSION_EXPIRE, TimeUnit.SECONDS); + return sessionToken; + } + + /** + * 查询传输日志 + * + * @param fileToken fileToken + * @return FileTransferLogDO + */ + private FileTransferLogDO getTransferLogByToken(String fileToken) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(FileTransferLogDO::getFileToken, fileToken) + .eq(FileTransferLogDO::getUserId, Currents.getUserId()); + return fileTransferLogDAO.selectOne(wrapper); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/StatisticsServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/StatisticsServiceImpl.java new file mode 100644 index 0000000..2d1e578 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/StatisticsServiceImpl.java @@ -0,0 +1,606 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.utils.Arrays1; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.ops.constant.KeyConst; +import cn.orionsec.ops.constant.app.*; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.dao.*; +import cn.orionsec.ops.entity.domain.*; +import cn.orionsec.ops.entity.dto.ApplicationBuildStatisticsDTO; +import cn.orionsec.ops.entity.dto.ApplicationPipelineTaskStatisticsDTO; +import cn.orionsec.ops.entity.dto.ApplicationReleaseStatisticsDTO; +import cn.orionsec.ops.entity.dto.SchedulerTaskRecordStatisticsDTO; +import cn.orionsec.ops.entity.dto.statistic.StatisticsCountDTO; +import cn.orionsec.ops.entity.request.home.HomeStatisticsRequest; +import cn.orionsec.ops.entity.vo.app.*; +import cn.orionsec.ops.entity.vo.home.HomeStatisticsCountVO; +import cn.orionsec.ops.entity.vo.home.HomeStatisticsVO; +import cn.orionsec.ops.entity.vo.scheduler.SchedulerTaskRecordStatisticsChartVO; +import cn.orionsec.ops.entity.vo.scheduler.SchedulerTaskRecordStatisticsVO; +import cn.orionsec.ops.service.api.*; +import cn.orionsec.ops.utils.Utils; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Service("statisticsService") +public class StatisticsServiceImpl implements StatisticsService { + + @Resource + private MachineInfoDAO machineInfoDAO; + + @Resource + private ApplicationProfileDAO applicationProfileDAO; + + @Resource + private ApplicationInfoDAO applicationInfoDAO; + + @Resource + private ApplicationPipelineDAO applicationPipelineDAO; + + @Resource + private SchedulerTaskRecordDAO schedulerTaskRecordDAO; + + @Resource + private ApplicationBuildDAO applicationBuildDAO; + + @Resource + private ApplicationReleaseDAO applicationReleaseDAO; + + @Resource + private ApplicationReleaseMachineService applicationReleaseMachineService; + + @Resource + private ApplicationActionService applicationActionService; + + @Resource + private ApplicationActionLogService applicationActionLogService; + + @Resource + private ApplicationPipelineDetailService applicationPipelineDetailService; + + @Resource + private ApplicationPipelineTaskDAO applicationPipelineTaskDAO; + + @Resource + private ApplicationPipelineTaskDetailService applicationPipelineTaskDetailService; + + @Resource + private RedisTemplate redisTemplate; + + @Override + public HomeStatisticsVO homeStatistics(HomeStatisticsRequest request) { + HomeStatisticsVO statistics = new HomeStatisticsVO(); + // 设置数量 + HomeStatisticsCountVO count = this.homeCountStatistics(request.getProfileId()); + statistics.setCount(count); + return statistics; + } + + /** + * 获取首页统计数量 + * + * @param profileId profileId + * @return 首页统计 + */ + private HomeStatisticsCountVO homeCountStatistics(Long profileId) { + StatisticsCountDTO count; + // 查询缓存 + String key = Strings.format(KeyConst.HOME_STATISTICS_COUNT_KEY, profileId); + String countCache = redisTemplate.opsForValue().get(key); + if (Strings.isBlank(countCache)) { + count = new StatisticsCountDTO(); + // 查询机器数量 + Integer machineCount = machineInfoDAO.selectCount(null); + // 查询环境数量 + Integer profileCount = applicationProfileDAO.selectCount(null); + // 查询应用数量 + Integer appCount = applicationInfoDAO.selectCount(null); + // 查询流水线数量 + LambdaQueryWrapper pipelineWrapper = new LambdaQueryWrapper() + .eq(ApplicationPipelineDO::getProfileId, profileId); + Integer pipelineCount = applicationPipelineDAO.selectCount(pipelineWrapper); + // 设置缓存 + count.setMachineCount(machineCount); + count.setProfileCount(profileCount); + count.setAppCount(appCount); + count.setPipelineCount(pipelineCount); + redisTemplate.opsForValue().set(key, JSON.toJSONString(count), + Integer.parseInt(SystemEnvAttr.STATISTICS_CACHE_EXPIRE.getValue()), TimeUnit.MINUTES); + } else { + count = JSON.parseObject(countCache, StatisticsCountDTO.class); + } + return Converts.to(count, HomeStatisticsCountVO.class); + } + + @Override + public SchedulerTaskRecordStatisticsVO schedulerTaskStatistics(Long taskId) { + // 查询缓存 + String cacheKey = Strings.format(KeyConst.SCHEDULER_TASK_STATISTICS_KEY, taskId); + String cacheData = redisTemplate.opsForValue().get(cacheKey); + if (Strings.isBlank(cacheData)) { + // 获取图表时间 + Date[] chartDates = Dates.getIncrementDates(Dates.clearHms(), Calendar.DAY_OF_MONTH, -1, 7); + Date rangeStartDate = Arrays1.last(chartDates); + // 获取任务统计信息 + SchedulerTaskRecordStatisticsDTO taskStatisticsDTO = schedulerTaskRecordDAO.getTaskRecordStatistics(taskId, rangeStartDate); + SchedulerTaskRecordStatisticsVO statisticsTask = Converts.to(taskStatisticsDTO, SchedulerTaskRecordStatisticsVO.class); + // 获取机器统计信息 + // List machines = schedulerTaskRecordDAO.getTaskMachineRecordStatistics(taskId); + // List statisticsMachines = Converts.toList(machines, SchedulerTaskMachineRecordStatisticsVO.class); + // statisticsTask.setMachineList(statisticsMachines); + // 获取任务统计图表 + List dateStatistics = schedulerTaskRecordDAO.getTaskRecordDateStatistics(taskId, rangeStartDate); + Map dateStatisticsMap = dateStatistics.stream() + .collect(Collectors.toMap(s -> Dates.format(s.getDate(), Dates.YMD), Function.identity(), (e1, e2) -> e2)); + // 填充图表数据 + List statisticsCharts = this.fillSchedulerStatisticsChartData(chartDates, dateStatisticsMap); + statisticsTask.setCharts(statisticsCharts); + // 设置缓存 + redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(statisticsTask), + Integer.parseInt(SystemEnvAttr.STATISTICS_CACHE_EXPIRE.getValue()), TimeUnit.MINUTES); + return statisticsTask; + } else { + return JSON.parseObject(cacheData, SchedulerTaskRecordStatisticsVO.class); + } + } + + @Override + public ApplicationBuildStatisticsViewVO appBuildStatisticsView(Long appId, Long profileId) { + // 查询构建配置 + List appActions = applicationActionService.getAppProfileActions(appId, profileId, StageType.BUILD.getType()); + if (appActions.isEmpty()) { + return null; + } + // 查询最近10次的构建记录 进行中/成功/失败/停止 + LambdaQueryWrapper buildWrapper = new LambdaQueryWrapper() + .eq(ApplicationBuildDO::getAppId, appId) + .eq(ApplicationBuildDO::getProfileId, profileId) + .in(ApplicationBuildDO::getBuildStatus, + BuildStatus.RUNNABLE.getStatus(), + BuildStatus.FINISH.getStatus(), + BuildStatus.FAILURE.getStatus(), + BuildStatus.TERMINATED.getStatus()) + .orderByDesc(ApplicationBuildDO::getId) + .last("LIMIT 10"); + List buildList = applicationBuildDAO.selectList(buildWrapper); + if (buildList.isEmpty()) { + return null; + } + // 查询构建明细 + List buildIdList = buildList.stream().map(ApplicationBuildDO::getId).collect(Collectors.toList()); + List buildActionLogs = applicationActionLogService.selectActionByRelIdList(buildIdList, StageType.BUILD); + // 封装数据 + ApplicationBuildStatisticsViewVO view = new ApplicationBuildStatisticsViewVO(); + // 成功构建平均耗时 + long avgUsed = (long) buildList.stream() + .filter(s -> BuildStatus.FINISH.getStatus().equals(s.getBuildStatus())) + .filter(s -> s.getBuildStartTime() != null && s.getBuildEndTime() != null) + .mapToLong(s -> s.getBuildEndTime().getTime() - s.getBuildStartTime().getTime()) + .average() + .orElse(0); + view.setAvgUsed(avgUsed); + view.setAvgUsedInterval(Utils.interval(avgUsed)); + // 设置构建操作 + List statisticsActions = Converts.toList(appActions, ApplicationActionStatisticsVO.class); + view.setActions(statisticsActions); + // 设置构建操作日志 + Map> buildActionLogsMap = buildActionLogs.stream().collect(Collectors.groupingBy(ApplicationActionLogDO::getRelId)); + List buildRecordList = Lists.newList(); + for (ApplicationBuildDO build : buildList) { + // 设置构建信息 + ApplicationBuildStatisticsRecordVO buildRecord = Converts.to(build, ApplicationBuildStatisticsRecordVO.class); + // 设置构建操作配置 + List buildRecordActionLogs = buildActionLogsMap.get(build.getId()); + if (Lists.isEmpty(buildRecordActionLogs)) { + continue; + } + // 设置构建操作日志 + List recordActionLogs = this.getStatisticsActionLogs(appActions, buildRecordActionLogs); + buildRecord.setActionLogs(recordActionLogs); + buildRecordList.add(buildRecord); + } + view.setBuildRecordList(buildRecordList); + // 设置构建操作平均使用时间 + for (ApplicationActionStatisticsVO statisticsAction : statisticsActions) { + long actionAvgUsed = (long) buildRecordList.stream() + .map(ApplicationBuildStatisticsRecordVO::getActionLogs) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .filter(Objects::nonNull) + .filter(s -> s.getActionId().equals(statisticsAction.getId())) + .filter(s -> ActionStatus.FINISH.getStatus().equals(s.getStatus())) + .map(ApplicationActionLogStatisticsVO::getUsed) + .filter(Objects::nonNull) + .mapToLong(s -> s) + .average() + .orElse(0); + statisticsAction.setAvgUsed(actionAvgUsed); + statisticsAction.setAvgUsedInterval(Utils.interval(actionAvgUsed)); + } + return view; + } + + @Override + public ApplicationBuildStatisticsMetricsWrapperVO appBuildStatisticsMetrics(Long appId, Long profileId) { + ApplicationBuildStatisticsMetricsWrapperVO wrapper = new ApplicationBuildStatisticsMetricsWrapperVO(); + // 获取图表时间 + Date rangeStartDate = Dates.stream().addDay(-7).get(); + // 获取最近构建统计信息 + ApplicationBuildStatisticsDTO lately = applicationBuildDAO.getBuildStatistics(appId, profileId, rangeStartDate); + wrapper.setLately(Converts.to(lately, ApplicationBuildStatisticsMetricsVO.class)); + // 获取所有构建统计信息 + ApplicationBuildStatisticsDTO all = applicationBuildDAO.getBuildStatistics(appId, profileId, null); + wrapper.setAll(Converts.to(all, ApplicationBuildStatisticsMetricsVO.class)); + return wrapper; + } + + @Override + public List appBuildStatisticsChart(Long appId, Long profileId) { + // 获取图表时间 + Date[] chartDates = Dates.getIncrementDates(Dates.clearHms(), Calendar.DAY_OF_MONTH, -1, 7); + Date rangeStartDate = Arrays1.last(chartDates); + // 获取构建统计图表 + List dateStatistics = applicationBuildDAO.getBuildDateStatistics(appId, profileId, rangeStartDate); + Map dateStatisticsMap = dateStatistics.stream() + .collect(Collectors.toMap(s -> Dates.format(s.getDate(), Dates.YMD), Function.identity(), (e1, e2) -> e2)); + // 填充图表数据 + return Arrays.stream(chartDates) + .sorted() + .map(s -> Dates.format(s, Dates.YMD)) + .map(date -> Optional.ofNullable(dateStatisticsMap.get(date)) + .map(s -> Converts.to(s, ApplicationBuildStatisticsChartVO.class)) + .orElseGet(() -> { + ApplicationBuildStatisticsChartVO dateChart = new ApplicationBuildStatisticsChartVO(); + dateChart.setDate(date); + dateChart.setBuildCount(0); + dateChart.setSuccessCount(0); + dateChart.setFailureCount(0); + return dateChart; + })) + .collect(Collectors.toList()); + } + + @Override + public ApplicationReleaseStatisticsViewVO appReleaseStatisticView(Long appId, Long profileId) { + // 查询发布配置 + List appActions = applicationActionService.getAppProfileActions(appId, profileId, StageType.RELEASE.getType()); + if (appActions.isEmpty()) { + return null; + } + // 查询最近10次的发布记录 进行中/成功/失败/停止 + LambdaQueryWrapper releaseWrapper = new LambdaQueryWrapper() + .eq(ApplicationReleaseDO::getAppId, appId) + .eq(ApplicationReleaseDO::getProfileId, profileId) + .in(ApplicationReleaseDO::getReleaseStatus, + ReleaseStatus.RUNNABLE.getStatus(), + ReleaseStatus.FINISH.getStatus(), + ReleaseStatus.FAILURE.getStatus(), + ReleaseStatus.TERMINATED.getStatus()) + .orderByDesc(ApplicationReleaseDO::getId) + .last("LIMIT 10"); + List lastReleaseList = applicationReleaseDAO.selectList(releaseWrapper); + if (lastReleaseList.isEmpty()) { + return null; + } + // 查询发布机器 + List releaseIdList = lastReleaseList.stream().map(ApplicationReleaseDO::getId).collect(Collectors.toList()); + List releaseMachineList = applicationReleaseMachineService.getReleaseMachines(releaseIdList); + if (releaseMachineList.isEmpty()) { + return null; + } + // 查询发布明细 + List releaseMachineIdList = releaseMachineList.stream().map(ApplicationReleaseMachineDO::getId).collect(Collectors.toList()); + List releaseActionLogs = applicationActionLogService.selectActionByRelIdList(releaseMachineIdList, StageType.RELEASE); + // 封装数据 + Map> releaseMachinesMap = releaseMachineList.stream().collect(Collectors.groupingBy(ApplicationReleaseMachineDO::getReleaseId)); + Map> releaseActionLogsMap = releaseActionLogs.stream().collect(Collectors.groupingBy(ApplicationActionLogDO::getRelId)); + ApplicationReleaseStatisticsViewVO view = new ApplicationReleaseStatisticsViewVO(); + // 成功发布平均耗时 + long avgUsed = (long) lastReleaseList.stream() + .filter(s -> ReleaseStatus.FINISH.getStatus().equals(s.getReleaseStatus())) + .filter(s -> s.getReleaseStartTime() != null && s.getReleaseEndTime() != null) + .mapToLong(s -> s.getReleaseEndTime().getTime() - s.getReleaseStartTime().getTime()) + .average() + .orElse(0); + view.setAvgUsed(avgUsed); + view.setAvgUsedInterval(Utils.interval(avgUsed)); + // 设置发布操作 + List statisticsActions = Converts.toList(appActions, ApplicationActionStatisticsVO.class); + view.setActions(statisticsActions); + // 设置发布操作日志 + List recordList = Lists.newList(); + for (ApplicationReleaseDO release : lastReleaseList) { + // 封装发布记录 + ApplicationReleaseStatisticsRecordVO record = Converts.to(release, ApplicationReleaseStatisticsRecordVO.class); + List releaseMachines = releaseMachinesMap.get(release.getId()); + if (Lists.isEmpty(releaseMachines)) { + continue; + } + // 封装发布机器 + List machines = Lists.newList(); + for (ApplicationReleaseMachineDO releaseMachine : releaseMachines) { + ApplicationReleaseStatisticsMachineVO machine = Converts.to(releaseMachine, ApplicationReleaseStatisticsMachineVO.class); + // 封装发布操作 + List recordActionLogs = releaseActionLogsMap.get(releaseMachine.getId()); + if (Lists.isEmpty(recordActionLogs)) { + continue; + } + List actionLogs = this.getStatisticsActionLogs(appActions, recordActionLogs); + machine.setActionLogs(actionLogs); + machines.add(machine); + } + record.setMachines(machines); + recordList.add(record); + } + view.setReleaseRecordList(recordList); + // 设置发布操作平均使用时间 + for (ApplicationActionStatisticsVO statisticsAction : statisticsActions) { + long actionAvgUsed = (long) recordList.stream() + .map(ApplicationReleaseStatisticsRecordVO::getMachines) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .filter(Objects::nonNull) + .map(ApplicationReleaseStatisticsMachineVO::getActionLogs) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .filter(Objects::nonNull) + .filter(s -> s.getActionId().equals(statisticsAction.getId())) + .filter(s -> ActionStatus.FINISH.getStatus().equals(s.getStatus())) + .map(ApplicationActionLogStatisticsVO::getUsed) + .filter(Objects::nonNull) + .mapToLong(s -> s) + .average() + .orElse(0); + statisticsAction.setAvgUsed(actionAvgUsed); + statisticsAction.setAvgUsedInterval(Utils.interval(actionAvgUsed)); + } + return view; + } + + @Override + public ApplicationReleaseStatisticsMetricsWrapperVO appReleaseStatisticMetrics(Long appId, Long profileId) { + ApplicationReleaseStatisticsMetricsWrapperVO wrapper = new ApplicationReleaseStatisticsMetricsWrapperVO(); + // 获取图表时间 + Date rangeStartDate = Dates.stream().addDay(-7).get(); + // 获取最近发布统计信息 + ApplicationReleaseStatisticsDTO lately = applicationReleaseDAO.getReleaseStatistics(appId, profileId, rangeStartDate); + wrapper.setLately(Converts.to(lately, ApplicationReleaseStatisticsMetricsVO.class)); + // 获取所有发布统计信息 + ApplicationReleaseStatisticsDTO all = applicationReleaseDAO.getReleaseStatistics(appId, profileId, null); + wrapper.setAll(Converts.to(all, ApplicationReleaseStatisticsMetricsVO.class)); + return wrapper; + } + + @Override + public List appReleaseStatisticChart(Long appId, Long profileId) { + Date[] chartDates = Dates.getIncrementDates(Dates.clearHms(), Calendar.DAY_OF_MONTH, -1, 7); + Date rangeStartDate = Arrays1.last(chartDates); + // 获取发布统计图表 + List dateStatistics = applicationReleaseDAO.getReleaseDateStatistics(appId, profileId, rangeStartDate); + Map dateStatisticsMap = dateStatistics.stream() + .collect(Collectors.toMap(s -> Dates.format(s.getDate(), Dates.YMD), Function.identity(), (e1, e2) -> e2)); + return Arrays.stream(chartDates) + .sorted() + .map(s -> Dates.format(s, Dates.YMD)) + .map(date -> Optional.ofNullable(dateStatisticsMap.get(date)) + .map(s -> Converts.to(s, ApplicationReleaseStatisticsChartVO.class)) + .orElseGet(() -> { + ApplicationReleaseStatisticsChartVO dateChart = new ApplicationReleaseStatisticsChartVO(); + dateChart.setDate(date); + dateChart.setReleaseCount(0); + dateChart.setSuccessCount(0); + dateChart.setFailureCount(0); + return dateChart; + })) + .collect(Collectors.toList()); + } + + @Override + public ApplicationPipelineTaskStatisticsViewVO appPipelineTaskStatisticView(Long pipelineId) { + // 查询流水线配置 + List pipelineDetails = applicationPipelineDetailService.selectByPipelineId(pipelineId); + if (pipelineDetails.isEmpty()) { + return null; + } + // 查询最近10次的执行记录 进行中/成功/失败/停止 + LambdaQueryWrapper pipelineTaskWrapper = new LambdaQueryWrapper() + .eq(ApplicationPipelineTaskDO::getPipelineId, pipelineId) + .in(ApplicationPipelineTaskDO::getExecStatus, + PipelineStatus.RUNNABLE.getStatus(), + PipelineStatus.FINISH.getStatus(), + PipelineStatus.FAILURE.getStatus(), + PipelineStatus.TERMINATED.getStatus()) + .orderByDesc(ApplicationPipelineTaskDO::getId) + .last("LIMIT 10"); + List taskList = applicationPipelineTaskDAO.selectList(pipelineTaskWrapper); + if (taskList.isEmpty()) { + return null; + } + // 查询执行任务 + List pipelineTaskIdList = taskList.stream().map(ApplicationPipelineTaskDO::getId).collect(Collectors.toList()); + List pipelineTaskDetails = applicationPipelineTaskDetailService.selectTaskDetails(pipelineTaskIdList); + // 封装数据 + ApplicationPipelineTaskStatisticsViewVO view = new ApplicationPipelineTaskStatisticsViewVO(); + // 成功执行平均耗时 + long avgUsed = (long) taskList.stream() + .filter(s -> PipelineStatus.FINISH.getStatus().equals(s.getExecStatus())) + .filter(s -> s.getExecStartTime() != null && s.getExecEndTime() != null) + .mapToLong(s -> s.getExecEndTime().getTime() - s.getExecStartTime().getTime()) + .average() + .orElse(0); + view.setAvgUsed(avgUsed); + view.setAvgUsedInterval(Utils.interval(avgUsed)); + // 设置流水线配置 + List details = Converts.toList(pipelineDetails, ApplicationPipelineStatisticsDetailVO.class); + view.setDetails(details); + // 查询应用名称 + List appIdList = details.stream().map(ApplicationPipelineStatisticsDetailVO::getAppId).collect(Collectors.toList()); + List appNameList = applicationInfoDAO.selectNameByIdList(appIdList); + details.forEach(d -> { + appNameList.stream().filter(s -> s.getId().equals(d.getAppId())) + .findFirst() + .map(ApplicationInfoDO::getAppName) + .ifPresent(d::setAppName); + }); + // 设置流水线任务信息 + Map> taskDetailsGroup = pipelineTaskDetails.stream().collect(Collectors.groupingBy(ApplicationPipelineTaskDetailDO::getTaskId)); + List pipelineTaskList = Lists.newList(); + for (ApplicationPipelineTaskDO task : taskList) { + // 设置流水线信息 + ApplicationPipelineTaskStatisticsTaskVO statisticsTask = Converts.to(task, ApplicationPipelineTaskStatisticsTaskVO.class); + // 设置流水线操作 + List taskDetails = taskDetailsGroup.get(task.getId()); + if (Lists.isEmpty(taskDetails)) { + continue; + } + // 设置流水线操作日志 + List taskDetailLogList = this.getStatisticsPipelineDetailLogs(pipelineDetails, taskDetails); + statisticsTask.setDetails(taskDetailLogList); + pipelineTaskList.add(statisticsTask); + } + view.setPipelineTaskList(pipelineTaskList); + // 设置流水线操作平均使用时间 + for (ApplicationPipelineStatisticsDetailVO detail : details) { + long actionAvgUsed = (long) pipelineTaskList.stream() + .map(ApplicationPipelineTaskStatisticsTaskVO::getDetails) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .filter(Objects::nonNull) + .filter(s -> s.getDetailId().equals(detail.getId())) + .filter(s -> PipelineDetailStatus.FINISH.getStatus().equals(s.getStatus())) + .map(ApplicationPipelineTaskStatisticsDetailVO::getUsed) + .filter(Objects::nonNull) + .mapToLong(s -> s) + .average() + .orElse(0); + detail.setAvgUsed(actionAvgUsed); + detail.setAvgUsedInterval(Utils.interval(actionAvgUsed)); + } + return view; + } + + @Override + public ApplicationPipelineTaskStatisticsMetricsWrapperVO appPipelineTaskStatisticMetrics(Long pipelineId) { + ApplicationPipelineTaskStatisticsMetricsWrapperVO wrapper = new ApplicationPipelineTaskStatisticsMetricsWrapperVO(); + // 获取图表时间 + Date rangeStartDate = Dates.stream().addDay(-7).get(); + // 获取最近发布统计信息 + ApplicationPipelineTaskStatisticsDTO lately = applicationPipelineTaskDAO.getPipelineTaskStatistics(pipelineId, rangeStartDate); + wrapper.setLately(Converts.to(lately, ApplicationPipelineTaskStatisticsMetricsVO.class)); + // 获取所有发布统计信息 + ApplicationPipelineTaskStatisticsDTO all = applicationPipelineTaskDAO.getPipelineTaskStatistics(pipelineId, null); + wrapper.setAll(Converts.to(all, ApplicationPipelineTaskStatisticsMetricsVO.class)); + return wrapper; + } + + @Override + public List appPipelineTaskStatisticChart(Long pipelineId) { + Date[] chartDates = Dates.getIncrementDates(Dates.clearHms(), Calendar.DAY_OF_MONTH, -1, 7); + Date rangeStartDate = Arrays1.last(chartDates); + // 获取发布统计图表 + List dateStatistics = applicationPipelineTaskDAO.getPipelineTaskDateStatistics(pipelineId, rangeStartDate); + Map dateStatisticsMap = dateStatistics.stream() + .collect(Collectors.toMap(s -> Dates.format(s.getDate(), Dates.YMD), Function.identity(), (e1, e2) -> e2)); + return Arrays.stream(chartDates) + .sorted() + .map(s -> Dates.format(s, Dates.YMD)) + .map(date -> Optional.ofNullable(dateStatisticsMap.get(date)) + .map(s -> Converts.to(s, ApplicationPipelineTaskStatisticsChartVO.class)) + .orElseGet(() -> { + ApplicationPipelineTaskStatisticsChartVO dateChart = new ApplicationPipelineTaskStatisticsChartVO(); + dateChart.setDate(date); + dateChart.setExecCount(0); + dateChart.setSuccessCount(0); + dateChart.setFailureCount(0); + return dateChart; + })) + .collect(Collectors.toList()); + } + + /** + * 填充调度任务统计图表数据 + * + * @param chartDates 图表时间 + * @param dateStatisticsMap 图表数据 + * @return data + */ + private List fillSchedulerStatisticsChartData(Date[] chartDates, Map dateStatisticsMap) { + return Arrays.stream(chartDates) + .sorted() + .map(s -> Dates.format(s, Dates.YMD)) + .map(date -> Optional.ofNullable(dateStatisticsMap.get(date)) + .map(s -> Converts.to(s, SchedulerTaskRecordStatisticsChartVO.class)) + .orElseGet(() -> { + SchedulerTaskRecordStatisticsChartVO dateChart = new SchedulerTaskRecordStatisticsChartVO(); + dateChart.setDate(date); + dateChart.setScheduledCount(0); + dateChart.setSuccessCount(0); + dateChart.setFailureCount(0); + return dateChart; + })) + .collect(Collectors.toList()); + } + + /** + * 获取应用统计操作日志 + * + * @param appActions 应用操作 + * @param actionLogs 操作记录 + * @return 统计操作记录 + */ + private List getStatisticsActionLogs(List appActions, List actionLogs) { + List recordActionLogs = Lists.newList(); + for (ApplicationActionDO appAction : appActions) { + ApplicationActionLogStatisticsVO actionLog = actionLogs.stream() + .filter(s -> appAction.getActionName().equals(s.getActionName())) + .findFirst() + .map(s -> { + ApplicationActionLogStatisticsVO log = Converts.to(s, ApplicationActionLogStatisticsVO.class); + log.setActionId(appAction.getId()); + return log; + }) + .orElse(null); + recordActionLogs.add(actionLog); + } + return recordActionLogs; + } + + /** + * 获取应用流水线明细执行日志 + * + * @param pipelineDetails pipelineDetails + * @param taskDetails taskDetails + * @return 执行明细 + */ + private List getStatisticsPipelineDetailLogs(List pipelineDetails, List taskDetails) { + List detailLogs = Lists.newList(); + for (ApplicationPipelineDetailDO pipelineDetail : pipelineDetails) { + ApplicationPipelineTaskStatisticsDetailVO detailLog = taskDetails.stream() + .filter(s -> s.getAppId().equals(pipelineDetail.getAppId())) + .filter(s -> s.getStageType().equals(pipelineDetail.getStageType())) + .findFirst() + .map(s -> { + ApplicationPipelineTaskStatisticsDetailVO log = Converts.to(s, ApplicationPipelineTaskStatisticsDetailVO.class); + log.setDetailId(pipelineDetail.getId()); + return log; + }) + .orElse(null); + detailLogs.add(detailLog); + } + return detailLogs; + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SystemEnvServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SystemEnvServiceImpl.java new file mode 100644 index 0000000..d4cac70 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SystemEnvServiceImpl.java @@ -0,0 +1,224 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.collect.MutableLinkedHashMap; +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.env.EnvConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.history.HistoryOperator; +import cn.orionsec.ops.constant.history.HistoryValueType; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.dao.SystemEnvDAO; +import cn.orionsec.ops.entity.domain.SystemEnvDO; +import cn.orionsec.ops.entity.request.system.SystemEnvRequest; +import cn.orionsec.ops.entity.vo.system.SystemEnvVO; +import cn.orionsec.ops.service.api.ApplicationRepositoryService; +import cn.orionsec.ops.service.api.HistoryValueService; +import cn.orionsec.ops.service.api.SystemEnvService; +import cn.orionsec.ops.utils.DataQuery; +import cn.orionsec.ops.utils.EventParamsHolder; +import cn.orionsec.ops.utils.Valid; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +@Service("systemEnvService") +public class SystemEnvServiceImpl implements SystemEnvService { + + @Resource + private SystemEnvDAO systemEnvDAO; + + @Resource + private HistoryValueService historyValueService; + + @Resource + private ApplicationRepositoryService applicationRepositoryService; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long addEnv(SystemEnvRequest request) { + // 查询 + String key = request.getKey(); + // 重复检查 + SystemEnvDO env = systemEnvDAO.selectOneRel(key); + // 修改 + if (env != null) { + SpringHolder.getBean(SystemEnvService.class).updateEnv(env, request); + return env.getId(); + } + // 新增 + SystemEnvDO insert = new SystemEnvDO(); + insert.setAttrKey(key); + insert.setAttrValue(request.getValue()); + insert.setDescription(request.getDescription()); + systemEnvDAO.insert(insert); + // 插入历史值 + Long id = insert.getId(); + historyValueService.addHistory(id, HistoryValueType.SYSTEM_ENV, HistoryOperator.ADD, null, request.getValue()); + // 设置日志参数 + EventParamsHolder.addParams(insert); + return id; + } + + @Override + public Integer updateEnv(SystemEnvRequest request) { + // 查询 + Long id = request.getId(); + SystemEnvDO before = systemEnvDAO.selectById(id); + Valid.notNull(before, MessageConst.ENV_ABSENT); + return SpringHolder.getBean(SystemEnvService.class).updateEnv(before, request); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer updateEnv(SystemEnvDO before, SystemEnvRequest request) { + // 检查是否修改了值 + Long id = before.getId(); + String key = before.getAttrKey(); + String beforeValue = before.getAttrValue(); + String afterValue = request.getValue(); + if (Const.IS_DELETED.equals(before.getDeleted())) { + // 设置新增历史值 + historyValueService.addHistory(id, HistoryValueType.SYSTEM_ENV, HistoryOperator.ADD, null, afterValue); + // 恢复 + systemEnvDAO.setDeleted(id, Const.NOT_DELETED); + } else if (afterValue != null && !afterValue.equals(beforeValue)) { + // 检查是否修改了值 增加历史值 + historyValueService.addHistory(id, HistoryValueType.SYSTEM_ENV, HistoryOperator.UPDATE, beforeValue, afterValue); + } + // 同步更新对象 + SystemEnvAttr env = SystemEnvAttr.of(key); + if (env != null) { + env.setValue(afterValue); + if (afterValue != null && !afterValue.equals(beforeValue)) { + if (SystemEnvAttr.REPO_PATH.equals(env)) { + // 如果修改的是仓库路径则修改仓库状态 + applicationRepositoryService.syncRepositoryStatus(); + } + } + } + // 修改 + SystemEnvDO update = new SystemEnvDO(); + update.setId(id); + update.setAttrKey(key); + update.setAttrValue(afterValue); + update.setDescription(request.getDescription()); + update.setUpdateTime(new Date()); + // 设置日志参数 + EventParamsHolder.addParams(update); + return systemEnvDAO.updateById(update); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer deleteEnv(List idList) { + int effect = 0; + for (Long id : idList) { + // 获取元数据 + SystemEnvDO env = systemEnvDAO.selectById(id); + Valid.notNull(env, MessageConst.ENV_ABSENT); + String key = env.getAttrKey(); + Valid.isTrue(SystemEnvAttr.of(key) == null, "{} " + MessageConst.FORBID_DELETE, key); + // 删除 + effect += systemEnvDAO.deleteById(id); + // 插入历史值 + historyValueService.addHistory(id, HistoryValueType.SYSTEM_ENV, HistoryOperator.DELETE, env.getAttrValue(), null); + } + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID_LIST, idList); + EventParamsHolder.addParam(EventKeys.COUNT, effect); + return effect; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveEnv(Map env) { + SystemEnvService self = SpringHolder.getBean(SystemEnvService.class); + // 倒排 + List> entries = Lists.newList(env.entrySet()); + int size = entries.size(); + for (int i = size - 1; i >= 0; i--) { + // 更新 + Map.Entry entry = entries.get(i); + SystemEnvRequest request = new SystemEnvRequest(); + request.setKey(entry.getKey()); + request.setValue(entry.getValue()); + self.addEnv(request); + } + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.COUNT, size); + } + + @Override + public DataGrid listEnv(SystemEnvRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .like(Strings.isNotBlank(request.getKey()), SystemEnvDO::getAttrKey, request.getKey()) + .like(Strings.isNotBlank(request.getValue()), SystemEnvDO::getAttrValue, request.getValue()) + .like(Strings.isNotBlank(request.getDescription()), SystemEnvDO::getDescription, request.getDescription()) + .eq(SystemEnvDO::getSystemEnv, Const.NOT_SYSTEM) + .orderByDesc(SystemEnvDO::getUpdateTime); + return DataQuery.of(systemEnvDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(SystemEnvVO.class); + } + + @Override + public SystemEnvVO getEnvDetail(Long id) { + SystemEnvDO env = systemEnvDAO.selectById(id); + Valid.notNull(env, MessageConst.UNKNOWN_DATA); + return Converts.to(env, SystemEnvVO.class); + } + + @Override + public String getEnvValue(String env) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(SystemEnvDO::getAttrKey, env) + .last(Const.LIMIT_1); + return Optional.ofNullable(systemEnvDAO.selectOne(wrapper)) + .map(SystemEnvDO::getAttrValue) + .orElse(null); + } + + @Override + public SystemEnvDO selectByName(String env) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(SystemEnvDO::getAttrKey, env) + .last(Const.LIMIT_1); + return systemEnvDAO.selectOne(wrapper); + } + + @Override + public MutableLinkedHashMap getSystemEnv() { + MutableLinkedHashMap env = Maps.newMutableLinkedMap(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(SystemEnvDO::getSystemEnv, Const.NOT_SYSTEM) + .orderByAsc(SystemEnvDO::getId); + systemEnvDAO.selectList(wrapper).forEach(e -> env.put(e.getAttrKey(), e.getAttrValue())); + return env; + } + + @Override + public MutableLinkedHashMap getFullSystemEnv() { + MutableLinkedHashMap env = Maps.newMutableLinkedMap(); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(SystemEnvDO::getSystemEnv, Const.NOT_SYSTEM) + .orderByAsc(SystemEnvDO::getId); + systemEnvDAO.selectList(wrapper).forEach(e -> env.put(EnvConst.SYSTEM_PREFIX + e.getAttrKey(), e.getAttrValue())); + return env; + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SystemServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SystemServiceImpl.java new file mode 100644 index 0000000..66912fd --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/SystemServiceImpl.java @@ -0,0 +1,274 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.Threads; +import cn.orionsec.kit.lang.utils.Valid; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.common.EnableType; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.system.SystemCleanType; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.constant.system.ThreadPoolMetricsType; +import cn.orionsec.ops.entity.domain.SystemEnvDO; +import cn.orionsec.ops.entity.dto.system.SystemSpaceAnalysisDTO; +import cn.orionsec.ops.entity.request.system.ConfigIpListRequest; +import cn.orionsec.ops.entity.request.system.SystemEnvRequest; +import cn.orionsec.ops.entity.vo.system.IpListConfigVO; +import cn.orionsec.ops.entity.vo.system.SystemAnalysisVO; +import cn.orionsec.ops.entity.vo.system.SystemOptionVO; +import cn.orionsec.ops.entity.vo.system.ThreadPoolMetricsVO; +import cn.orionsec.ops.interceptor.IpFilterInterceptor; +import cn.orionsec.ops.service.api.SystemEnvService; +import cn.orionsec.ops.service.api.SystemService; +import cn.orionsec.ops.utils.EventParamsHolder; +import cn.orionsec.ops.utils.FileCleaner; +import cn.orionsec.ops.utils.Utils; +import com.alibaba.fastjson.JSON; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.stream.Collectors; + +@Slf4j +@Service("systemService") +public class SystemServiceImpl implements SystemService { + + private final SystemSpaceAnalysisDTO systemSpace; + + @Resource + private SystemEnvService systemEnvService; + + @Resource + private IpFilterInterceptor ipFilterInterceptor; + + public SystemServiceImpl() { + this.systemSpace = new SystemSpaceAnalysisDTO(); + } + + @Override + public IpListConfigVO getIpInfo(String ip) { + IpListConfigVO ipConfig = new IpListConfigVO(); + // 查询黑名单 + ipConfig.setBlackIpList(SystemEnvAttr.BLACK_IP_LIST.getValue()); + // 查询白名单 + ipConfig.setWhiteIpList(SystemEnvAttr.WHITE_IP_LIST.getValue()); + // 查询是否启用过滤 + ipConfig.setEnableIpFilter(EnableType.of(SystemEnvAttr.ENABLE_IP_FILTER.getValue()).getValue()); + // 查询是否启用IP白名单 + ipConfig.setEnableWhiteIpList(EnableType.of(SystemEnvAttr.ENABLE_WHITE_IP_LIST.getValue()).getValue()); + // ip + ipConfig.setCurrentIp(ip); + // ip 位置 + ipConfig.setIpLocation(Utils.getIpLocation(ip)); + return ipConfig; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void configIpFilterList(ConfigIpListRequest request) { + // 检查名单 + String blackIpList = request.getBlackIpList(); + String whiteIpList = request.getWhiteIpList(); + this.validIpConfig(blackIpList); + this.validIpConfig(whiteIpList); + // 设置黑名单 + SystemEnvDO blackEnv = systemEnvService.selectByName(SystemEnvAttr.BLACK_IP_LIST.getKey()); + SystemEnvRequest updateBlack = new SystemEnvRequest(); + updateBlack.setValue(blackIpList); + systemEnvService.updateEnv(blackEnv, updateBlack); + SystemEnvAttr.BLACK_IP_LIST.setValue(blackIpList); + // 设置白名单 + SystemEnvDO whiteEnv = systemEnvService.selectByName(SystemEnvAttr.WHITE_IP_LIST.getKey()); + SystemEnvRequest updateWhite = new SystemEnvRequest(); + updateWhite.setValue(whiteIpList); + systemEnvService.updateEnv(whiteEnv, updateWhite); + SystemEnvAttr.WHITE_IP_LIST.setValue(whiteIpList); + // 更改启用状态 + EnableType enableIpFilter = EnableType.of(request.getEnableIpFilter()); + SystemEnvDO filterEnv = systemEnvService.selectByName(SystemEnvAttr.ENABLE_IP_FILTER.getKey()); + SystemEnvRequest updateFilter = new SystemEnvRequest(); + updateFilter.setValue(enableIpFilter.getLabel()); + systemEnvService.updateEnv(filterEnv, updateFilter); + SystemEnvAttr.ENABLE_IP_FILTER.setValue(enableIpFilter.getLabel()); + // 更改启用列表 + EnableType enableWhiteIp = EnableType.of(request.getEnableWhiteIpList()); + SystemEnvDO enableWhiteIpEnv = systemEnvService.selectByName(SystemEnvAttr.ENABLE_WHITE_IP_LIST.getKey()); + SystemEnvRequest updateEnableWhiteIp = new SystemEnvRequest(); + updateEnableWhiteIp.setValue(enableWhiteIp.getLabel()); + systemEnvService.updateEnv(enableWhiteIpEnv, updateEnableWhiteIp); + SystemEnvAttr.ENABLE_WHITE_IP_LIST.setValue(enableWhiteIp.getLabel()); + // 设置日志参数 + EventParamsHolder.addParams(request); + // 设置 ip 过滤器 + Boolean enableIpFilterValue = enableIpFilter.getValue(); + Boolean enableWhiteIpValue = enableWhiteIp.getValue(); + ipFilterInterceptor.set(enableIpFilterValue, enableWhiteIpValue, enableWhiteIpValue ? whiteIpList : blackIpList); + } + + @Override + public void cleanSystemFile(SystemCleanType cleanType) { + // 清理 + Threads.start(() -> FileCleaner.cleanDir(cleanType)); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.LABEL, cleanType.getLabel()); + } + + @Override + public void analysisSystemSpace() { + // 临时文件 + File tempPath = new File(SystemEnvAttr.TEMP_PATH.getValue()); + List tempFiles = Files1.listFiles(tempPath, true); + long tempFilesBytes = tempFiles.stream().mapToLong(File::length).sum(); + systemSpace.setTempFileCount(tempFiles.size()); + systemSpace.setTempFileSize(Files1.getSize(tempFilesBytes)); + tempFiles.clear(); + + // 日志文件 + File logPath = new File(SystemEnvAttr.LOG_PATH.getValue()); + List logFiles = Files1.listFiles(logPath, true); + long logFilesBytes = logFiles.stream().mapToLong(File::length).sum(); + systemSpace.setLogFileCount(logFiles.size()); + systemSpace.setLogFileSize(Files1.getSize(logFilesBytes)); + logFiles.clear(); + + // 交换区文件 + File swapPath = new File(SystemEnvAttr.SWAP_PATH.getValue()); + List swapFiles = Files1.listFiles(swapPath, true); + long swapFilesBytes = swapFiles.stream().mapToLong(File::length).sum(); + systemSpace.setSwapFileCount(swapFiles.size()); + systemSpace.setSwapFileSize(Files1.getSize(swapFilesBytes)); + swapFiles.clear(); + + // 构建产物 + File buildPath = new File(SystemEnvAttr.DIST_PATH.getValue() + Const.BUILD_DIR); + List buildFiles = Files1.listFiles(buildPath, true); + long buildFilesBytes = buildFiles.stream().filter(File::isFile).mapToLong(File::length).sum(); + int distVersions = Files1.listDirs(buildPath).size(); + systemSpace.setDistVersionCount(distVersions); + systemSpace.setDistFileSize(Files1.getSize(buildFilesBytes)); + buildFiles.clear(); + + // 文件太多会导致 oom + // // 应用仓库 + // File repoPath = new File(SystemEnvAttr.REPO_PATH.getValue()); + // List repoPaths = Files1.listFilesFilter(repoPath, (f, n) -> f.isDirectory() && !Const.EVENT.equals(n), false, true); + // int repoVersionCount = 0; + // long repoDirFilesBytes = 0L; + // for (File repoDir : repoPaths) { + // repoVersionCount += Files1.listDirs(repoDir).size(); + // List repoDirFiles = Files1.listFiles(repoDir, true); + // repoDirFilesBytes += repoDirFiles.stream().mapToLong(File::length).sum(); + // } + // systemSpace.setRepoVersionCount(repoVersionCount); + // systemSpace.setRepoVersionFileSize(Files1.getSize(repoDirFilesBytes)); + // repoPaths.clear(); + + // 录屏文件 + File screenPath = new File(SystemEnvAttr.SCREEN_PATH.getValue()); + List screenFiles = Files1.listFiles(screenPath, true); + long screenFilesBytes = screenFiles.stream().mapToLong(File::length).sum(); + systemSpace.setScreenFileCount(screenFiles.size()); + systemSpace.setScreenFileSize(Files1.getSize(screenFilesBytes)); + screenFiles.clear(); + log.info("分析占用磁盘空间完成 {}", JSON.toJSONString(systemSpace)); + } + + @Override + public SystemAnalysisVO getSystemAnalysis() { + SystemAnalysisVO vo = Converts.to(systemSpace, SystemAnalysisVO.class); + // 文件清理 + vo.setFileCleanThreshold(Integer.valueOf(SystemEnvAttr.FILE_CLEAN_THRESHOLD.getValue())); + vo.setAutoCleanFile(EnableType.of(SystemEnvAttr.ENABLE_AUTO_CLEAN_FILE.getValue()).getValue()); + return vo; + } + + @Override + public void updateSystemOption(SystemEnvAttr env, String value) { + String key = env.getKey(); + String beforeValue = env.getValue(); + // 更新系统配置 + SystemEnvDO systemEnv = systemEnvService.selectByName(key); + SystemEnvRequest updateEnv = new SystemEnvRequest(); + updateEnv.setValue(value); + systemEnvService.updateEnv(systemEnv, updateEnv); + env.setValue(value); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.KEY, env.getDescription()); + EventParamsHolder.addParam(EventKeys.LABEL, env.getDescription()); + EventParamsHolder.addParam(EventKeys.BEFORE, beforeValue); + EventParamsHolder.addParam(EventKeys.AFTER, value); + } + + @Override + public SystemOptionVO getSystemOptions() { + SystemOptionVO options = new SystemOptionVO(); + options.setAutoCleanFile(EnableType.of(SystemEnvAttr.ENABLE_AUTO_CLEAN_FILE.getValue()).getValue()); + options.setFileCleanThreshold(Integer.valueOf(SystemEnvAttr.FILE_CLEAN_THRESHOLD.getValue())); + options.setAllowMultipleLogin(EnableType.of(SystemEnvAttr.ALLOW_MULTIPLE_LOGIN.getValue()).getValue()); + options.setLoginFailureLock(EnableType.of(SystemEnvAttr.LOGIN_FAILURE_LOCK.getValue()).getValue()); + options.setLoginIpBind(EnableType.of(SystemEnvAttr.LOGIN_IP_BIND.getValue()).getValue()); + options.setLoginTokenAutoRenew(EnableType.of(SystemEnvAttr.LOGIN_TOKEN_AUTO_RENEW.getValue()).getValue()); + options.setLoginTokenExpire(Integer.valueOf(SystemEnvAttr.LOGIN_TOKEN_EXPIRE.getValue())); + options.setLoginFailureLockThreshold(Integer.valueOf(SystemEnvAttr.LOGIN_FAILURE_LOCK_THRESHOLD.getValue())); + options.setLoginTokenAutoRenewThreshold(Integer.valueOf(SystemEnvAttr.LOGIN_TOKEN_AUTO_RENEW_THRESHOLD.getValue())); + options.setResumeEnableSchedulerTask(EnableType.of(SystemEnvAttr.RESUME_ENABLE_SCHEDULER_TASK.getValue()).getValue()); + options.setTerminalActivePushHeartbeat(EnableType.of(SystemEnvAttr.TERMINAL_ACTIVE_PUSH_HEARTBEAT.getValue()).getValue()); + options.setSftpUploadThreshold(Integer.valueOf(SystemEnvAttr.SFTP_UPLOAD_THRESHOLD.getValue())); + options.setStatisticsCacheExpire(Integer.valueOf(SystemEnvAttr.STATISTICS_CACHE_EXPIRE.getValue())); + return options; + } + + @Override + public List getThreadPoolMetrics() { + return Arrays.stream(ThreadPoolMetricsType.values()) + .map(this::getThreadPoolMetrics) + .collect(Collectors.toList()); + } + + /** + * 校验 ip 过滤列表 + * + * @param ipList ipList + */ + private void validIpConfig(String ipList) { + if (Strings.isBlank(ipList)) { + return; + } + String[] lines = ipList.split(Const.LF); + for (String ip : lines) { + if (Strings.isBlank(ip)) { + continue; + } + Valid.isTrue(Utils.validIpLine(ip), Strings.format("{} " + MessageConst.INVALID_CONFIG, ip)); + } + } + + + /** + * 获取线程池指标 + * + * @param metricsType 指标类型 + * @return 指标 + */ + private ThreadPoolMetricsVO getThreadPoolMetrics(ThreadPoolMetricsType metricsType) { + ThreadPoolMetricsVO metrics = new ThreadPoolMetricsVO(); + metrics.setType(metricsType.getType()); + ThreadPoolExecutor executor = metricsType.getExecutor(); + metrics.setActiveThreadCount(executor.getActiveCount()); + metrics.setTotalTaskCount(executor.getTaskCount()); + metrics.setCompletedTaskCount(executor.getCompletedTaskCount()); + metrics.setQueueSize(executor.getQueue().size()); + return metrics; + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/UserEventLogServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/UserEventLogServiceImpl.java new file mode 100644 index 0000000..18d26bd --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/UserEventLogServiceImpl.java @@ -0,0 +1,105 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.collect.MutableMap; +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Objects1; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.ResultCode; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.dao.UserEventLogDAO; +import cn.orionsec.ops.entity.domain.UserEventLogDO; +import cn.orionsec.ops.entity.request.user.EventLogRequest; +import cn.orionsec.ops.entity.vo.user.UserEventLogVO; +import cn.orionsec.ops.service.api.UserEventLogService; +import cn.orionsec.ops.utils.*; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.Objects; +import java.util.Optional; + +@Service("userEventLogService") +public class UserEventLogServiceImpl implements UserEventLogService { + + @Resource + private UserEventLogDAO userEventLogDAO; + + @Override + public void recordLog(EventType eventType, boolean isSuccess) { + // 获取以及移除参数 + MutableMap paramsMap = EventParamsHolder.get(); + EventParamsHolder.remove(); + // 判断是否保存 + if (!paramsMap.getBooleanValue(EventKeys.INNER_SAVE, true)) { + return; + } + // 读取内置参数 + Long userId = paramsMap.getLong(EventKeys.INNER_USER_ID); + if (userId == null) { + return; + } + // 模板 + String template = paramsMap.getString(EventKeys.INNER_TEMPLATE, eventType.getTemplate()); + // 设置对象 + UserEventLogDO log = new UserEventLogDO(); + log.setUserId(userId); + log.setUsername(paramsMap.getString(EventKeys.INNER_USER_NAME)); + log.setEventClassify(eventType.getClassify().getClassify()); + log.setEventType(eventType.getType()); + log.setLogInfo(Strings.format(template, paramsMap)); + // 移除内部key + EventLogUtils.removeInnerKeys(paramsMap); + log.setParamsJson(JSON.toJSONString(paramsMap)); + log.setExecResult(isSuccess ? Const.ENABLE : Const.DISABLE); + log.setCreateTime(new Date()); + // 插入 + userEventLogDAO.insert(log); + } + + @Override + public DataGrid getLogList(EventLogRequest request) { + if (Const.ENABLE.equals(request.getOnlyMyself())) { + request.setUserId(Currents.getUserId()); + } else if (!Currents.isAdministrator()) { + throw Exceptions.httpWrapper(HttpWrapper.of(ResultCode.NO_PERMISSION)); + } + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(Objects.nonNull(request.getUserId()), UserEventLogDO::getUserId, request.getUserId()) + .eq(Objects.nonNull(request.getClassify()), UserEventLogDO::getEventClassify, request.getClassify()) + .eq(Objects.nonNull(request.getType()), UserEventLogDO::getEventType, request.getType()) + .eq(Objects.nonNull(request.getResult()), UserEventLogDO::getExecResult, request.getResult()) + .like(Strings.isNotBlank(request.getUsername()), UserEventLogDO::getUsername, request.getUsername()) + .like(Strings.isNotBlank(request.getLog()), UserEventLogDO::getLogInfo, request.getLog()) + .like(Strings.isNotBlank(request.getParams()), UserEventLogDO::getParamsJson, request.getParams()) + .between(Objects1.isNoneNull(request.getRangeStart(), request.getRangeEnd()), UserEventLogDO::getCreateTime, + request.getRangeStart(), request.getRangeEnd()) + .orderByDesc(UserEventLogDO::getCreateTime); + // 查询 + DataGrid logs = DataQuery.of(userEventLogDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(UserEventLogVO.class); + if (!Const.ENABLE.equals(request.getParserIp())) { + return logs; + } + // 解析ip + logs.forEach(log -> { + String ip = Optional.ofNullable(log.getParams()) + .map(JSON::parseObject) + .map(s -> s.getString(EventKeys.INNER_REQUEST_IP)) + .orElse(null); + log.setIp(ip); + log.setIpLocation(Utils.getIpLocation(ip)); + }); + return logs; + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/UserServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..24ff7ed --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/UserServiceImpl.java @@ -0,0 +1,316 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.kit.lang.id.UUIds; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Objects1; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.codec.Base64s; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.lang.utils.crypto.Signatures; +import cn.orionsec.kit.lang.utils.io.FileWriters; +import cn.orionsec.ops.constant.CnConst; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.KeyConst; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.constant.user.RoleType; +import cn.orionsec.ops.dao.UserInfoDAO; +import cn.orionsec.ops.entity.domain.UserInfoDO; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.entity.request.user.UserInfoRequest; +import cn.orionsec.ops.entity.vo.user.UserInfoVO; +import cn.orionsec.ops.interceptor.UserActiveInterceptor; +import cn.orionsec.ops.service.api.AlarmGroupUserService; +import cn.orionsec.ops.service.api.UserService; +import cn.orionsec.ops.utils.*; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +@Service("userService") +public class UserServiceImpl implements UserService { + + private static final String DEFAULT_USERNAME = "orionadmin"; + + private static final String DEFAULT_NICKNAME = "管理员"; + + @Resource + private UserInfoDAO userInfoDAO; + + @Resource + private AlarmGroupUserService alarmGroupUserService; + + @Resource + private RedisTemplate redisTemplate; + + @Resource + private UserActiveInterceptor userActiveInterceptor; + + @Override + public DataGrid userList(UserInfoRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(Objects.nonNull(request.getId()), UserInfoDO::getId, request.getId()) + .eq(Objects.nonNull(request.getRole()), UserInfoDO::getRoleType, request.getRole()) + .eq(Objects.nonNull(request.getStatus()), UserInfoDO::getUserStatus, request.getStatus()) + .like(Strings.isNotBlank(request.getUsername()), UserInfoDO::getUsername, request.getUsername()) + .like(Strings.isNotBlank(request.getNickname()), UserInfoDO::getNickname, request.getNickname()) + .like(Strings.isNotBlank(request.getPhone()), UserInfoDO::getContactPhone, request.getPhone()) + .like(Strings.isNotBlank(request.getEmail()), UserInfoDO::getContactEmail, request.getEmail()); + return DataQuery.of(userInfoDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(UserInfoVO.class); + } + + @Override + public UserInfoVO userDetail(UserInfoRequest request) { + Long id = Objects1.def(request.getId(), Currents::getUserId); + UserInfoDO info = userInfoDAO.selectById(id); + Valid.notNull(info, MessageConst.UNKNOWN_USER); + UserInfoVO user = Converts.to(info, UserInfoVO.class); + user.setAvatar(AvatarPicHolder.getUserAvatar(info.getAvatarPic())); + return user; + } + + @Override + public Long addUser(UserInfoRequest request) { + // 用户名重复检查 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(UserInfoDO::getUsername, request.getUsername()); + UserInfoDO query = userInfoDAO.selectOne(wrapper); + if (query != null) { + throw Exceptions.httpWrapper(HttpWrapper.error(MessageConst.USERNAME_PRESENT)); + } + // 密码 + String salt = UUIds.random19(); + String password = ValueMix.encPassword(request.getPassword(), salt); + // 创建 + UserInfoDO insert = new UserInfoDO(); + insert.setUsername(request.getUsername()); + insert.setNickname(request.getNickname()); + insert.setPassword(password); + insert.setSalt(salt); + insert.setRoleType(request.getRole()); + insert.setUserStatus(Const.ENABLE); + insert.setContactPhone(request.getPhone()); + insert.setContactEmail(request.getEmail()); + userInfoDAO.insert(insert); + // 设置日志参数 + EventParamsHolder.addParams(insert); + return insert.getId(); + } + + @Override + public Integer updateUser(UserInfoRequest request) { + Long userId = Currents.getUserId(); + Long updateId = request.getId(); + String nickname = request.getNickname(); + final boolean updateCurrent = updateId.equals(userId); + // 查询用户信息 + UserInfoDO userInfo = userInfoDAO.selectById(updateId); + Valid.notNull(userInfo, MessageConst.UNKNOWN_USER); + // 设置更新信息 + UserInfoDO update = new UserInfoDO(); + update.setId(updateId); + update.setUsername(userInfo.getUsername()); + update.setNickname(nickname); + update.setContactPhone(request.getPhone()); + update.setContactEmail(request.getEmail()); + update.setUserStatus(request.getStatus()); + update.setUpdateTime(new Date()); + if (!updateCurrent && Currents.isAdministrator()) { + update.setRoleType(request.getRole()); + } + int effect = userInfoDAO.updateById(update); + // 设置日志参数 + EventParamsHolder.addParams(update); + // 更新用户缓存数据 + this.updateUserCache(updateId, nickname, update.getRoleType(), null); + return effect; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer deleteUser(Long id) { + Long userId = Currents.getUserId(); + // 删除检查 + Valid.isTrue(!userId.equals(id), MessageConst.UNSUPPORTED_OPERATOR); + // 查询用户信息 + UserInfoDO userInfo = userInfoDAO.selectById(id); + Valid.notNull(userInfo, MessageConst.UNKNOWN_USER); + // 删除用户 + int effect = userInfoDAO.deleteById(id); + // 删除报警组员 + effect += alarmGroupUserService.deleteByUserId(id); + // 删除token + RedisUtils.deleteLoginToken(redisTemplate, id); + // 删除活跃时间 + userActiveInterceptor.deleteActiveTime(id); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.USERNAME, userInfo.getUsername()); + return effect; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer updateStatus(Long id, Integer status) { + Long userId = Currents.getUserId(); + // 更新检查 + Valid.isTrue(!userId.equals(id), MessageConst.UNSUPPORTED_OPERATOR); + // 查询用户信息 + UserInfoDO userInfo = userInfoDAO.selectById(id); + Valid.notNull(userInfo, MessageConst.UNKNOWN_USER); + // 更新 + boolean enable = Const.ENABLE.equals(status); + UserInfoDO update = new UserInfoDO(); + update.setId(id); + update.setUserStatus(status); + update.setUpdateTime(new Date()); + int effect = userInfoDAO.updateById(update); + // 更新用户缓存数据 + this.updateUserCache(id, null, null, status); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.USERNAME, userInfo.getUsername()); + EventParamsHolder.addParam(EventKeys.OPERATOR, enable ? CnConst.ENABLE : CnConst.DISABLE); + return effect; + } + + @Override + public Integer unlockUser(Long id) { + // 查询用户信息 + UserInfoDO userInfo = userInfoDAO.selectById(id); + Valid.notNull(userInfo, MessageConst.UNKNOWN_USER); + // 更新 + UserInfoDO update = new UserInfoDO(); + update.setId(id); + update.setLockStatus(Const.ENABLE); + update.setFailedLoginCount(0); + update.setUpdateTime(new Date()); + int effect = userInfoDAO.updateById(update); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.USERNAME, userInfo.getUsername()); + return effect; + } + + @Override + public Integer updateAvatar(String avatar) { + Long userId = Currents.getUserId(); + UserInfoDO userInfo = userInfoDAO.selectById(userId); + // 删除原图片 + if (!Strings.isBlank(userInfo.getAvatarPic())) { + AvatarPicHolder.deletePic(userInfo.getAvatarPic()); + } + // 写入图片 + String type = Base64s.getMimeTypeLast(avatar); + String url = AvatarPicHolder.getPicPath(userId, type); + byte[] pic = Base64s.mimeTypeDecode(avatar); + String fullPicPath = AvatarPicHolder.touchPicFile(userId, type); + FileWriters.writeFast(fullPicPath, pic); + // 更新 + UserInfoDO update = new UserInfoDO(); + update.setId(userId); + update.setAvatarPic(url); + update.setUpdateTime(new Date()); + return userInfoDAO.updateById(update); + } + + @Override + public void generatorDefaultAdminUser() { + // 用户名重复检查 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(UserInfoDO::getUsername, DEFAULT_USERNAME); + UserInfoDO admin = userInfoDAO.selectOne(wrapper); + if (admin != null) { + return; + } + // 密码 + String salt = UUIds.random19(); + String password = ValueMix.encPassword(Signatures.md5(DEFAULT_USERNAME), salt); + // 创建用户 + UserInfoDO insert = new UserInfoDO(); + insert.setUsername(DEFAULT_USERNAME); + insert.setNickname(DEFAULT_NICKNAME); + insert.setPassword(password); + insert.setSalt(salt); + insert.setRoleType(RoleType.ADMINISTRATOR.getType()); + insert.setUserStatus(Const.ENABLE); + userInfoDAO.insert(insert); + } + + @Override + public void resetDefaultAdminUserPassword() { + // 用户名重复检查 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(UserInfoDO::getUsername, DEFAULT_USERNAME); + UserInfoDO admin = userInfoDAO.selectOne(wrapper); + if (admin != null) { + // 重置用户 + Long id = admin.getId(); + UserInfoDO update = new UserInfoDO(); + String password = ValueMix.encPassword(Signatures.md5(DEFAULT_USERNAME), admin.getSalt()); + update.setId(id); + update.setPassword(password); + update.setFailedLoginCount(0); + update.setUserStatus(Const.ENABLE); + update.setLockStatus(Const.ENABLE); + update.setUpdateTime(new Date()); + userInfoDAO.updateById(update); + if (!password.equals(admin.getPassword())) { + // 密码不同删除token + RedisUtils.deleteLoginToken(redisTemplate, id); + } + } else { + // 生成用户 + this.generatorDefaultAdminUser(); + } + } + + /** + * 更新用户缓存 + * + * @param updateId updateId + * @param nickname nickname + * @param roleType roleType + * @param userStatus userStatus + */ + private void updateUserCache(Long updateId, String nickname, Integer roleType, Integer userStatus) { + // 查询缓存 + String cacheKey = Strings.format(KeyConst.LOGIN_TOKEN_KEY, updateId); + String tokenValue = redisTemplate.opsForValue().get(cacheKey); + if (Strings.isEmpty(tokenValue)) { + return; + } + // 查询过期时间 + Long expire = redisTemplate.getExpire(cacheKey, TimeUnit.SECONDS); + if (expire == null) { + expire = TimeUnit.HOURS.toSeconds(Long.parseLong(SystemEnvAttr.LOGIN_TOKEN_EXPIRE.getValue())); + } + // 更新缓存 + UserDTO userDTO = JSON.parseObject(tokenValue, UserDTO.class); + if (roleType != null) { + userDTO.setRoleType(roleType); + } + if (!Strings.isBlank(nickname)) { + userDTO.setNickname(nickname); + } + if (userStatus != null) { + userDTO.setUserStatus(userStatus); + } + redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(userDTO), expire, TimeUnit.SECONDS); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/WebSideMessageServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/WebSideMessageServiceImpl.java new file mode 100644 index 0000000..403fb43 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/WebSideMessageServiceImpl.java @@ -0,0 +1,200 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Objects1; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.message.MessageType; +import cn.orionsec.ops.constant.message.ReadStatus; +import cn.orionsec.ops.dao.WebSideMessageDAO; +import cn.orionsec.ops.entity.domain.WebSideMessageDO; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.entity.request.message.WebSideMessageRequest; +import cn.orionsec.ops.entity.vo.message.WebSideMessagePollVO; +import cn.orionsec.ops.entity.vo.message.WebSideMessageVO; +import cn.orionsec.ops.service.api.WebSideMessageService; +import cn.orionsec.ops.utils.Currents; +import cn.orionsec.ops.utils.DataQuery; +import cn.orionsec.ops.utils.Valid; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +@Service("webSideMessageService") +public class WebSideMessageServiceImpl implements WebSideMessageService { + + @Resource + private WebSideMessageDAO webSideMessageDAO; + + @Override + public Integer getUnreadCount() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(WebSideMessageDO::getToUserId, Currents.getUserId()) + .eq(WebSideMessageDO::getReadStatus, ReadStatus.UNREAD.getStatus()); + return webSideMessageDAO.selectCount(wrapper); + } + + @Override + public Integer setAllRead() { + WebSideMessageDO update = new WebSideMessageDO(); + update.setReadStatus(ReadStatus.READ.getStatus()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(WebSideMessageDO::getToUserId, Currents.getUserId()) + .eq(WebSideMessageDO::getReadStatus, ReadStatus.UNREAD.getStatus()); + return webSideMessageDAO.update(update, wrapper); + } + + @Override + public Integer readMessage(List idList) { + WebSideMessageDO update = new WebSideMessageDO(); + update.setReadStatus(ReadStatus.READ.getStatus()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(WebSideMessageDO::getToUserId, Currents.getUserId()) + .in(WebSideMessageDO::getId, idList) + .eq(WebSideMessageDO::getReadStatus, ReadStatus.UNREAD.getStatus()); + return webSideMessageDAO.update(update, wrapper); + } + + @Override + public Integer deleteAllRead() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(WebSideMessageDO::getToUserId, Currents.getUserId()) + .eq(WebSideMessageDO::getReadStatus, ReadStatus.READ.getStatus()); + return webSideMessageDAO.delete(wrapper); + } + + @Override + public Integer deleteMessage(List idList) { + return webSideMessageDAO.deleteBatchIds(idList); + } + + @Override + public WebSideMessageVO getMessageDetail(Long id) { + WebSideMessageDO message = webSideMessageDAO.selectById(id); + Valid.notNull(message, MessageConst.UNKNOWN_DATA); + // 设为已读 + if (ReadStatus.UNREAD.getStatus().equals(message.getReadStatus())) { + WebSideMessageDO update = new WebSideMessageDO(); + update.setId(message.getId()); + update.setReadStatus(ReadStatus.READ.getStatus()); + webSideMessageDAO.updateById(update); + } + return Converts.to(message, WebSideMessageVO.class); + } + + @Override + public DataGrid getMessageList(WebSideMessageRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(WebSideMessageDO::getToUserId, Currents.getUserId()) + .eq(Objects.nonNull(request.getClassify()), WebSideMessageDO::getMessageClassify, request.getClassify()) + .eq(Objects.nonNull(request.getType()), WebSideMessageDO::getMessageType, request.getType()) + .eq(Objects.nonNull(request.getStatus()), WebSideMessageDO::getReadStatus, request.getStatus()) + .like(Strings.isNotBlank(request.getMessage()), WebSideMessageDO::getSendMessage, request.getMessage()) + .between(Objects1.isNoneNull(request.getRangeStart(), request.getRangeEnd()), WebSideMessageDO::getCreateTime, + request.getRangeStart(), request.getRangeEnd()) + .orderByDesc(WebSideMessageDO::getId); + return DataQuery.of(webSideMessageDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(WebSideMessageVO.class); + } + + @Override + public WebSideMessagePollVO getNewMessage(WebSideMessageRequest request) { + WebSideMessagePollVO vo = new WebSideMessagePollVO(); + vo.setUnreadCount(this.getUnreadCount()); + // 查询列表 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(WebSideMessageDO::getToUserId, Currents.getUserId()) + .eq(Objects.nonNull(request.getStatus()), WebSideMessageDO::getReadStatus, request.getStatus()) + .gt(Objects.nonNull(request.getMaxId()), WebSideMessageDO::getId, request.getMaxId()) + .orderByDesc(WebSideMessageDO::getId); + List list = DataQuery.of(webSideMessageDAO) + .wrapper(wrapper) + .list(WebSideMessageVO.class); + vo.setNewMessages(list); + return vo; + } + + @Override + public List getMoreMessage(WebSideMessageRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(WebSideMessageDO::getToUserId, Currents.getUserId()) + .eq(Objects.nonNull(request.getStatus()), WebSideMessageDO::getReadStatus, request.getStatus()) + .lt(WebSideMessageDO::getId, request.getMaxId()) + .orderByDesc(WebSideMessageDO::getId) + .last(Const.LIMIT + Const.SPACE + request.getLimit()); + return DataQuery.of(webSideMessageDAO) + .wrapper(wrapper) + .list(WebSideMessageVO.class); + } + + @Override + public WebSideMessagePollVO pollWebSideMessage(Long maxId) { + WebSideMessagePollVO vo = new WebSideMessagePollVO(); + Long userId = Currents.getUserId(); + // 未读数量 + vo.setUnreadCount(this.getUnreadCount()); + if (maxId == null) { + // 没传则代表第一次访问接口 获取最大id + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(WebSideMessageDO::getToUserId, userId) + .eq(WebSideMessageDO::getReadStatus, ReadStatus.UNREAD.getStatus()) + .orderByDesc(WebSideMessageDO::getId) + .last(Const.LIMIT_1); + WebSideMessageDO lastMessage = webSideMessageDAO.selectOne(wrapper); + if (lastMessage == null) { + vo.setMaxId(0L); + } else { + vo.setMaxId(lastMessage.getId()); + } + } else { + // 传了则设置新消息 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(WebSideMessageDO::getToUserId, userId) + .eq(WebSideMessageDO::getReadStatus, ReadStatus.UNREAD.getStatus()) + .gt(WebSideMessageDO::getId, maxId); + List newMessages = webSideMessageDAO.selectList(wrapper); + if (newMessages.isEmpty()) { + vo.setMaxId(maxId); + } else { + vo.setMaxId(Lists.last(newMessages).getId()); + vo.setNewMessages(Converts.toList(newMessages, WebSideMessageVO.class)); + } + } + return vo; + } + + @Override + public void addMessage(MessageType type, Long relId, Map params) { + UserDTO user = Currents.getUser(); + this.addMessage(type, relId, user.getId(), user.getUsername(), params); + } + + @Override + public void addMessage(MessageType type, Long relId, Long userId, String username, Map params) { + WebSideMessageDO message = new WebSideMessageDO(); + message.setMessageClassify(type.getClassify().getClassify()); + message.setMessageType(type.getType()); + message.setReadStatus(ReadStatus.UNREAD.getStatus()); + message.setToUserId(userId); + message.setToUserName(username); + message.setSendMessage(Strings.format(type.getTemplate(), params)); + message.setRelId(relId); + message.setDeleted(Const.NOT_DELETED); + Date now = new Date(); + message.setCreateTime(now); + message.setUpdateTime(now); + webSideMessageDAO.insert(message); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/WebhookConfigServiceImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/WebhookConfigServiceImpl.java new file mode 100644 index 0000000..7ef6e99 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/service/impl/WebhookConfigServiceImpl.java @@ -0,0 +1,125 @@ + +package cn.orionsec.ops.service.impl; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.dao.WebhookConfigDAO; +import cn.orionsec.ops.entity.domain.WebhookConfigDO; +import cn.orionsec.ops.entity.request.webhook.WebhookConfigRequest; +import cn.orionsec.ops.entity.vo.webhook.WebhookConfigVO; +import cn.orionsec.ops.service.api.AlarmGroupNotifyService; +import cn.orionsec.ops.service.api.WebhookConfigService; +import cn.orionsec.ops.utils.DataQuery; +import cn.orionsec.ops.utils.EventParamsHolder; +import cn.orionsec.ops.utils.Valid; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.Objects; + +@Service("webhookConfigService") +public class WebhookConfigServiceImpl implements WebhookConfigService { + + @Resource + private WebhookConfigDAO webhookConfigDAO; + + @Resource + private AlarmGroupNotifyService alarmGroupNotifyService; + + @Override + public DataGrid getWebhookList(WebhookConfigRequest request) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(Objects.nonNull(request.getId()), WebhookConfigDO::getId, request.getId()) + .eq(Objects.nonNull(request.getType()), WebhookConfigDO::getWebhookType, request.getType()) + .like(Strings.isNotBlank(request.getUrl()), WebhookConfigDO::getWebhookUrl, request.getUrl()) + .like(Strings.isNotBlank(request.getName()), WebhookConfigDO::getWebhookName, request.getName()); + return DataQuery.of(webhookConfigDAO) + .page(request) + .wrapper(wrapper) + .dataGrid(WebhookConfigVO.class); + } + + @Override + public WebhookConfigVO getWebhookDetail(Long id) { + // 查询 + WebhookConfigDO config = webhookConfigDAO.selectById(id); + Valid.notNull(config, MessageConst.WEBHOOK_ABSENT); + return Converts.to(config, WebhookConfigVO.class); + } + + @Override + public Long addWebhook(WebhookConfigRequest request) { + // 检查名称是否重复 + String name = request.getName(); + this.checkNamePresent(null, name); + // 插入 + WebhookConfigDO insert = new WebhookConfigDO(); + insert.setWebhookName(name); + insert.setWebhookUrl(request.getUrl()); + insert.setWebhookType(request.getType()); + insert.setWebhookConfig(request.getConfig()); + webhookConfigDAO.insert(insert); + // 设置日志参数 + EventParamsHolder.addParams(insert); + return insert.getId(); + } + + @Override + public Integer updateWebhook(WebhookConfigRequest request) { + Long id = request.getId(); + String name = request.getName(); + // 查询 + WebhookConfigDO config = webhookConfigDAO.selectById(id); + Valid.notNull(config, MessageConst.WEBHOOK_ABSENT); + // 检查名称是否重复 + this.checkNamePresent(id, name); + // 更新 + WebhookConfigDO update = new WebhookConfigDO(); + update.setId(id); + update.setWebhookName(name); + update.setWebhookUrl(request.getUrl()); + update.setWebhookType(request.getType()); + update.setWebhookConfig(request.getConfig()); + int effect = webhookConfigDAO.updateById(update); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.NAME, config.getWebhookName()); + EventParamsHolder.addParams(update); + return effect; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Integer deleteWebhook(Long id) { + // 查询 + WebhookConfigDO config = webhookConfigDAO.selectById(id); + Valid.notNull(config, MessageConst.WEBHOOK_ABSENT); + // 删除 + int effect = webhookConfigDAO.deleteById(id); + // 删除报警组通知方式 + effect += alarmGroupNotifyService.deleteByWebhookId(id); + EventParamsHolder.addParam(EventKeys.ID, id); + EventParamsHolder.addParam(EventKeys.NAME, config.getWebhookName()); + // 设置日志参数 + return effect; + } + + /** + * 检查是否存在 + * + * @param id id + * @param name name + */ + private void checkNamePresent(Long id, String name) { + LambdaQueryWrapper presentWrapper = new LambdaQueryWrapper() + .ne(id != null, WebhookConfigDO::getId, id) + .eq(WebhookConfigDO::getWebhookName, name); + boolean present = DataQuery.of(webhookConfigDAO).wrapper(presentWrapper).present(); + Valid.isTrue(!present, MessageConst.NAME_PRESENT); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/TaskRegister.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/TaskRegister.java new file mode 100644 index 0000000..a15596d --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/TaskRegister.java @@ -0,0 +1,104 @@ + +package cn.orionsec.ops.task; + +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.ops.constant.MessageConst; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.support.CronTrigger; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.Map; + +@Component +public class TaskRegister implements DisposableBean { + + private final Map taskMap = Maps.newCurrentHashMap(); + + @Resource + @Qualifier("taskScheduler") + private TaskScheduler scheduler; + + /** + * 提交任务 + * + * @param type type + * @param time time + * @param params params + */ + public void submit(TaskType type, Date time, Object params) { + // 获取任务 + TimedTask timedTask = this.getTask(type, params); + // 执行任务 + timedTask.submit(scheduler, time); + } + + /** + * 提交任务 + * + * @param type type + * @param cron cron + * @param params params + */ + public void submit(TaskType type, String cron, Object params) { + // 获取任务 + TimedTask timedTask = this.getTask(type, params); + // 执行任务 + timedTask.submit(scheduler, new CronTrigger(cron)); + } + + /** + * 获取任务 + * + * @param type type + * @param params params + */ + private TimedTask getTask(TaskType type, Object params) { + // 生成任务 + Runnable runnable = type.create(params); + String key = type.getKey(params); + // 判断是否存在任务 + if (taskMap.containsKey(key)) { + throw Exceptions.init(MessageConst.TASK_PRESENT); + } + TimedTask timedTask = new TimedTask(runnable); + taskMap.put(key, timedTask); + return timedTask; + } + + /** + * 取消任务 + * + * @param type type + * @param params params + */ + public void cancel(TaskType type, Object params) { + String key = type.getKey(params); + TimedTask task = taskMap.get(key); + if (task != null) { + taskMap.remove(key); + task.cancel(); + } + } + + /** + * 是否存在 + * + * @param type type + * @param params params + */ + public boolean has(TaskType type, Object params) { + return taskMap.containsKey(type.getKey(params)); + } + + @Override + public void destroy() { + taskMap.values().forEach(TimedTask::cancel); + taskMap.clear(); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/TaskType.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/TaskType.java new file mode 100644 index 0000000..b72e498 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/TaskType.java @@ -0,0 +1,67 @@ + +package cn.orionsec.ops.task; + +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.task.impl.PipelineTaskImpl; +import cn.orionsec.ops.task.impl.ReleaseTaskImpl; +import cn.orionsec.ops.task.impl.SchedulerTaskImpl; +import lombok.AllArgsConstructor; + +import java.util.function.Function; + +@AllArgsConstructor +public enum TaskType { + + /** + * 发布任务 + */ + RELEASE(id -> new ReleaseTaskImpl((Long) id)) { + @Override + public String getKey(Object params) { + return Const.RELEASE + "-" + params; + } + }, + + /** + * 调度任务 + */ + SCHEDULER_TASK(id -> new SchedulerTaskImpl((Long) id)) { + @Override + public String getKey(Object params) { + return Const.TASK + "-" + params; + } + }, + + /** + * 流水线任务 + */ + PIPELINE(id -> new PipelineTaskImpl((Long) id)) { + @Override + public String getKey(Object params) { + return Const.PIPELINE + "-" + params; + } + }, + + ; + + private final Function factory; + + /** + * 创建任务 + * + * @param params params + * @return task + */ + public Runnable create(Object params) { + return factory.apply(params); + } + + /** + * 获取 key + * + * @param params params + * @return key + */ + public abstract String getKey(Object params); + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/TimedTask.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/TimedTask.java new file mode 100644 index 0000000..a0a1681 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/TimedTask.java @@ -0,0 +1,55 @@ + +package cn.orionsec.ops.task; + +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.Trigger; + +import java.util.Date; +import java.util.concurrent.ScheduledFuture; + +public class TimedTask { + + /** + * 任务 + */ + private final Runnable runnable; + + /** + * 异步执行 + */ + private volatile ScheduledFuture future; + + public TimedTask(Runnable runnable) { + this.runnable = runnable; + } + + /** + * 提交任务 一次性 + * + * @param scheduler scheduler + * @param time time + */ + public void submit(TaskScheduler scheduler, Date time) { + this.future = scheduler.schedule(runnable, time); + } + + /** + * 提交任务 cron表达式 + * + * @param trigger trigger + * @param scheduler scheduler + */ + public void submit(TaskScheduler scheduler, Trigger trigger) { + this.future = scheduler.schedule(runnable, trigger); + } + + /** + * 取消定时任务 + */ + public void cancel() { + if (future != null) { + future.cancel(true); + } + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/fixed/AutoCleanFileTask.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/fixed/AutoCleanFileTask.java new file mode 100644 index 0000000..fe86b82 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/fixed/AutoCleanFileTask.java @@ -0,0 +1,26 @@ + +package cn.orionsec.ops.task.fixed; + +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.ops.constant.common.EnableType; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.utils.FileCleaner; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class AutoCleanFileTask { + + @Scheduled(cron = "0 30 1 * * ?") + private void cleanHandler() { + if (!EnableType.of(SystemEnvAttr.ENABLE_AUTO_CLEAN_FILE.getValue()).getValue()) { + return; + } + log.info("自动清理文件-开始 {}", Dates.current()); + FileCleaner.cleanAll(); + log.info("自动清理文件-结束 {}", Dates.current()); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/fixed/SystemSpaceAnalysisTask.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/fixed/SystemSpaceAnalysisTask.java new file mode 100644 index 0000000..21a86ff --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/fixed/SystemSpaceAnalysisTask.java @@ -0,0 +1,27 @@ + +package cn.orionsec.ops.task.fixed; + +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.ops.service.api.SystemService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +@Slf4j +@Component +public class SystemSpaceAnalysisTask { + + @Resource + private SystemService systemService; + + @Scheduled(cron = "0 0 0,1,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23 * * ?") + private void analysisSystemSpace() { + log.info("task-执行占用磁盘空间统计-开始 {}", Dates.current()); + // 不考虑多线程计算 + systemService.analysisSystemSpace(); + log.info("task-执行占用磁盘空间统计-结束 {}", Dates.current()); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/fixed/TerminalHeartbeatDownChecker.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/fixed/TerminalHeartbeatDownChecker.java new file mode 100644 index 0000000..19fa4c3 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/fixed/TerminalHeartbeatDownChecker.java @@ -0,0 +1,33 @@ + +package cn.orionsec.ops.task.fixed; + +import cn.orionsec.ops.handler.terminal.manager.TerminalSessionManager; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +@Slf4j +@Component +public class TerminalHeartbeatDownChecker { + + @Resource + private TerminalSessionManager terminalSessionManager; + + @Scheduled(cron = "0 */1 * * * ?") + private void checkHeartbeat() { + terminalSessionManager.getSessionHolder().forEach((k, v) -> { + if (!v.isDown()) { + return; + } + log.info("terminal 心跳检查down token: {}", k); + try { + v.heartbeatDownClose(); + } catch (Exception e) { + log.error("terminal 心跳检查断连失败 token: {}", k, e); + } + }); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/fixed/TerminalHeartbeatPusher.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/fixed/TerminalHeartbeatPusher.java new file mode 100644 index 0000000..21a7c6b --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/fixed/TerminalHeartbeatPusher.java @@ -0,0 +1,36 @@ + +package cn.orionsec.ops.task.fixed; + +import cn.orionsec.ops.constant.common.EnableType; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.handler.terminal.manager.TerminalSessionManager; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +@Slf4j +@Component +public class TerminalHeartbeatPusher { + + @Resource + private TerminalSessionManager terminalSessionManager; + + @Scheduled(cron = "*/30 * * * * ?") + private void checkHeartbeat() { + EnableType type = EnableType.of(SystemEnvAttr.TERMINAL_ACTIVE_PUSH_HEARTBEAT.getValue()); + if (!type.getValue()) { + return; + } + terminalSessionManager.getSessionHolder().forEach((k, v) -> { + log.info("terminal 开始主动发送心跳 token: {}", k); + try { + v.sendHeartbeat(); + } catch (Exception e) { + log.error("terminal 主动发送心跳失败 token: {} {}", k, e); + } + }); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/impl/PipelineTaskImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/impl/PipelineTaskImpl.java new file mode 100644 index 0000000..c080cd3 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/impl/PipelineTaskImpl.java @@ -0,0 +1,26 @@ + +package cn.orionsec.ops.task.impl; + +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.service.api.ApplicationPipelineTaskService; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class PipelineTaskImpl implements Runnable { + + protected static ApplicationPipelineTaskService applicationPipelineTaskService = SpringHolder.getBean(ApplicationPipelineTaskService.class); + + private final Long pipelineRecordId; + + public PipelineTaskImpl(Long pipelineRecordId) { + this.pipelineRecordId = pipelineRecordId; + } + + @Override + public void run() { + log.info("定时执行流水线任务-触发 releaseId: {}, time: {}", pipelineRecordId, Dates.current()); + applicationPipelineTaskService.execPipeline(pipelineRecordId, true); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/impl/ReleaseTaskImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/impl/ReleaseTaskImpl.java new file mode 100644 index 0000000..4fdc488 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/impl/ReleaseTaskImpl.java @@ -0,0 +1,26 @@ + +package cn.orionsec.ops.task.impl; + +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.service.api.ApplicationReleaseService; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ReleaseTaskImpl implements Runnable { + + protected static ApplicationReleaseService applicationReleaseService = SpringHolder.getBean(ApplicationReleaseService.class); + + private final Long releaseId; + + public ReleaseTaskImpl(Long releaseId) { + this.releaseId = releaseId; + } + + @Override + public void run() { + log.info("定时执行发布任务-触发 releaseId: {}, time: {}", releaseId, Dates.current()); + applicationReleaseService.runnableAppRelease(releaseId, true, true); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/impl/SchedulerTaskImpl.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/impl/SchedulerTaskImpl.java new file mode 100644 index 0000000..0caf8ab --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/task/impl/SchedulerTaskImpl.java @@ -0,0 +1,41 @@ + +package cn.orionsec.ops.task.impl; + +import cn.orionsec.kit.lang.utils.Valid; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.kit.spring.SpringHolder; +import cn.orionsec.ops.constant.SchedulerPools; +import cn.orionsec.ops.constant.common.SerialType; +import cn.orionsec.ops.dao.SchedulerTaskDAO; +import cn.orionsec.ops.entity.domain.SchedulerTaskDO; +import cn.orionsec.ops.handler.scheduler.ITaskProcessor; +import cn.orionsec.ops.service.api.SchedulerTaskRecordService; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SchedulerTaskImpl implements Runnable { + + private static final SchedulerTaskDAO schedulerTaskDAO = SpringHolder.getBean(SchedulerTaskDAO.class); + + private static final SchedulerTaskRecordService schedulerTaskRecordService = SpringHolder.getBean(SchedulerTaskRecordService.class); + + private final Long id; + + public SchedulerTaskImpl(Long id) { + this.id = id; + } + + @Override + public void run() { + log.info("定制调度任务-触发 taskId: {}, time: {}", id, Dates.current()); + // 查询任务 + SchedulerTaskDO task = schedulerTaskDAO.selectById(id); + Valid.notNull(task); + // 创建任务 + Long recordId = schedulerTaskRecordService.createTaskRecord(id); + // 执行任务 + ITaskProcessor processor = ITaskProcessor.with(recordId, SerialType.of(task.getSerializeType())); + SchedulerPools.SCHEDULER_TASK_MAIN_SCHEDULER.execute(processor); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/utils/Currents.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/utils/Currents.java new file mode 100644 index 0000000..81c46ef --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/utils/Currents.java @@ -0,0 +1,96 @@ + +package cn.orionsec.ops.utils; + +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.ops.constant.PropertiesConst; +import cn.orionsec.ops.constant.ResultCode; +import cn.orionsec.ops.constant.user.RoleType; +import cn.orionsec.ops.entity.dto.user.UserDTO; + +import javax.servlet.http.HttpServletRequest; +import java.util.Optional; + +public class Currents { + + private Currents() { + } + + /** + * 获取当前登录token + * + * @param request request + * @return token + */ + public static String getLoginToken(HttpServletRequest request) { + return getToken(request, PropertiesConst.LOGIN_TOKEN_HEADER); + } + + /** + * 获取token + * + * @param request request + * @param token tokenKey + * @return token + */ + public static String getToken(HttpServletRequest request, String token) { + return request.getHeader(token); + } + + /** + * 获取当前登录用户 + *

+ * 可以匿名登录的接口并且用户未登录获取的是null + * + * @return 用户 + */ + public static UserDTO getUser() { + return UserHolder.get(); + } + + /** + * 获取当前登录用户id + * + * @return id + */ + public static Long getUserId() { + return Optional.ofNullable(UserHolder.get()) + .map(UserDTO::getId) + .orElse(null); + } + + /** + * 获取当前登录用户username + * + * @return username + */ + public static String getUserName() { + return Optional.ofNullable(UserHolder.get()) + .map(UserDTO::getUsername) + .orElse(null); + } + + /** + * 是否为 管理员 + * + * @return true 管理员 + */ + public static boolean isAdministrator() { + UserDTO user = UserHolder.get(); + if (user == null) { + return false; + } + Integer roleType = user.getRoleType(); + return RoleType.isAdministrator(roleType); + } + + /** + * 检查是否为管理员权限 + */ + public static void requireAdministrator() { + if (!isAdministrator()) { + throw Exceptions.httpWrapper(HttpWrapper.of(ResultCode.NO_PERMISSION)); + } + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/utils/EventParamsHolder.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/utils/EventParamsHolder.java new file mode 100644 index 0000000..ff33cfe --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/utils/EventParamsHolder.java @@ -0,0 +1,91 @@ + +package cn.orionsec.ops.utils; + +import cn.orionsec.kit.lang.define.collect.MutableMap; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.kit.lang.utils.reflect.BeanMap; +import cn.orionsec.kit.lang.utils.time.Dates; +import cn.orionsec.kit.web.servlet.web.Servlets; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.interceptor.LogPrintInterceptor; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.util.Optional; + +public class EventParamsHolder { + + /** + * 参数 + */ + private static final ThreadLocal> PARAMS = ThreadLocal.withInitial(Maps::newMutableLinkedMap); + + public static MutableMap get() { + return PARAMS.get(); + } + + public static void set(MutableMap user) { + PARAMS.set(user); + } + + public static void remove() { + PARAMS.remove(); + } + + /** + * 设置参数 + * + * @param key key + * @param value value + */ + public static void addParam(String key, Object value) { + PARAMS.get().put(key, value); + } + + /** + * 设置参数 + * + * @param value value + */ + public static void addParams(Object value) { + if (value == null) { + return; + } + PARAMS.get().putAll(BeanMap.create(value)); + } + + /** + * 设置是否保存 + * + * @param save 是否保存 + */ + public static void setSave(boolean save) { + PARAMS.get().put(EventKeys.INNER_SAVE, save); + } + + /** + * 设置默认参数 + */ + public static void setDefaultEventParams() { + // 请求时间 + EventParamsHolder.addParam(EventKeys.INNER_REQUEST_TIME, Dates.current()); + // 登录接口为空 + UserDTO user = Currents.getUser(); + if (user != null) { + EventParamsHolder.addParam(EventKeys.INNER_USER_ID, user.getId()); + EventParamsHolder.addParam(EventKeys.INNER_USER_NAME, user.getUsername()); + } + // 请求序列 + EventParamsHolder.addParam(EventKeys.INNER_REQUEST_SEQ, LogPrintInterceptor.SEQ_HOLDER.get()); + // 请求信息 + Optional.ofNullable(RequestContextHolder.getRequestAttributes()) + .map(s -> (ServletRequestAttributes) s) + .map(ServletRequestAttributes::getRequest) + .ifPresent(request -> { + EventParamsHolder.addParam(EventKeys.INNER_REQUEST_USER_AGENT, Servlets.getUserAgent(request)); + EventParamsHolder.addParam(EventKeys.INNER_REQUEST_IP, Servlets.getRemoteAddr(request)); + }); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/utils/RedisUtils.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/utils/RedisUtils.java new file mode 100644 index 0000000..1cb8b45 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/utils/RedisUtils.java @@ -0,0 +1,74 @@ + +package cn.orionsec.ops.utils; + +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.io.Streams; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.KeyConst; +import org.springframework.data.redis.core.Cursor; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ScanOptions; + +import java.util.HashSet; +import java.util.Set; + +public class RedisUtils { + + private RedisUtils() { + } + + /** + * 扫描key + * + * @param redisTemplate redisTemplate + * @param match 匹配值 + * @param count 数量 + * @return keys + */ + public static Set scanKeys(RedisTemplate redisTemplate, String match, int count) { + return scanKeys(redisTemplate, ScanOptions.scanOptions() + .match(match) + .count(count) + .build()); + } + + /** + * 扫描key + * + * @param redisTemplate redisTemplate + * @param scanOptions scan + * @return keys + */ + public static Set scanKeys(RedisTemplate redisTemplate, ScanOptions scanOptions) { + return redisTemplate.execute((RedisCallback>) connection -> { + Set keys = new HashSet<>(); + Cursor cursor = connection.scan(scanOptions); + while (cursor.hasNext()) { + keys.add(new String(cursor.next())); + } + Streams.close(cursor); + return keys; + }); + } + + /** + * 删除用户登录&绑定 token + * + * @param userId userId + */ + public static void deleteLoginToken(RedisTemplate redisTemplate, Long userId) { + // 删除登录 token + redisTemplate.delete(Strings.format(KeyConst.LOGIN_TOKEN_KEY, userId)); + // 删除绑定 token + String scanMatches = Strings.format(KeyConst.LOGIN_TOKEN_BIND_KEY, userId, "*"); + Set bindTokens = scanKeys(redisTemplate, scanMatches, Const.N_10000); + if (!Lists.isEmpty(bindTokens)) { + for (String bindToken : bindTokens) { + redisTemplate.delete(bindToken); + } + } + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/utils/UserHolder.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/utils/UserHolder.java new file mode 100644 index 0000000..6a1e7c0 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/utils/UserHolder.java @@ -0,0 +1,22 @@ + +package cn.orionsec.ops.utils; + +import cn.orionsec.ops.entity.dto.user.UserDTO; + +public class UserHolder { + + private static final ThreadLocal LOCAL = new ThreadLocal<>(); + + public static UserDTO get() { + return LOCAL.get(); + } + + public static void set(UserDTO user) { + LOCAL.set(user); + } + + public static void remove() { + LOCAL.remove(); + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/utils/WebSockets.java b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/utils/WebSockets.java new file mode 100644 index 0000000..3b794e0 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/java/cn/orionsec/ops/utils/WebSockets.java @@ -0,0 +1,149 @@ + +package cn.orionsec.ops.utils; + +import cn.orionsec.kit.lang.define.wrapper.Tuple; +import cn.orionsec.kit.lang.exception.AuthenticationException; +import cn.orionsec.kit.lang.exception.ConnectionRuntimeException; +import cn.orionsec.kit.lang.exception.DisabledException; +import cn.orionsec.kit.lang.exception.TimeoutException; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Urls; +import cn.orionsec.ops.constant.terminal.TerminalClientOperate; +import cn.orionsec.ops.constant.ws.WsCloseCode; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +import java.io.IOException; +import java.util.Objects; + +@Slf4j +public class WebSockets { + + public static final String MID = "mid"; + + public static final String UID = "uid"; + + public static final String TOKEN = "token"; + + public static final String READONLY = "readonly"; + + public static final String CONNECTED = "connected"; + + public static final String AUTHED = "authed"; + + public static final String CONFIG = "config"; + + private WebSockets() { + } + + /** + * 解析请求 + *

+ * .e.g xx + * .e.g xx|body + * + * @param payload payload + * @return operator, body + */ + public static Tuple parsePayload(String payload) { + // 检查长度 + if (payload.length() < TerminalClientOperate.PREFIX_SIZE) { + return null; + } + // 解析操作 + TerminalClientOperate operate = TerminalClientOperate.of(payload.substring(0, TerminalClientOperate.PREFIX_SIZE)); + if (operate == null) { + return null; + } + if (!operate.isHasBody()) { + return Tuple.of(operate, null); + } + // 检查是否有body + if (payload.length() < TerminalClientOperate.PREFIX_SIZE + 1) { + return null; + } + return Tuple.of(operate, payload.substring(TerminalClientOperate.PREFIX_SIZE + 1)); + } + + /** + * 发送消息 忽略并发报错 + * + * @param session session + * @param message message + */ + public static void sendText(WebSocketSession session, byte[] message) { + if (!session.isOpen()) { + return; + } + try { + // 响应 + session.sendMessage(new TextMessage(message)); + } catch (IllegalStateException e) { + // 并发异常 + log.error("terminal 读取流发生并发 {}", Exceptions.getDigest(e)); + } catch (IOException e) { + throw Exceptions.ioRuntime(e); + } + } + + /** + * 关闭会话 + * + * @param session session + * @param code code + */ + public static void close(WebSocketSession session, WsCloseCode code) { + if (!session.isOpen()) { + return; + } + try { + session.close(new CloseStatus(code.getCode(), code.getReason())); + } catch (Exception e) { + log.error("websocket close failure", e); + } + } + + /** + * 获取 urlToken + * + * @param request request + * @return token + */ + public static String getToken(ServerHttpRequest request) { + return Urls.getUrlSource(Objects.requireNonNull(request.getURI().toString())); + } + + /** + * 获取 urlToken + * + * @param session session + * @return token + */ + public static String getToken(WebSocketSession session) { + return Urls.getUrlSource(Objects.requireNonNull(session.getUri()).toString()); + } + + /** + * 打开 session 异常关闭 + * + * @param session session + * @param e e + */ + public static void openSessionStoreThrowClose(WebSocketSession session, Exception e) { + if (Exceptions.isCausedBy(e, TimeoutException.class)) { + close(session, WsCloseCode.CONNECTION_TIMEOUT); + } else if (Exceptions.isCausedBy(e, ConnectionRuntimeException.class)) { + close(session, WsCloseCode.CONNECTION_FAILURE); + } else if (Exceptions.isCausedBy(e, AuthenticationException.class)) { + close(session, WsCloseCode.CONNECTION_AUTH_FAILURE); + } else if (Exceptions.isCausedBy(e, DisabledException.class)) { + close(session, WsCloseCode.MACHINE_DISABLED); + } else { + close(session, WsCloseCode.CONNECTION_EXCEPTION); + } + } + +} diff --git a/orion-ops-api/orion-ops-service/src/main/resources/templates/push/machine-alarm-ding.template b/orion-ops-api/orion-ops-service/src/main/resources/templates/push/machine-alarm-ding.template new file mode 100644 index 0000000..2ebb5d3 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/resources/templates/push/machine-alarm-ding.template @@ -0,0 +1,5 @@ +#### __报警机器:__ ${name} +#### __报警主机:__ ${host} +#### __报警内容:__ 当前${type}为 __${value}%__ +#### __报警时间:__ ${time} +#### __机器发生报警, 请及时排查处理!__ diff --git a/orion-ops-api/orion-ops-service/src/main/resources/templates/push/machine-alarm-webside.template b/orion-ops-api/orion-ops-service/src/main/resources/templates/push/machine-alarm-webside.template new file mode 100644 index 0000000..7e921ca --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/resources/templates/push/machine-alarm-webside.template @@ -0,0 +1,5 @@ +报警机器: ${machineName} +报警主机: ${machineHost} +报警内容: 当前${metrics}使用率为 ${usage}% +报警时间: ${time} +机器发生报警, 请及时排查处理! diff --git a/orion-ops-api/orion-ops-service/src/main/resources/templates/script/start-monitor-agent.sh b/orion-ops-api/orion-ops-service/src/main/resources/templates/script/start-monitor-agent.sh new file mode 100644 index 0000000..5db0c96 --- /dev/null +++ b/orion-ops-api/orion-ops-service/src/main/resources/templates/script/start-monitor-agent.sh @@ -0,0 +1,28 @@ +#!/bin/sh +AGENT_PROCESS=${processName} +STARTED=$(ps -ef | grep $AGENT_PROCESS | grep '.jar' | grep -v grep | wc -l) +PIDS=$(ps -ef | grep $AGENT_PROCESS | grep '.jar' | grep -v grep | awk '{print $2}') + +# KILL +if [ $STARTED -eq 0 ] +then + echo "Agent is not running." +else + echo "Killing agent with PID(s): $PIDS" + for PID in $PIDS; do + kill -9 $PID + done +fi +echo 'Agent starting...' + +# START +nohup java -jar -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -Xms128m -Xmx128m -Xmn32m -Xss512k -XX:SurvivorRatio=8 -XX:+UseG1GC ${agentJarPath} --machineId=${machineId} --spring.profiles.active=prod >/dev/null 2>&1 & + +# CHECK +sleep 2 +NEW_STARTED=$(ps -ef | grep $AGENT_PROCESS | grep '.jar' | grep -v grep | wc -l) +if [ $NEW_STARTED -eq 0 ]; then + echo "Failed to start agent." +else + echo "Agent started successfully." +fi diff --git a/orion-ops-api/orion-ops-web/pom.xml b/orion-ops-api/orion-ops-web/pom.xml new file mode 100644 index 0000000..6baaaf8 --- /dev/null +++ b/orion-ops-api/orion-ops-web/pom.xml @@ -0,0 +1,165 @@ + + + + cn.orionsec.ops + orion-ops-api + 1.3.1 + ../pom.xml + + + orion-ops-web + orion-ops-web + 4.0.0 + + + + + cn.orionsec.ops + orion-ops-service + ${project.version} + + + + + cn.orionsec.ops + orion-ops-data + ${project.version} + + + + + cn.orionsec.ops + orion-ops-mapping + ${project.version} + + + + + cn.orionsec.ops + orion-ops-runner + ${project.version} + + + + + org.aspectj + aspectjweaver + + + + + io.springfox + springfox-swagger-ui + + + + + com.github.xiaoymin + knife4j-micro-spring-boot-starter + + + com.github.xiaoymin + knife4j-spring-ui + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + ${project.artifactId} + + + src/main/resources + + ** + + + + + + + src/main/resources + + ** + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + UTF-8 + + + + + org.apache.maven.plugins + maven-source-plugin + + true + + + + compile + + jar + + + + + + + + diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/OrionApplication.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/OrionApplication.java new file mode 100644 index 0000000..eb7c04f --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/OrionApplication.java @@ -0,0 +1,17 @@ +package cn.orionsec.ops; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ImportResource; + +@SpringBootApplication +@ImportResource(locations = {"classpath:config/spring-*.xml"}) +@MapperScan("cn.orionsec.ops.dao") +public class OrionApplication { + + public static void main(String[] args) { + SpringApplication.run(OrionApplication.class, args); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/config/LogPrintConfig.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/config/LogPrintConfig.java new file mode 100644 index 0000000..6246088 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/config/LogPrintConfig.java @@ -0,0 +1,33 @@ + +package cn.orionsec.ops.config; + +import cn.orionsec.ops.interceptor.LogPrintInterceptor; +import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.Resource; + +@Configuration +@ConditionalOnBean(LogPrintInterceptor.class) +public class LogPrintConfig { + + @Value("${log.interceptor.expression:}") + private String logInterceptorExpression; + + @Resource + private LogPrintInterceptor logPrintInterceptor; + + @Bean + @ConditionalOnProperty(name = "log.interceptor.expression") + public AspectJExpressionPointcutAdvisor logAdvisor() { + AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor(); + advisor.setExpression(logInterceptorExpression); + advisor.setAdvice(logPrintInterceptor); + return advisor; + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/config/SwaggerConfig.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/config/SwaggerConfig.java new file mode 100644 index 0000000..1926451 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/config/SwaggerConfig.java @@ -0,0 +1,74 @@ + +package cn.orionsec.ops.config; + +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.PropertiesConst; +import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import springfox.documentation.builders.ApiInfoBuilder; +import springfox.documentation.builders.PathSelectors; +import springfox.documentation.builders.RequestHandlerSelectors; +import springfox.documentation.service.ApiInfo; +import springfox.documentation.service.ApiKey; +import springfox.documentation.service.Contact; +import springfox.documentation.service.SecurityScheme; +import springfox.documentation.spi.DocumentationType; +import springfox.documentation.spring.web.plugins.Docket; +import springfox.documentation.swagger2.annotations.EnableSwagger2; + +import java.util.List; + +@Configuration +@EnableSwagger2 +@EnableKnife4j +@Profile({"dev"}) +public class SwaggerConfig { + + @Value("${login.token.header}") + private String loginTokenHeader; + + @Value("${expose.api.access.header}") + private String accessTokenHeader; + + @Bean + public Docket createRestApi() { + return new Docket(DocumentationType.SWAGGER_2) + .apiInfo(this.getApiInfo()) + .securitySchemes(this.getSecuritySchemes()) + .enable(true) + .select() + .apis(RequestHandlerSelectors.basePackage("cn.orionsec.ops")) + .paths(PathSelectors.any()) + .build(); + } + + /** + * 配置 api 信息 + * + * @return api info + */ + private ApiInfo getApiInfo() { + return new ApiInfoBuilder() + .title("orion-ops restful API") + .contact(new Contact(Const.ORION_AUTHOR, Const.ORION_GITEE, Const.ORION_EMAIL)) + .version(PropertiesConst.ORION_OPS_VERSION) + .description("orion-ops api 管理") + .build(); + } + + /** + * 认证配置 + * + * @return security scheme + */ + private List getSecuritySchemes() { + ApiKey loginToken = new ApiKey(loginTokenHeader, loginTokenHeader, "header"); + ApiKey accessToken = new ApiKey(accessTokenHeader, accessTokenHeader, "header"); + return Lists.of(loginToken, accessToken); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/config/WebMvcConfig.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/config/WebMvcConfig.java new file mode 100644 index 0000000..6fe07e0 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/config/WebMvcConfig.java @@ -0,0 +1,247 @@ + +package cn.orionsec.ops.config; + +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.kit.lang.exception.*; +import cn.orionsec.kit.lang.exception.argument.CodeArgumentException; +import cn.orionsec.kit.lang.exception.argument.HttpWrapperException; +import cn.orionsec.kit.lang.exception.argument.InvalidArgumentException; +import cn.orionsec.kit.lang.exception.argument.RpcWrapperException; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.interceptor.*; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.EncryptedDocumentException; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.validation.BindException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.multipart.MaxUploadSizeExceededException; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.sql.SQLException; + +@Slf4j +@Configuration +@RestControllerAdvice +public class WebMvcConfig implements WebMvcConfigurer { + + @Resource + private IpFilterInterceptor ipFilterInterceptor; + + @Resource + private AuthenticateInterceptor authenticateInterceptor; + + @Resource + private RoleInterceptor roleInterceptor; + + @Resource + private UserActiveInterceptor userActiveInterceptor; + + @Resource + private ExposeApiHeaderInterceptor exposeApiHeaderInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + // IP拦截器 + registry.addInterceptor(ipFilterInterceptor) + .addPathPatterns("/**") + .order(5); + // 认证拦截器 + registry.addInterceptor(authenticateInterceptor) + .addPathPatterns("/orion/api/**") + .order(10); + // 权限拦截器 + registry.addInterceptor(roleInterceptor) + .addPathPatterns("/orion/api/**") + .order(20); + // 活跃拦截器 + registry.addInterceptor(userActiveInterceptor) + .addPathPatterns("/orion/api/**") + .excludePathPatterns("/orion/api/auth/**") + .order(30); + // 暴露服务请求头拦截器 + registry.addInterceptor(exposeApiHeaderInterceptor) + .addPathPatterns("/orion/expose-api/**") + .order(40); + } + + @Override + public void addCorsMappings(CorsRegistry registry) { + // 暴露服务允许跨域 + registry.addMapping("/orion/expose-api/**") + .allowCredentials(true) + .allowedOriginPatterns("*") + .allowedMethods("*") + .allowedHeaders("*") + .maxAge(3600); + } + + /** + * @return 演示模式禁用 api 切面 + */ + @Bean + @ConditionalOnProperty(value = "demo.mode", havingValue = "true") + public DemoDisableApiAspect demoDisableApiAspect() { + return new DemoDisableApiAspect(); + } + + @ExceptionHandler(value = Exception.class) + public HttpWrapper normalExceptionHandler(HttpServletRequest request, Exception ex) { + log.error("normalExceptionHandler url: {}, 抛出异常: {}, message: {}", request.getRequestURI(), ex.getClass(), ex.getMessage(), ex); + return HttpWrapper.error(MessageConst.EXCEPTION_MESSAGE).data(ex.getMessage()); + } + + @ExceptionHandler(value = ApplicationException.class) + public HttpWrapper applicationExceptionHandler(HttpServletRequest request, Exception ex) { + log.error("applicationExceptionHandler url: {}, 抛出异常: {}, message: {}", request.getRequestURI(), ex.getClass(), ex.getMessage(), ex); + return HttpWrapper.error(ex.getMessage()); + } + + @ExceptionHandler(value = DataAccessResourceFailureException.class) + public HttpWrapper dataAccessResourceFailureExceptionHandler(HttpServletRequest request, Exception ex) { + log.error("dataAccessResourceFailureExceptionHandler url: {}, 抛出异常: {}, message: {}", request.getRequestURI(), ex.getClass(), ex.getMessage(), ex); + return HttpWrapper.error(MessageConst.NETWORK_FLUCTUATION); + } + + @ExceptionHandler(value = {HttpMessageNotReadableException.class, MethodArgumentTypeMismatchException.class, + HttpMessageNotReadableException.class, MethodArgumentNotValidException.class, BindException.class}) + public HttpWrapper httpRequestExceptionHandler(HttpServletRequest request, Exception ex) { + log.error("httpRequestExceptionHandler url: {}, http请求异常: {}, message: {}", request.getRequestURI(), ex.getClass(), ex.getMessage(), ex); + return HttpWrapper.error(MessageConst.INVALID_PARAM); + } + + @ExceptionHandler(value = {HttpRequestException.class}) + public HttpWrapper httpApiRequestExceptionHandler(HttpServletRequest request, Exception ex) { + log.error("httpApiRequestExceptionHandler url: {}, http-api请求异常: {}, message: {}", request.getRequestURI(), ex.getClass(), ex.getMessage(), ex); + return HttpWrapper.error(MessageConst.HTTP_API); + } + + @ExceptionHandler(value = {InvalidArgumentException.class, IllegalArgumentException.class, DisabledException.class}) + public HttpWrapper invalidArgumentExceptionHandler(HttpServletRequest request, Exception ex) { + log.error("invalidArgumentExceptionHandler url: {}, 参数异常: {}, message: {}", request.getRequestURI(), ex.getClass(), ex.getMessage(), ex); + return HttpWrapper.error(ex.getMessage()); + } + + @ExceptionHandler(value = {IOException.class, IORuntimeException.class}) + public HttpWrapper ioExceptionHandler(HttpServletRequest request, Exception ex) { + log.error("ioExceptionHandler url: {}, io异常: {}, message: {}", request.getRequestURI(), ex.getClass(), ex.getMessage(), ex); + return HttpWrapper.error(MessageConst.IO_EXCEPTION_MESSAGE).data(ex.getMessage()); + } + + @ExceptionHandler(value = SQLException.class) + public HttpWrapper sqlExceptionHandler(HttpServletRequest request, Exception ex) { + log.error("sqlExceptionHandler url: {}, sql异常: {}, message: {}", request.getRequestURI(), ex.getClass(), ex.getMessage(), ex); + return HttpWrapper.error(MessageConst.SQL_EXCEPTION_MESSAGE); + } + + @ExceptionHandler(value = {SftpException.class, com.jcraft.jsch.SftpException.class}) + public HttpWrapper sftpExceptionHandler(HttpServletRequest request, Exception ex) { + log.error("sftpExceptionHandler url: {}, sftp处理异常: {}, message: {}", request.getRequestURI(), ex.getClass(), ex.getMessage(), ex); + return HttpWrapper.error(MessageConst.OPERATOR_ERROR).data(ex.getMessage()); + } + + @ExceptionHandler(value = ParseRuntimeException.class) + public HttpWrapper parseExceptionHandler(HttpServletRequest request, Exception ex) { + log.error("parseExceptionHandler url: {}, 解析异常: {}, message: {}", request.getRequestURI(), ex.getClass(), ex.getMessage(), ex); + if (Exceptions.isCausedBy(ex, EncryptedDocumentException.class)) { + // excel 密码错误 + return HttpWrapper.error(MessageConst.OPEN_TEMPLATE_ERROR).data(ex.getMessage()); + } else { + return HttpWrapper.error(MessageConst.PARSE_TEMPLATE_DATA_ERROR).data(ex.getMessage()); + } + } + + @ExceptionHandler(value = EncryptException.class) + public HttpWrapper encryptExceptionHandler(HttpServletRequest request, Exception ex) { + log.error("encryptExceptionHandler url: {}, 数据加密异常: {}, message: {}", request.getRequestURI(), ex.getClass(), ex.getMessage(), ex); + return HttpWrapper.error(MessageConst.ENCRYPT_ERROR).data(ex.getMessage()); + } + + @ExceptionHandler(value = DecryptException.class) + public HttpWrapper decryptExceptionHandler(HttpServletRequest request, Exception ex) { + log.error("decryptExceptionHandler url: {}, 数据解密异常: {}, message: {}", request.getRequestURI(), ex.getClass(), ex.getMessage(), ex); + return HttpWrapper.error(MessageConst.DECRYPT_ERROR).data(ex.getMessage()); + } + + @ExceptionHandler(value = VcsException.class) + public HttpWrapper vcsExceptionHandler(HttpServletRequest request, Exception ex) { + log.error("vcsExceptionHandler url: {}, vcs处理异常: {}, message: {}", request.getRequestURI(), ex.getClass(), ex.getMessage(), ex); + return HttpWrapper.error(MessageConst.REPOSITORY_OPERATOR_ERROR).data(ex.getMessage()); + } + + @ExceptionHandler(value = {TaskExecuteException.class, ExecuteException.class}) + public HttpWrapper taskExceptionHandler(HttpServletRequest request, Exception ex) { + log.error("taskExceptionHandler url: {}, task处理异常: {}, message: {}", request.getRequestURI(), ex.getClass(), ex.getMessage(), ex); + return HttpWrapper.error(MessageConst.TASK_ERROR).data(ex.getMessage()); + } + + @ExceptionHandler(value = ConnectionRuntimeException.class) + public HttpWrapper connectionExceptionHandler(HttpServletRequest request, Exception ex) { + log.error("connectionExceptionHandler url: {}, connect异常: {}, message: {}", request.getRequestURI(), ex.getClass(), ex.getMessage(), ex); + return HttpWrapper.error(MessageConst.CONNECT_ERROR).data(ex.getMessage()); + } + + @ExceptionHandler(value = {TimeoutException.class, java.util.concurrent.TimeoutException.class}) + public HttpWrapper timeoutExceptionHandler(HttpServletRequest request, Exception ex) { + log.error("timeoutExceptionHandler url: {}, timeout异常: {}, message: {}", request.getRequestURI(), ex.getClass(), ex.getMessage(), ex); + return HttpWrapper.error(MessageConst.TIMEOUT_ERROR).data(ex.getMessage()); + } + + @ExceptionHandler(value = {InterruptedException.class, InterruptedRuntimeException.class, InterruptedIOException.class}) + public HttpWrapper interruptExceptionHandler(HttpServletRequest request, Exception ex) { + log.error("interruptExceptionHandler url: {}, interrupt异常: {}, message: {}", request.getRequestURI(), ex.getClass(), ex.getMessage(), ex); + return HttpWrapper.error(MessageConst.INTERRUPT_ERROR).data(ex.getMessage()); + } + + @ExceptionHandler(value = UnsafeException.class) + public HttpWrapper unsafeExceptionHandler(HttpServletRequest request, Exception ex) { + log.error("unsafeExceptionHandler url: {}, unsafe异常: {}, message: {}", request.getRequestURI(), ex.getClass(), ex.getMessage(), ex); + return HttpWrapper.error(MessageConst.UNSAFE_OPERATOR).data(ex.getMessage()); + } + + @ExceptionHandler(value = LogException.class) + public HttpWrapper logExceptionHandler(HttpServletRequest request, LogException ex) { + log.error("logExceptionHandler url: {}, 处理异常打印日志: {}, message: {}", request.getRequestURI(), ex.getClass(), ex.getMessage(), ex); + return HttpWrapper.error(MessageConst.EXCEPTION_MESSAGE).data(ex.getMessage()); + } + + @ExceptionHandler(value = ParseCronException.class) + public HttpWrapper parseCronExceptionHandler(ParseCronException ex) { + return HttpWrapper.error(MessageConst.ERROR_EXPRESSION).data(ex.getMessage()); + } + + @ExceptionHandler(value = MaxUploadSizeExceededException.class) + public HttpWrapper maxUploadSizeExceededExceptionHandler(HttpServletRequest request, MaxUploadSizeExceededException ex) { + log.error("maxUploadSizeExceededExceptionHandler url: {}, 上传异常: {}, message: {}", request.getRequestURI(), ex.getClass(), ex.getMessage(), ex); + return HttpWrapper.error(MessageConst.FILE_TOO_LARGE).data(ex.getMessage()); + } + + @ExceptionHandler(value = CodeArgumentException.class) + public HttpWrapper codeArgumentExceptionHandler(CodeArgumentException ex) { + return HttpWrapper.of(ex.getCode(), ex.getMessage()); + } + + @ExceptionHandler(value = HttpWrapperException.class) + public HttpWrapper httpWrapperExceptionHandler(HttpWrapperException ex) { + return ex.getWrapper(); + } + + @ExceptionHandler(value = RpcWrapperException.class) + public HttpWrapper rpcWrapperExceptionHandler(RpcWrapperException ex) { + return ex.getWrapper().toHttpWrapper(); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/config/WebSocketConfig.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/config/WebSocketConfig.java new file mode 100644 index 0000000..20b8d40 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/config/WebSocketConfig.java @@ -0,0 +1,77 @@ + +package cn.orionsec.ops.config; + +import cn.orionsec.ops.handler.sftp.notify.FileTransferNotifyHandler; +import cn.orionsec.ops.handler.tail.TailFileHandler; +import cn.orionsec.ops.handler.terminal.TerminalMessageHandler; +import cn.orionsec.ops.handler.terminal.watcher.TerminalWatcherHandler; +import cn.orionsec.ops.interceptor.FileTransferNotifyInterceptor; +import cn.orionsec.ops.interceptor.TailFileInterceptor; +import cn.orionsec.ops.interceptor.TerminalAccessInterceptor; +import cn.orionsec.ops.interceptor.TerminalWatcherInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; +import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean; + +import javax.annotation.Resource; + +@Configuration +@EnableWebSocket +public class WebSocketConfig implements WebSocketConfigurer { + + @Resource + private TerminalMessageHandler terminalMessageHandler; + + @Resource + private TerminalAccessInterceptor terminalAccessInterceptor; + + @Resource + private TerminalWatcherHandler terminalWatcherHandler; + + @Resource + private TerminalWatcherInterceptor terminalWatcherInterceptor; + + @Resource + private TailFileHandler tailFileHandler; + + @Resource + private TailFileInterceptor tailFileInterceptor; + + @Resource + private FileTransferNotifyHandler fileTransferNotifyHandler; + + @Resource + private FileTransferNotifyInterceptor fileTransferNotifyInterceptor; + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) { + webSocketHandlerRegistry.addHandler(terminalMessageHandler, "/orion/keep-alive/machine/terminal/{token}") + .addInterceptors(terminalAccessInterceptor) + .setAllowedOrigins("*"); + webSocketHandlerRegistry.addHandler(terminalWatcherHandler, "/orion/keep-alive/watcher/terminal/{token}") + .addInterceptors(terminalWatcherInterceptor) + .setAllowedOrigins("*"); + webSocketHandlerRegistry.addHandler(tailFileHandler, "/orion/keep-alive/tail/{token}") + .addInterceptors(tailFileInterceptor) + .setAllowedOrigins("*"); + webSocketHandlerRegistry.addHandler(fileTransferNotifyHandler, "/orion/keep-alive/sftp/notify/{token}") + .addInterceptors(fileTransferNotifyInterceptor) + .setAllowedOrigins("*"); + } + + /** + * web socket 缓冲区大小配置 + */ + @Bean + public ServletServerContainerFactoryBean servletServerContainerFactoryBean() { + ServletServerContainerFactoryBean factory = new ServletServerContainerFactoryBean(); + factory.setMaxBinaryMessageBufferSize(1024 * 1024); + factory.setMaxTextMessageBufferSize(1024 * 1024); + factory.setMaxSessionIdleTimeout(30 * 60000L); + return factory; + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/config/WrapperResultConfig.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/config/WrapperResultConfig.java new file mode 100644 index 0000000..86f0ccc --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/config/WrapperResultConfig.java @@ -0,0 +1,69 @@ + +package cn.orionsec.ops.config; + +import cn.orionsec.kit.lang.constant.StandardContentType; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.kit.lang.define.wrapper.RpcWrapper; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.web.servlet.web.Servlets; +import cn.orionsec.ops.annotation.IgnoreWrapper; +import cn.orionsec.ops.annotation.RestWrapper; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.MethodParameter; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodReturnValueHandler; +import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +@Configuration +public class WrapperResultConfig implements HandlerMethodReturnValueHandler { + + @Resource + private RequestMappingHandlerAdapter requestMappingHandlerAdapter; + + @PostConstruct + public void compare() { + List handlers = requestMappingHandlerAdapter.getReturnValueHandlers(); + List list = Lists.newList(); + list.add(this); + if (handlers != null) { + list.addAll(handlers); + } + requestMappingHandlerAdapter.setReturnValueHandlers(list); + } + + @Override + public boolean supportsReturnType(MethodParameter methodParameter) { + // 统一返回值 + if (!methodParameter.getContainingClass().isAnnotationPresent(RestWrapper.class)) { + return false; + } + return !methodParameter.hasMethodAnnotation(IgnoreWrapper.class); + // && methodParameter.getExecutable().getAnnotatedReturnType().getType() != Void.TYPE; + } + + @Override + public void handleReturnValue(Object o, MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest) throws Exception { + HttpServletResponse response = nativeWebRequest.getNativeResponse(HttpServletResponse.class); + if (response == null) { + return; + } + HttpWrapper wrapper; + if (o instanceof HttpWrapper) { + wrapper = (HttpWrapper) o; + } else if (o instanceof RpcWrapper) { + wrapper = ((RpcWrapper) o).toHttpWrapper(); + } else { + wrapper = new HttpWrapper<>().data(o); + } + modelAndViewContainer.setRequestHandled(true); + response.setContentType(StandardContentType.APPLICATION_JSON); + Servlets.transfer(response, wrapper.toJsonString().getBytes()); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/AlarmGroupController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/AlarmGroupController.java new file mode 100644 index 0000000..3217218 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/AlarmGroupController.java @@ -0,0 +1,79 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.entity.request.alarm.AlarmGroupRequest; +import cn.orionsec.ops.entity.vo.alarm.AlarmGroupVO; +import cn.orionsec.ops.service.api.AlarmGroupService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +@Api(tags = "报警组配置") +@RestController +@RestWrapper +@RequestMapping("/orion/api/alarm-group") +public class AlarmGroupController { + + @Resource + private AlarmGroupService alarmGroupService; + + @PostMapping("/add") + @ApiOperation(value = "添加报警组") + @EventLog(EventType.ADD_ALARM_GROUP) + public Long addAlarmGroup(@RequestBody AlarmGroupRequest request) { + this.validParams(request); + return alarmGroupService.addAlarmGroup(request); + } + + @PostMapping("/update") + @ApiOperation(value = "更新报警组") + @EventLog(EventType.UPDATE_ALARM_GROUP) + public Integer updateAlarmGroup(@RequestBody AlarmGroupRequest request) { + Valid.notNull(request.getId()); + this.validParams(request); + return alarmGroupService.updateAlarmGroup(request); + } + + @PostMapping("/delete") + @ApiOperation(value = "删除报警组") + @EventLog(EventType.DELETE_ALARM_GROUP) + public Integer deleteAlarmGroup(@RequestBody AlarmGroupRequest request) { + Long id = Valid.notNull(request.getId()); + return alarmGroupService.deleteAlarmGroup(id); + } + + @PostMapping("/list") + @ApiOperation(value = "查询列表") + public DataGrid getAlarmGroupList(@RequestBody AlarmGroupRequest request) { + return alarmGroupService.getAlarmGroupList(request); + } + + @PostMapping("/get") + @ApiOperation(value = "查询详情") + public AlarmGroupVO getAlarmGroupDetail(@RequestBody AlarmGroupRequest request) { + Long id = Valid.notNull(request.getId()); + return alarmGroupService.getAlarmGroupDetail(id); + } + + /** + * 验证参数 + * + * @param request request + */ + private void validParams(AlarmGroupRequest request) { + Valid.notBlank(request.getName()); + Valid.notEmpty(request.getUserIdList()); + Valid.notEmpty(request.getNotifyIdList()); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationActionLogController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationActionLogController.java new file mode 100644 index 0000000..07e29e3 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationActionLogController.java @@ -0,0 +1,43 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.ops.annotation.IgnoreLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.entity.request.app.ApplicationActionLogRequest; +import cn.orionsec.ops.entity.vo.app.ApplicationActionLogVO; +import cn.orionsec.ops.service.api.ApplicationActionLogService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +@Api(tags = "应用操作日志") +@RestController +@RestWrapper +@RequestMapping("/orion/api/app-action-log") +public class ApplicationActionLogController { + + @Resource + private ApplicationActionLogService applicationActionLogService; + + @PostMapping("/detail") + @ApiOperation(value = "获取日志详情") + public ApplicationActionLogVO getActionDetail(@RequestBody ApplicationActionLogRequest request) { + Long id = Valid.notNull(request.getId()); + return applicationActionLogService.getDetailById(id); + } + + @IgnoreLog + @PostMapping("/status") + @ApiOperation(value = "获取日志状态") + public ApplicationActionLogVO getActionStatus(@RequestBody ApplicationActionLogRequest request) { + Long id = Valid.notNull(request.getId()); + return applicationActionLogService.getStatusById(id); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationBuildController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationBuildController.java new file mode 100644 index 0000000..e74d89c --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationBuildController.java @@ -0,0 +1,115 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.IgnoreLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.entity.request.app.ApplicationBuildRequest; +import cn.orionsec.ops.entity.vo.app.ApplicationBuildReleaseListVO; +import cn.orionsec.ops.entity.vo.app.ApplicationBuildStatusVO; +import cn.orionsec.ops.entity.vo.app.ApplicationBuildVO; +import cn.orionsec.ops.service.api.ApplicationBuildService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.List; + +@Api(tags = "应用构建") +@RestController +@RestWrapper +@RequestMapping("/orion/api/app-build") +public class ApplicationBuildController { + + @Resource + private ApplicationBuildService applicationBuildService; + + @PostMapping("/submit") + @ApiOperation(value = "提交执行") + @EventLog(EventType.SUBMIT_BUILD) + public Long submitAppBuild(@RequestBody ApplicationBuildRequest request) { + Valid.allNotNull(request.getAppId(), request.getProfileId()); + return applicationBuildService.submitBuildTask(request, true); + } + + @PostMapping("/list") + @ApiOperation(value = "获取构建列表") + public DataGrid getBuildList(@RequestBody ApplicationBuildRequest request) { + Valid.notNull(request.getProfileId()); + return applicationBuildService.getBuildList(request); + } + + @PostMapping("/detail") + @ApiOperation(value = "获取构建详情") + public ApplicationBuildVO getBuildDetail(@RequestBody ApplicationBuildRequest request) { + Long id = Valid.notNull(request.getId()); + return applicationBuildService.getBuildDetail(id); + } + + @IgnoreLog + @PostMapping("/status") + @ApiOperation(value = "查询构建状态") + public ApplicationBuildStatusVO getBuildStatus(@RequestBody ApplicationBuildRequest request) { + Long id = Valid.notNull(request.getId()); + return applicationBuildService.getBuildStatus(id); + } + + @IgnoreLog + @PostMapping("/list-status") + @ApiOperation(value = "查询构建状态列表") + public List getListStatus(@RequestBody ApplicationBuildRequest request) { + List idList = Valid.notEmpty(request.getIdList()); + return applicationBuildService.getBuildStatusList(idList); + } + + @PostMapping("/terminate") + @ApiOperation(value = "终止构建") + @EventLog(EventType.BUILD_TERMINATE) + public HttpWrapper terminateTask(@RequestBody ApplicationBuildRequest request) { + Long id = Valid.notNull(request.getId()); + applicationBuildService.terminateBuildTask(id); + return HttpWrapper.ok(); + } + + @PostMapping("/write") + @ApiOperation(value = "输入命令") + public HttpWrapper writeTask(@RequestBody ApplicationBuildRequest request) { + Long id = Valid.notNull(request.getId()); + String command = Valid.notEmpty(request.getCommand()); + applicationBuildService.writeBuildTask(id, command); + return HttpWrapper.ok(); + } + + @PostMapping("/delete") + @ApiOperation(value = "删除构建") + @EventLog(EventType.DELETE_BUILD) + public Integer deleteTask(@RequestBody ApplicationBuildRequest request) { + List idList = Valid.notEmpty(request.getIdList()); + return applicationBuildService.deleteBuildTask(idList); + } + + @PostMapping("/rebuild") + @ApiOperation(value = "删除构建") + @EventLog(EventType.SUBMIT_REBUILD) + public Long rebuildTask(@RequestBody ApplicationBuildRequest request) { + Long id = Valid.notNull(request.getId()); + return applicationBuildService.rebuild(id); + } + + @PostMapping("/release-list") + @ApiOperation(value = "获取发布构建列表") + public List getBuildReleaseList(@RequestBody ApplicationBuildRequest request) { + Long appId = Valid.notNull(request.getAppId()); + Long profileId = Valid.notNull(request.getProfileId()); + return applicationBuildService.getBuildReleaseList(appId, profileId); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationEnvController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationEnvController.java new file mode 100644 index 0000000..f8bd801 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationEnvController.java @@ -0,0 +1,126 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.collect.MutableLinkedHashMap; +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.ops.annotation.DemoDisableApi; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.env.EnvViewType; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.entity.request.app.ApplicationEnvRequest; +import cn.orionsec.ops.entity.vo.app.ApplicationEnvVO; +import cn.orionsec.ops.service.api.ApplicationEnvService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; + +@Api(tags = "应用环境变量") +@RestController +@RestWrapper +@RequestMapping("/orion/api/app-env") +public class ApplicationEnvController { + + @Resource + private ApplicationEnvService applicationEnvService; + + @DemoDisableApi + @PostMapping("/add") + @ApiOperation(value = "添加环境变量") + public Long addAppEnv(@RequestBody ApplicationEnvRequest request) { + Valid.notNull(request.getAppId()); + Valid.notNull(request.getProfileId()); + Valid.notBlank(request.getKey()); + Valid.notBlank(request.getValue()); + return applicationEnvService.addAppEnv(request); + } + + @DemoDisableApi + @PostMapping("/delete") + @ApiOperation(value = "删除环境变量") + @EventLog(EventType.DELETE_APP_ENV) + public Integer deleteAppEnv(@RequestBody ApplicationEnvRequest request) { + List idList = Valid.notEmpty(request.getIdList()); + return applicationEnvService.deleteAppEnv(idList); + } + + @DemoDisableApi + @PostMapping("/update") + @ApiOperation(value = "更新环境变量") + public Integer updateAppEnv(@RequestBody ApplicationEnvRequest request) { + Valid.notNull(request.getId()); + return applicationEnvService.updateAppEnv(request); + } + + @PostMapping("/list") + @ApiOperation(value = "获取环境变量列表") + public DataGrid listAppEnv(@RequestBody ApplicationEnvRequest request) { + Valid.notNull(request.getAppId()); + Valid.notNull(request.getProfileId()); + return applicationEnvService.listAppEnv(request); + } + + @PostMapping("/detail") + @ApiOperation(value = "获取环境变量详情") + public ApplicationEnvVO appEnvDetail(@RequestBody ApplicationEnvRequest request) { + Long id = Valid.notNull(request.getId()); + return applicationEnvService.getAppEnvDetail(id); + } + + @DemoDisableApi + @PostMapping("/sync") + @ApiOperation(value = "同步环境变量到其他环境") + @EventLog(EventType.SYNC_APP_ENV) + public HttpWrapper syncAppEnv(@RequestBody ApplicationEnvRequest request) { + Long id = Valid.notNull(request.getId()); + Long appId = Valid.notNull(request.getAppId()); + Long profileId = Valid.notNull(request.getProfileId()); + List targetProfileIdList = Valid.notEmpty(request.getTargetProfileIdList()); + applicationEnvService.syncAppEnv(id, appId, profileId, targetProfileIdList); + return HttpWrapper.ok(); + } + + @PostMapping("/view") + @ApiOperation(value = "获取环境变量视图") + public String view(@RequestBody ApplicationEnvRequest request) { + Valid.notNull(request.getAppId()); + Valid.notNull(request.getProfileId()); + EnvViewType viewType = Valid.notNull(EnvViewType.of(request.getViewType())); + request.setLimit(Const.N_100000); + // 查询列表 + Map env = Maps.newLinkedMap(); + applicationEnvService.listAppEnv(request).forEach(e -> env.put(e.getKey(), e.getValue())); + return viewType.toValue(env); + } + + @DemoDisableApi + @PostMapping("/view-save") + @ApiOperation(value = "保存环境变量视图") + public Integer viewSave(@RequestBody ApplicationEnvRequest request) { + Long appId = Valid.notNull(request.getAppId()); + Long profileId = Valid.notNull(request.getProfileId()); + EnvViewType viewType = Valid.notNull(EnvViewType.of(request.getViewType())); + String value = Valid.notBlank(request.getValue()); + try { + MutableLinkedHashMap result = viewType.toMap(value); + applicationEnvService.saveEnv(appId, profileId, result); + return result.size(); + } catch (Exception e) { + throw Exceptions.argument(MessageConst.PARSE_ERROR, e); + } + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationInfoController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationInfoController.java new file mode 100644 index 0000000..9028a28 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationInfoController.java @@ -0,0 +1,211 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.ops.annotation.DemoDisableApi; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.app.ActionType; +import cn.orionsec.ops.constant.app.StageType; +import cn.orionsec.ops.constant.app.TransferMode; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.entity.request.app.*; +import cn.orionsec.ops.entity.vo.app.ApplicationDetailVO; +import cn.orionsec.ops.entity.vo.app.ApplicationInfoVO; +import cn.orionsec.ops.entity.vo.app.ApplicationMachineVO; +import cn.orionsec.ops.service.api.ApplicationInfoService; +import cn.orionsec.ops.service.api.ApplicationMachineService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.List; + +@Api(tags = "应用信息") +@RestController +@RestWrapper +@RequestMapping("/orion/api/app-info") +public class ApplicationInfoController { + + @Resource + private ApplicationInfoService applicationService; + + @Resource + private ApplicationMachineService applicationMachineService; + + @DemoDisableApi + @PostMapping("/add") + @ApiOperation(value = "添加应用") + @EventLog(EventType.ADD_APP) + public Long insertApp(@RequestBody ApplicationInfoRequest request) { + Valid.allNotBlank(request.getName(), request.getTag()); + return applicationService.insertApp(request); + } + + @DemoDisableApi + @PostMapping("/update") + @ApiOperation(value = "更新应用") + @EventLog(EventType.UPDATE_APP) + public Integer updateApp(@RequestBody ApplicationInfoRequest request) { + Valid.notNull(request.getId()); + return applicationService.updateApp(request); + } + + @PostMapping("/sort") + @ApiOperation(value = "更新排序") + public Integer updateAppSort(@RequestBody ApplicationInfoRequest request) { + Long id = Valid.notNull(request.getId()); + Integer adjust = Valid.in(request.getSortAdjust(), Const.INCREMENT, Const.DECREMENT); + return applicationService.updateAppSort(id, Const.INCREMENT.equals(adjust)); + } + + @DemoDisableApi + @PostMapping("/delete") + @ApiOperation(value = "删除应用") + @EventLog(EventType.DELETE_APP) + public Integer deleteApp(@RequestBody ApplicationInfoRequest request) { + Long id = Valid.notNull(request.getId()); + return applicationService.deleteApp(id); + } + + @PostMapping("/list") + @ApiOperation(value = "获取应用列表") + public DataGrid listApp(@RequestBody ApplicationInfoRequest request) { + return applicationService.listApp(request); + } + + @PostMapping("/list-machine") + @ApiOperation(value = "获取应用机器列表") + public List listAppMachines(@RequestBody ApplicationInfoRequest request) { + Long id = Valid.notNull(request.getId()); + Long profileId = Valid.notNull(request.getProfileId()); + return applicationService.getAppMachines(id, profileId); + } + + @PostMapping("/detail") + @ApiOperation(value = "获取详情应用") + public ApplicationDetailVO appDetail(@RequestBody ApplicationInfoRequest request) { + Long appId = Valid.notNull(request.getId()); + return applicationService.getAppDetail(appId, request.getProfileId()); + } + + @PostMapping("/config") + @ApiOperation(value = "配置应用") + @EventLog(EventType.CONFIG_APP) + public HttpWrapper configApp(@RequestBody ApplicationConfigRequest request) { + Valid.notNull(request.getAppId()); + Valid.notNull(request.getProfileId()); + this.checkConfig(request); + applicationService.configAppProfile(request); + return HttpWrapper.ok(); + } + + @PostMapping("/sync") + @ApiOperation(value = "同步配置") + @EventLog(EventType.SYNC_APP) + public HttpWrapper syncAppConfig(@RequestBody ApplicationSyncConfigRequest request) { + Long appId = Valid.notNull(request.getAppId()); + Long profileId = Valid.notNull(request.getProfileId()); + List targetProfileList = Valid.notEmpty(request.getTargetProfileIdList()); + applicationService.syncAppProfileConfig(appId, profileId, targetProfileList); + return HttpWrapper.ok(); + } + + @DemoDisableApi + @PostMapping("/copy") + @ApiOperation(value = "复制应用") + @EventLog(EventType.COPY_APP) + public HttpWrapper copyApplication(@RequestBody ApplicationInfoRequest request) { + Long appId = Valid.notNull(request.getId()); + applicationService.copyApplication(appId); + return HttpWrapper.ok(); + } + + @DemoDisableApi + @PostMapping("/delete-machine") + @ApiOperation(value = "删除发布机器") + public Integer deleteAppMachine(@RequestBody ApplicationInfoRequest request) { + Long id = Valid.notNull(request.getId()); + return applicationMachineService.deleteById(id); + } + + @PostMapping("/get-machine-id") + @ApiOperation(value = "获取发布机器id") + public List getAppMachineId(@RequestBody ApplicationInfoRequest request) { + Long appId = Valid.notNull(request.getId()); + Long profileId = Valid.notNull(request.getProfileId()); + return applicationMachineService.getAppProfileMachineIdList(appId, profileId, true); + } + + /** + * 检查配置 + * + * @param request request + */ + private void checkConfig(ApplicationConfigRequest request) { + StageType stageType = Valid.notNull(StageType.of(request.getStageType())); + List actions; + ApplicationConfigEnvRequest env = Valid.notNull(request.getEnv()); + if (StageType.BUILD.equals(stageType)) { + // 构建检查产物路径 + Valid.notBlank(env.getBundlePath()); + // 检查操作 + actions = Valid.notEmpty(request.getBuildActions()); + } else if (StageType.RELEASE.equals(stageType)) { + // 发布序列 + Valid.notNull(env.getReleaseSerial()); + // 异常处理 + Valid.notNull(env.getExceptionHandler()); + // 发布检查机器id + Valid.notEmpty(request.getMachineIdList()); + // 检查操作 + actions = Valid.notEmpty(request.getReleaseActions()); + } else { + throw Exceptions.unsupported(); + } + // 检查操作 + for (ApplicationConfigActionRequest action : actions) { + Valid.notBlank(action.getName()); + ActionType actionType = Valid.notNull(ActionType.of(action.getType(), stageType.getType())); + // 检查命令 + if (ActionType.BUILD_COMMAND.equals(actionType) || ActionType.RELEASE_COMMAND.equals(actionType)) { + Valid.notBlank(action.getCommand()); + } + // 检查传输 scp 命令 + if (ActionType.RELEASE_TRANSFER.equals(actionType) + && TransferMode.SCP.getValue().equals(env.getTransferMode())) { + Valid.notBlank(action.getCommand()); + } + } + // 检查检出操作唯一性 + int checkoutActionCount = actions.stream() + .map(ApplicationConfigActionRequest::getType) + .map(ActionType::of) + .filter(ActionType.BUILD_CHECKOUT::equals) + .mapToInt(s -> Const.N_1) + .sum(); + Valid.lte(checkoutActionCount, 1, MessageConst.CHECKOUT_ACTION_PRESENT); + // 检查传输操作唯一性 + int transferActionCount = actions.stream() + .map(ApplicationConfigActionRequest::getType) + .map(ActionType::of) + .filter(ActionType.RELEASE_TRANSFER::equals) + .mapToInt(s -> Const.N_1) + .sum(); + Valid.lte(transferActionCount, 1, MessageConst.TRANSFER_ACTION_PRESENT); + if (transferActionCount != 0) { + // 传输目录 + Valid.notBlank(env.getTransferPath()); + } + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationPipelineController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationPipelineController.java new file mode 100644 index 0000000..9b66085 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationPipelineController.java @@ -0,0 +1,91 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.annotation.DemoDisableApi; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.app.StageType; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.entity.request.app.ApplicationPipelineDetailRequest; +import cn.orionsec.ops.entity.request.app.ApplicationPipelineRequest; +import cn.orionsec.ops.entity.vo.app.ApplicationPipelineVO; +import cn.orionsec.ops.service.api.ApplicationPipelineService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.List; + +@Api(tags = "应用流水线") +@RestController +@RestWrapper +@RequestMapping("/orion/api/app-pipeline") +public class ApplicationPipelineController { + + @Resource + private ApplicationPipelineService applicationPipelineService; + + @DemoDisableApi + @PostMapping("/add") + @ApiOperation(value = "新增应用流水线") + @EventLog(EventType.ADD_PIPELINE) + public Long addPipeline(@RequestBody ApplicationPipelineRequest request) { + this.validParams(request); + return applicationPipelineService.addPipeline(request); + } + + @DemoDisableApi + @PostMapping("/update") + @ApiOperation(value = "修改应用流水线") + @EventLog(EventType.UPDATE_PIPELINE) + public Integer updatePipeline(@RequestBody ApplicationPipelineRequest request) { + Valid.notNull(request.getId()); + this.validParams(request); + return applicationPipelineService.updatePipeline(request); + } + + @PostMapping("/list") + @ApiOperation(value = "获取应用流水线列表") + public DataGrid listPipeline(@RequestBody ApplicationPipelineRequest request) { + Valid.notNull(request.getProfileId()); + return applicationPipelineService.listPipeline(request); + } + + @PostMapping("/get") + @ApiOperation(value = "详情获取应用流水线") + public ApplicationPipelineVO getPipeline(@RequestBody ApplicationPipelineRequest request) { + Long id = Valid.notNull(request.getId()); + return applicationPipelineService.getPipeline(id); + } + + @DemoDisableApi + @PostMapping("/delete") + @ApiOperation(value = "删除应用流水线") + @EventLog(EventType.DELETE_PIPELINE) + public Integer deletePipeline(@RequestBody ApplicationPipelineRequest request) { + List idList = Valid.notEmpty(request.getIdList()); + return applicationPipelineService.deletePipeline(idList); + } + + /** + * 检查参数 + * + * @param request request + */ + private void validParams(@RequestBody ApplicationPipelineRequest request) { + Valid.notBlank(request.getName()); + Valid.notNull(request.getProfileId()); + List details = Valid.notEmpty(request.getDetails()); + for (ApplicationPipelineDetailRequest detail : details) { + Valid.notNull(detail.getAppId()); + Valid.notNull(StageType.of(detail.getStageType())); + } + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationPipelineTaskController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationPipelineTaskController.java new file mode 100644 index 0000000..e711afa --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationPipelineTaskController.java @@ -0,0 +1,208 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.IgnoreLog; +import cn.orionsec.ops.annotation.RequireRole; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.app.PipelineStatus; +import cn.orionsec.ops.constant.app.TimedType; +import cn.orionsec.ops.constant.common.AuditStatus; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.constant.user.RoleType; +import cn.orionsec.ops.entity.request.app.ApplicationPipelineTaskDetailRequest; +import cn.orionsec.ops.entity.request.app.ApplicationPipelineTaskRequest; +import cn.orionsec.ops.entity.vo.app.*; +import cn.orionsec.ops.service.api.ApplicationPipelineTaskDetailService; +import cn.orionsec.ops.service.api.ApplicationPipelineTaskLogService; +import cn.orionsec.ops.service.api.ApplicationPipelineTaskService; +import cn.orionsec.ops.task.TaskRegister; +import cn.orionsec.ops.task.TaskType; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.List; + +@Api(tags = "应用流水线任务") +@RestController +@RestWrapper +@RequestMapping("/orion/api/app-pipeline-task") +public class ApplicationPipelineTaskController { + + @Resource + private ApplicationPipelineTaskService applicationPipelineTaskService; + + @Resource + private ApplicationPipelineTaskDetailService applicationPipelineTaskDetailService; + + @Resource + private ApplicationPipelineTaskLogService applicationPipelineTaskLogService; + + @Resource + private TaskRegister taskRegister; + + @PostMapping("/list") + @ApiOperation(value = "获取任务列表") + public DataGrid getPipelineTaskList(@RequestBody ApplicationPipelineTaskRequest request) { + Valid.notNull(request.getProfileId()); + return applicationPipelineTaskService.getPipelineTaskList(request); + } + + @PostMapping("/detail") + @ApiOperation(value = "获取任务详情") + public ApplicationPipelineTaskVO getPipelineTaskDetail(@RequestBody ApplicationPipelineTaskRequest request) { + Long id = Valid.notNull(request.getId()); + return applicationPipelineTaskService.getPipelineTaskDetail(id); + } + + @PostMapping("/task-details") + @ApiOperation(value = "获取任务执行详情") + public List getTaskDetails(@RequestBody ApplicationPipelineTaskRequest request) { + Long id = Valid.notNull(request.getId()); + return applicationPipelineTaskDetailService.getTaskDetails(id); + } + + @PostMapping("/submit") + @ApiOperation(value = "提交任务") + @EventLog(EventType.SUBMIT_PIPELINE_TASK) + public Long submitPipelineTask(@RequestBody ApplicationPipelineTaskRequest request) { + Valid.notNull(request.getPipelineId()); + Valid.notBlank(request.getTitle()); + List details = Valid.notEmpty(request.getDetails()); + for (ApplicationPipelineTaskDetailRequest detail : details) { + Valid.notNull(detail.getId()); + } + TimedType timedType = Valid.notNull(TimedType.of(request.getTimedExec())); + if (TimedType.TIMED.equals(timedType)) { + Date timedExecTime = Valid.notNull(request.getTimedExecTime()); + Valid.isTrue(timedExecTime.compareTo(new Date()) > 0, MessageConst.TIMED_GREATER_THAN_NOW); + } + Long id = applicationPipelineTaskService.submitPipelineTask(request); + // 提交任务 + if (PipelineStatus.WAIT_SCHEDULE.getStatus().equals(request.getStatus())) { + taskRegister.submit(TaskType.PIPELINE, request.getTimedExecTime(), id); + } + return id; + } + + @PostMapping("/audit") + @ApiOperation(value = "审核任务") + @RequireRole(RoleType.ADMINISTRATOR) + @EventLog(EventType.AUDIT_PIPELINE_TASK) + public Integer auditPipelineTask(@RequestBody ApplicationPipelineTaskRequest request) { + Valid.notNull(request.getId()); + AuditStatus status = Valid.notNull(AuditStatus.of(request.getAuditStatus())); + if (AuditStatus.REJECT.equals(status)) { + Valid.notBlank(request.getAuditReason()); + } + return applicationPipelineTaskService.auditPipelineTask(request); + } + + @PostMapping("/copy") + @ApiOperation(value = "复制任务") + @EventLog(EventType.COPY_PIPELINE_TASK) + public Long copyPipelineTask(@RequestBody ApplicationPipelineTaskRequest request) { + Long id = Valid.notNull(request.getId()); + return applicationPipelineTaskService.copyPipeline(id); + } + + @PostMapping("/exec") + @ApiOperation(value = "执行任务") + @EventLog(EventType.EXEC_PIPELINE_TASK) + public HttpWrapper execPipelineTask(@RequestBody ApplicationPipelineTaskRequest request) { + Long id = Valid.notNull(request.getId()); + applicationPipelineTaskService.execPipeline(id, false); + return HttpWrapper.ok(); + } + + @PostMapping("/delete") + @ApiOperation(value = "删除任务") + @EventLog(EventType.DELETE_PIPELINE_TASK) + public Integer deletePipelineTask(@RequestBody ApplicationPipelineTaskRequest request) { + List idList = Valid.notEmpty(request.getIdList()); + return applicationPipelineTaskService.deletePipeline(idList); + } + + @PostMapping("/set-timed") + @ApiOperation(value = "设置定时执行") + @EventLog(EventType.SET_PIPELINE_TIMED_TASK) + public HttpWrapper setTaskTimedExec(@RequestBody ApplicationPipelineTaskRequest request) { + Long id = Valid.notNull(request.getId()); + Date timedExecTime = Valid.notNull(request.getTimedExecTime()); + Valid.isTrue(timedExecTime.compareTo(new Date()) > 0, MessageConst.TIMED_GREATER_THAN_NOW); + applicationPipelineTaskService.setPipelineTimedExec(id, timedExecTime); + return HttpWrapper.ok(); + } + + @PostMapping("/cancel-timed") + @ApiOperation(value = "取消定时") + @EventLog(EventType.CANCEL_PIPELINE_TIMED_TASK) + public HttpWrapper cancelTaskTimedExec(@RequestBody ApplicationPipelineTaskRequest request) { + Long id = Valid.notNull(request.getId()); + applicationPipelineTaskService.cancelPipelineTimedExec(id); + return HttpWrapper.ok(); + } + + @PostMapping("/terminate") + @ApiOperation(value = "停止执行任务") + @EventLog(EventType.TERMINATE_PIPELINE_TASK) + public HttpWrapper terminateTask(@RequestBody ApplicationPipelineTaskRequest request) { + Long id = Valid.notNull(request.getId()); + applicationPipelineTaskService.terminateExec(id); + return HttpWrapper.ok(); + } + + @PostMapping("/terminate-detail") + @ApiOperation(value = "停止部分操作") + @EventLog(EventType.TERMINATE_PIPELINE_TASK_DETAIL) + public HttpWrapper terminateTaskDetail(@RequestBody ApplicationPipelineTaskRequest request) { + Long id = Valid.notNull(request.getId()); + Long detailId = Valid.notNull(request.getDetailId()); + applicationPipelineTaskService.terminateExecDetail(id, detailId); + return HttpWrapper.ok(); + } + + @PostMapping("/skip-detail") + @ApiOperation(value = "跳过部分操作") + @EventLog(EventType.SKIP_PIPELINE_TASK_DETAIL) + public HttpWrapper skipTaskDetail(@RequestBody ApplicationPipelineTaskRequest request) { + Long id = Valid.notNull(request.getId()); + Long detailId = Valid.notNull(request.getDetailId()); + applicationPipelineTaskService.skipExecDetail(id, detailId); + return HttpWrapper.ok(); + } + + @IgnoreLog + @PostMapping("/status") + @ApiOperation(value = "获取单个任务状态") + public ApplicationPipelineTaskStatusVO getTaskStatus(@RequestBody ApplicationPipelineTaskRequest request) { + Long id = Valid.notNull(request.getId()); + return applicationPipelineTaskService.getTaskStatus(id); + } + + @IgnoreLog + @PostMapping("/list-status") + @ApiOperation(value = "获取多个任务状态") + public List getTaskStatusList(@RequestBody ApplicationPipelineTaskRequest request) { + List idList = Valid.notEmpty(request.getIdList()); + return applicationPipelineTaskService.getTaskStatusList(idList, request.getDetailIdList()); + } + + @PostMapping("/log") + @ApiOperation(value = "获取任务日志") + public List getTaskLogList(@RequestBody ApplicationPipelineTaskRequest request) { + Long id = Valid.notNull(request.getId()); + return applicationPipelineTaskLogService.getLogList(id); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationProfileController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationProfileController.java new file mode 100644 index 0000000..23b31a3 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationProfileController.java @@ -0,0 +1,87 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.annotation.DemoDisableApi; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.RequireRole; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.constant.user.RoleType; +import cn.orionsec.ops.entity.request.app.ApplicationProfileRequest; +import cn.orionsec.ops.entity.vo.app.ApplicationProfileFastVO; +import cn.orionsec.ops.entity.vo.app.ApplicationProfileVO; +import cn.orionsec.ops.service.api.ApplicationProfileService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +@Api(tags = "应用环境") +@RestController +@RestWrapper +@RequestMapping("/orion/api/app-profile") +public class ApplicationProfileController { + + @Resource + private ApplicationProfileService applicationProfileService; + + @DemoDisableApi + @PostMapping("/add") + @ApiOperation(value = "添加应用环境") + @RequireRole(RoleType.ADMINISTRATOR) + @EventLog(EventType.ADD_PROFILE) + public Long addProfile(@RequestBody ApplicationProfileRequest request) { + Valid.notBlank(request.getName()); + Valid.notBlank(request.getTag()); + Valid.in(request.getReleaseAudit(), Const.ENABLE, Const.DISABLE); + return applicationProfileService.addProfile(request); + } + + @DemoDisableApi + @PostMapping("/update") + @ApiOperation(value = "更新应用环境") + @RequireRole(RoleType.ADMINISTRATOR) + @EventLog(EventType.UPDATE_PROFILE) + public Integer updateProfile(@RequestBody ApplicationProfileRequest request) { + Valid.notNull(request.getId()); + if (request.getReleaseAudit() != null) { + Valid.in(request.getReleaseAudit(), Const.ENABLE, Const.DISABLE); + } + return applicationProfileService.updateProfile(request); + } + + @DemoDisableApi + @PostMapping("/delete") + @ApiOperation(value = "删除应用环境") + @RequireRole(RoleType.ADMINISTRATOR) + @EventLog(EventType.DELETE_PROFILE) + public Integer deleteProfile(@RequestBody ApplicationProfileRequest request) { + Long id = Valid.notNull(request.getId()); + return applicationProfileService.deleteProfile(id); + } + + @PostMapping("/list") + @ApiOperation(value = "获取应用环境列表") + public DataGrid listProfiles(@RequestBody ApplicationProfileRequest request) { + return applicationProfileService.listProfiles(request); + } + + @GetMapping("/fast-list") + @ApiOperation(value = "获取应用环境列表 (缓存)") + public List listProfiles() { + return applicationProfileService.fastListProfiles(); + } + + @PostMapping("/detail") + @ApiOperation(value = "获取应用环境详情") + public ApplicationProfileVO getProfile(@RequestBody ApplicationProfileRequest request) { + Long id = Valid.notNull(request.getId()); + return applicationProfileService.getProfile(id); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationReleaseController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationReleaseController.java new file mode 100644 index 0000000..419e823 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationReleaseController.java @@ -0,0 +1,230 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.IgnoreLog; +import cn.orionsec.ops.annotation.RequireRole; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.app.ReleaseStatus; +import cn.orionsec.ops.constant.app.TimedType; +import cn.orionsec.ops.constant.common.AuditStatus; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.constant.user.RoleType; +import cn.orionsec.ops.entity.request.app.ApplicationReleaseAuditRequest; +import cn.orionsec.ops.entity.request.app.ApplicationReleaseRequest; +import cn.orionsec.ops.entity.vo.app.*; +import cn.orionsec.ops.service.api.ApplicationReleaseService; +import cn.orionsec.ops.task.TaskRegister; +import cn.orionsec.ops.task.TaskType; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.Date; +import java.util.List; + +@Api(tags = "应用发布") +@RestController +@RestWrapper +@RequestMapping("/orion/api/app-release") +public class ApplicationReleaseController { + + @Resource + private ApplicationReleaseService applicationReleaseService; + + @Resource + private TaskRegister taskRegister; + + @PostMapping("/list") + @ApiOperation(value = "获取发布列表") + public DataGrid getReleaseList(@RequestBody ApplicationReleaseRequest request) { + return applicationReleaseService.getReleaseList(request); + } + + @PostMapping("/list-machine") + @ApiOperation(value = "获取发布机器列表") + public List getReleaseMachineList(@RequestBody ApplicationReleaseRequest request) { + Long id = Valid.notNull(request.getId()); + return applicationReleaseService.getReleaseMachineList(id); + } + + @PostMapping("/detail") + @ApiOperation(value = "获取发布详情") + public ApplicationReleaseDetailVO getReleaseDetail(@RequestBody ApplicationReleaseRequest request) { + Valid.notNull(request.getId()); + return applicationReleaseService.getReleaseDetail(request); + } + + @PostMapping("/machine-detail") + @ApiOperation(value = "获取发布机器详情") + public ApplicationReleaseMachineVO getReleaseMachineDetail(@RequestBody ApplicationReleaseRequest request) { + Long releaseMachineId = Valid.notNull(request.getReleaseMachineId()); + return applicationReleaseService.getReleaseMachineDetail(releaseMachineId); + } + + @PostMapping("/submit") + @ApiOperation(value = "提交发布任务") + @EventLog(EventType.SUBMIT_RELEASE) + public Long submitAppRelease(@RequestBody ApplicationReleaseRequest request) { + Valid.notBlank(request.getTitle()); + Valid.notNull(request.getAppId()); + Valid.notNull(request.getProfileId()); + Valid.notNull(request.getBuildId()); + Valid.notEmpty(request.getMachineIdList()); + TimedType timedType = Valid.notNull(TimedType.of(request.getTimedRelease())); + if (TimedType.TIMED.equals(timedType)) { + Date timedReleaseTime = Valid.notNull(request.getTimedReleaseTime()); + Valid.isTrue(timedReleaseTime.compareTo(new Date()) > 0, MessageConst.TIMED_GREATER_THAN_NOW); + } + // 提交 + Long id = applicationReleaseService.submitAppRelease(request); + // 提交任务 + if (ReleaseStatus.WAIT_SCHEDULE.getStatus().equals(request.getStatus())) { + taskRegister.submit(TaskType.RELEASE, request.getTimedReleaseTime(), id); + } + return id; + } + + @PostMapping("/copy") + @ApiOperation(value = "复制发布任务") + @EventLog(EventType.COPY_RELEASE) + public Long copyAppRelease(@RequestBody ApplicationReleaseRequest request) { + Long id = Valid.notNull(request.getId()); + return applicationReleaseService.copyAppRelease(id); + } + + @PostMapping("/audit") + @ApiOperation(value = "发布任务审核") + @RequireRole(RoleType.ADMINISTRATOR) + @EventLog(EventType.AUDIT_RELEASE) + public Integer auditAppRelease(@RequestBody ApplicationReleaseAuditRequest request) { + Valid.notNull(request.getId()); + AuditStatus status = Valid.notNull(AuditStatus.of(request.getStatus())); + if (AuditStatus.REJECT.equals(status)) { + Valid.notBlank(request.getReason()); + } + return applicationReleaseService.auditAppRelease(request); + } + + @PostMapping("/runnable") + @ApiOperation(value = "执行发布任务") + @EventLog(EventType.RUNNABLE_RELEASE) + public HttpWrapper runnableAppRelease(@RequestBody ApplicationReleaseRequest request) { + Long id = Valid.notNull(request.getId()); + applicationReleaseService.runnableAppRelease(id, false, true); + return HttpWrapper.ok(); + } + + @PostMapping("/set-timed") + @ApiOperation(value = "设置定时发布") + @EventLog(EventType.SET_TIMED_RELEASE) + public HttpWrapper setTimedRelease(@RequestBody ApplicationReleaseRequest request) { + Long id = Valid.notNull(request.getId()); + Date timedReleaseTime = Valid.notNull(request.getTimedReleaseTime()); + Valid.isTrue(timedReleaseTime.compareTo(new Date()) > 0, MessageConst.TIMED_GREATER_THAN_NOW); + applicationReleaseService.setTimedRelease(id, timedReleaseTime); + return HttpWrapper.ok(); + } + + @PostMapping("/cancel-timed") + @ApiOperation(value = "取消定时发布") + @EventLog(EventType.CANCEL_TIMED_RELEASE) + public HttpWrapper cancelTimedRelease(@RequestBody ApplicationReleaseRequest request) { + Long id = Valid.notNull(request.getId()); + applicationReleaseService.cancelAppTimedRelease(id); + return HttpWrapper.ok(); + } + + @PostMapping("/rollback") + @ApiOperation(value = "回滚发布") + @EventLog(EventType.ROLLBACK_RELEASE) + public Long rollbackAppRelease(@RequestBody ApplicationReleaseRequest request) { + Long id = Valid.notNull(request.getId()); + return applicationReleaseService.rollbackAppRelease(id); + } + + @PostMapping("/terminate") + @EventLog(EventType.TERMINATE_RELEASE) + @ApiOperation(value = "停止发布任务") + public HttpWrapper terminateAppRelease(@RequestBody ApplicationReleaseRequest request) { + Long id = Valid.notNull(request.getId()); + applicationReleaseService.terminateRelease(id); + return HttpWrapper.ok(); + } + + @PostMapping("/terminate-machine") + @ApiOperation(value = "停止机器发布操作") + @EventLog(EventType.TERMINATE_MACHINE_RELEASE) + public HttpWrapper terminateMachine(@RequestBody ApplicationReleaseRequest request) { + Long releaseMachineId = Valid.notNull(request.getReleaseMachineId()); + applicationReleaseService.terminateMachine(releaseMachineId); + return HttpWrapper.ok(); + } + + @PostMapping("/skip-machine") + @ApiOperation(value = "跳过机器发布操作") + @EventLog(EventType.SKIP_MACHINE_RELEASE) + public HttpWrapper skipMachine(@RequestBody ApplicationReleaseRequest request) { + Long releaseMachineId = Valid.notNull(request.getReleaseMachineId()); + applicationReleaseService.skipMachine(releaseMachineId); + return HttpWrapper.ok(); + } + + @PostMapping("/write-machine") + @ApiOperation(value = "机器操作输入命令") + public HttpWrapper writeMachine(@RequestBody ApplicationReleaseRequest request) { + Long releaseMachineId = Valid.notNull(request.getReleaseMachineId()); + String command = Valid.notEmpty(request.getCommand()); + applicationReleaseService.writeMachine(releaseMachineId, command); + return HttpWrapper.ok(); + } + + @PostMapping("/delete") + @ApiOperation(value = "删除发布任务") + @EventLog(EventType.DELETE_RELEASE) + public Integer deleteAppRelease(@RequestBody ApplicationReleaseRequest request) { + List idList = Valid.notNull(request.getIdList()); + return applicationReleaseService.deleteRelease(idList); + } + + @IgnoreLog + @PostMapping("/list-status") + @ApiOperation(value = "获取发布状态列表") + public List getReleaseStatusList(@RequestBody ApplicationReleaseRequest request) { + List idList = Valid.notEmpty(request.getIdList()); + return applicationReleaseService.getReleaseStatusList(idList, request.getMachineIdList()); + } + + @IgnoreLog + @PostMapping("/status") + @ApiOperation(value = "获取发布状态") + public ApplicationReleaseStatusVO getReleaseStatus(@RequestBody ApplicationReleaseRequest request) { + Long id = Valid.notNull(request.getId()); + return applicationReleaseService.getReleaseStatus(id); + } + + @IgnoreLog + @PostMapping("/list-machine-status") + @ApiOperation(value = "获取发布机器状态列表") + public List getReleaseMachineStatusList(@RequestBody ApplicationReleaseRequest request) { + List idList = Valid.notEmpty(request.getReleaseMachineIdList()); + return applicationReleaseService.getReleaseMachineStatusList(idList); + } + + @IgnoreLog + @PostMapping("/machine-status") + @ApiOperation(value = "获取发布机器状态") + public ApplicationReleaseMachineStatusVO getReleaseMachineStatus(@RequestBody ApplicationReleaseRequest request) { + Long id = Valid.notNull(request.getReleaseMachineId()); + return applicationReleaseService.getReleaseMachineStatus(id); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationRepositoryController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationRepositoryController.java new file mode 100644 index 0000000..76850eb --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/ApplicationRepositoryController.java @@ -0,0 +1,133 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.ops.annotation.DemoDisableApi; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.app.RepositoryAuthType; +import cn.orionsec.ops.constant.app.RepositoryTokenType; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.entity.request.app.ApplicationRepositoryRequest; +import cn.orionsec.ops.entity.vo.app.ApplicationRepositoryBranchVO; +import cn.orionsec.ops.entity.vo.app.ApplicationRepositoryCommitVO; +import cn.orionsec.ops.entity.vo.app.ApplicationRepositoryInfoVO; +import cn.orionsec.ops.entity.vo.app.ApplicationRepositoryVO; +import cn.orionsec.ops.service.api.ApplicationRepositoryService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.List; + +@Api(tags = "应用版本仓库") +@RestController +@RestWrapper +@RequestMapping("/orion/api/app-repo") +public class ApplicationRepositoryController { + + @Resource + private ApplicationRepositoryService applicationRepositoryService; + + @DemoDisableApi + @PostMapping("/add") + @ApiOperation(value = "添加版本仓库") + @EventLog(EventType.ADD_REPOSITORY) + public Long addRepository(@RequestBody ApplicationRepositoryRequest request) { + Valid.allNotBlank(request.getName(), request.getUrl()); + RepositoryAuthType authType = Valid.notNull(RepositoryAuthType.of(request.getAuthType())); + if (RepositoryAuthType.TOKEN.equals(authType)) { + Valid.notNull(RepositoryTokenType.of(request.getTokenType())); + Valid.notBlank(request.getPrivateToken()); + } + return applicationRepositoryService.addRepository(request); + } + + @DemoDisableApi + @ApiOperation(value = "更新版本仓库") + @PostMapping("/update") + @EventLog(EventType.UPDATE_REPOSITORY) + public Integer updateRepository(@RequestBody ApplicationRepositoryRequest request) { + Valid.notNull(request.getId()); + Valid.allNotBlank(request.getName(), request.getUrl()); + return applicationRepositoryService.updateRepository(request); + } + + @DemoDisableApi + @PostMapping("/delete") + @ApiOperation(value = "删除版本仓库") + @EventLog(EventType.DELETE_REPOSITORY) + public Integer deleteRepository(@RequestBody ApplicationRepositoryRequest request) { + Long id = Valid.notNull(request.getId()); + return applicationRepositoryService.deleteRepository(id); + } + + @PostMapping("/list") + @ApiOperation(value = "获取版本仓库列表") + public DataGrid listRepository(@RequestBody ApplicationRepositoryRequest request) { + return applicationRepositoryService.listRepository(request); + } + + @PostMapping("/detail") + @ApiOperation(value = "获取版本仓库详情") + public ApplicationRepositoryVO getRepositoryDetail(@RequestBody ApplicationRepositoryRequest request) { + Long id = Valid.notNull(request.getId()); + return applicationRepositoryService.getRepositoryDetail(id); + } + + @PostMapping("/init") + @ApiOperation(value = "初始化版本仓库") + @EventLog(EventType.INIT_REPOSITORY) + public HttpWrapper initRepository(@RequestBody ApplicationRepositoryRequest request) { + Long id = Valid.notNull(request.getId()); + applicationRepositoryService.initEventRepository(id, false); + return HttpWrapper.ok(); + } + + @PostMapping("/re-init") + @ApiOperation(value = "重新初始化版本仓库") + @EventLog(EventType.RE_INIT_REPOSITORY) + public HttpWrapper reInitRepository(@RequestBody ApplicationRepositoryRequest request) { + Long id = Valid.notNull(request.getId()); + applicationRepositoryService.initEventRepository(id, true); + return HttpWrapper.ok(); + } + + @ApiOperation(value = "获取分支和提交记录列表") + @PostMapping("/info") + public ApplicationRepositoryInfoVO getRepositoryInfo(@RequestBody ApplicationRepositoryRequest request) { + Valid.notNull(request.getId()); + return applicationRepositoryService.getRepositoryInfo(request); + } + + @PostMapping("/branch") + @ApiOperation(value = "获取分支列表") + public List getRepositoryBranchList(@RequestBody ApplicationRepositoryRequest request) { + Long id = Valid.notNull(request.getId()); + return applicationRepositoryService.getRepositoryBranchList(id); + } + + @PostMapping("/commit") + @ApiOperation(value = "获取提交列表") + public List getRepositoryCommitList(@RequestBody ApplicationRepositoryRequest request) { + Long id = Valid.notNull(request.getId()); + String branchName = Valid.notBlank(request.getBranchName()); + return applicationRepositoryService.getRepositoryCommitList(id, branchName); + } + + @PostMapping("/clean") + @ApiOperation(value = "清空应用构建历史版本") + @EventLog(EventType.CLEAN_REPOSITORY) + public HttpWrapper cleanBuildRepository(@RequestBody ApplicationRepositoryRequest request) { + Long id = Valid.notNull(request.getId()); + applicationRepositoryService.cleanBuildRepository(id); + return HttpWrapper.ok(); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/AuthenticateController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/AuthenticateController.java new file mode 100644 index 0000000..1e96fed --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/AuthenticateController.java @@ -0,0 +1,77 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.kit.lang.utils.Objects1; +import cn.orionsec.kit.lang.utils.convert.Converts; +import cn.orionsec.kit.web.servlet.web.Servlets; +import cn.orionsec.ops.annotation.DemoDisableApi; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.IgnoreAuth; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.entity.request.user.UserLoginRequest; +import cn.orionsec.ops.entity.request.user.UserResetRequest; +import cn.orionsec.ops.entity.vo.user.UserInfoVO; +import cn.orionsec.ops.entity.vo.user.UserLoginVO; +import cn.orionsec.ops.service.api.PassportService; +import cn.orionsec.ops.utils.Currents; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; + +@Api(tags = "用户认证") +@RestController +@RestWrapper +@RequestMapping("/orion/api/auth") +public class AuthenticateController { + + @Resource + private PassportService passportService; + + @IgnoreAuth + @PostMapping("/login") + @ApiOperation(value = "登录") + @EventLog(EventType.LOGIN) + public UserLoginVO login(@RequestBody UserLoginRequest login, HttpServletRequest request) { + String username = Valid.notBlank(login.getUsername()).trim(); + String password = Valid.notBlank(login.getPassword()).trim(); + login.setUsername(username); + login.setPassword(password); + login.setIp(Servlets.getRemoteAddr(request)); + // 登录 + return passportService.login(login); + } + + @IgnoreAuth + @GetMapping("/logout") + @ApiOperation(value = "登出") + @EventLog(EventType.LOGOUT) + public HttpWrapper logout() { + passportService.logout(); + return HttpWrapper.ok(); + } + + @DemoDisableApi + @PostMapping("/reset") + @ApiOperation(value = "重置密码") + @EventLog(EventType.RESET_PASSWORD) + public HttpWrapper resetPassword(@RequestBody UserResetRequest request) { + String password = Valid.notBlank(request.getPassword()).trim(); + request.setUserId(Objects1.def(request.getUserId(), Currents::getUserId)); + request.setPassword(password); + passportService.resetPassword(request); + return HttpWrapper.ok(); + } + + @GetMapping("/valid") + @ApiOperation(value = "检查用户信息") + public UserInfoVO validToken() { + return Converts.to(Currents.getUser(), UserInfoVO.class); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/BatchExecCommandController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/BatchExecCommandController.java new file mode 100644 index 0000000..eaa89e8 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/BatchExecCommandController.java @@ -0,0 +1,90 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.IgnoreLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.entity.request.exec.CommandExecRequest; +import cn.orionsec.ops.entity.vo.exec.CommandExecStatusVO; +import cn.orionsec.ops.entity.vo.exec.CommandExecVO; +import cn.orionsec.ops.entity.vo.exec.CommandTaskSubmitVO; +import cn.orionsec.ops.service.api.CommandExecService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.List; + +@Api(tags = "批量执行") +@RestController +@RestWrapper +@RequestMapping("/orion/api/batch-exec") +public class BatchExecCommandController { + + @Resource + private CommandExecService commandExecService; + + @PostMapping("/submit") + @ApiOperation(value = "提交批量执行任务") + @EventLog(EventType.EXEC_SUBMIT) + public List submitTask(@RequestBody CommandExecRequest request) { + Valid.notBlank(request.getCommand()); + Valid.notEmpty(request.getMachineIdList()); + return commandExecService.batchSubmitTask(request); + } + + @PostMapping("/list") + @ApiOperation(value = "获取执行列表") + public DataGrid list(@RequestBody CommandExecRequest request) { + return commandExecService.execList(request); + } + + @PostMapping("/detail") + @ApiOperation(value = "获取执行详情") + public CommandExecVO detail(@RequestBody CommandExecRequest request) { + Long id = Valid.notNull(request.getId()); + return commandExecService.execDetail(id); + } + + @PostMapping("/write") + @ApiOperation(value = "写入命令") + public void write(@RequestBody CommandExecRequest request) { + Long id = Valid.notNull(request.getId()); + String command = Valid.notEmpty(request.getCommand()); + commandExecService.writeCommand(id, command); + } + + @PostMapping("/terminate") + @ApiOperation(value = "停止执行任务") + @EventLog(EventType.EXEC_TERMINATE) + public HttpWrapper terminate(@RequestBody CommandExecRequest request) { + Long id = Valid.notNull(request.getId()); + commandExecService.terminateExec(id); + return HttpWrapper.ok(); + } + + @PostMapping("/delete") + @ApiOperation(value = "删除任务") + @EventLog(EventType.EXEC_DELETE) + public Integer delete(@RequestBody CommandExecRequest request) { + List idList = Valid.notEmpty(request.getIdList()); + return commandExecService.deleteTask(idList); + } + + @IgnoreLog + @PostMapping("/list-status") + @ApiOperation(value = "获取状态列表") + public List getListStatus(@RequestBody CommandExecRequest request) { + List idList = Valid.notEmpty(request.getIdList()); + return commandExecService.getExecStatusList(idList); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/BatchUploadController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/BatchUploadController.java new file mode 100644 index 0000000..de636aa --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/BatchUploadController.java @@ -0,0 +1,97 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.id.ObjectIds; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.entity.dto.sftp.SftpUploadInfoDTO; +import cn.orionsec.ops.entity.request.sftp.FileUploadRequest; +import cn.orionsec.ops.entity.request.upload.BatchUploadRequest; +import cn.orionsec.ops.entity.vo.upload.BatchUploadCheckVO; +import cn.orionsec.ops.entity.vo.upload.BatchUploadTokenVO; +import cn.orionsec.ops.service.api.BatchUploadService; +import cn.orionsec.ops.service.api.SftpService; +import cn.orionsec.ops.utils.PathBuilders; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +@Api(tags = "批量上传") +@RestController +@RestWrapper +@RequestMapping("/orion/api/batch-upload") +public class BatchUploadController { + + @Resource + private BatchUploadService batchUploadService; + + @Resource + private SftpService sftpService; + + @PostMapping("/check") + @ApiOperation(value = "检查机器以及文件") + public BatchUploadCheckVO checkFilePresent(@RequestBody BatchUploadRequest request) { + Valid.checkNormalize(request.getRemotePath()); + Valid.notEmpty(request.getMachineIds()); + Valid.notEmpty(request.getNames()); + Valid.checkUploadSize(request.getSize()); + return batchUploadService.checkMachineFiles(request); + } + + @PostMapping("/token") + @ApiOperation(value = "获取上传token") + public BatchUploadTokenVO getUploadAccessToken(@RequestBody BatchUploadRequest request) { + Valid.checkNormalize(request.getRemotePath()); + Valid.notEmpty(request.getMachineIds()); + return batchUploadService.getUploadAccessToken(request); + } + + @PostMapping("/exec") + @ApiOperation(value = "执行上传") + @EventLog(EventType.SFTP_UPLOAD) + public List uploadFile(@RequestParam("accessToken") String accessToken, @RequestParam("files") List files) throws IOException { + // 检查文件 + Valid.notBlank(accessToken); + Valid.notEmpty(files); + // 检查token + SftpUploadInfoDTO uploadInfo = sftpService.checkUploadAccessToken(accessToken); + String remotePath = uploadInfo.getRemotePath(); + List machineIdList = uploadInfo.getMachineIdList(); + + List requestFiles = Lists.newList(); + for (Long machineId : machineIdList) { + for (MultipartFile file : files) { + // 传输文件到本地 + String fileToken = ObjectIds.nextId(); + String localPath = PathBuilders.getSftpUploadFilePath(fileToken); + Path localAbsolutePath = Paths.get(SystemEnvAttr.SWAP_PATH.getValue(), localPath); + Files1.touch(localAbsolutePath); + file.transferTo(localAbsolutePath); + + // 请求参数 + FileUploadRequest request = new FileUploadRequest(); + request.setMachineId(machineId); + request.setLocalPath(localPath); + request.setFileToken(fileToken); + request.setRemotePath(Files1.getPath(remotePath, file.getOriginalFilename())); + request.setSize(file.getSize()); + requestFiles.add(request); + } + } + // 提交任务 + return batchUploadService.batchUpload(requestFiles); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/CommandTemplateController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/CommandTemplateController.java new file mode 100644 index 0000000..8d0f36a --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/CommandTemplateController.java @@ -0,0 +1,74 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.annotation.DemoDisableApi; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.entity.request.template.CommandTemplateRequest; +import cn.orionsec.ops.entity.vo.template.CommandTemplateVO; +import cn.orionsec.ops.service.api.CommandTemplateService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.List; + +@Api(tags = "命令模板") +@RestController +@RestWrapper +@RequestMapping("/orion/api/template") +public class CommandTemplateController { + + @Resource + private CommandTemplateService commandTemplateService; + + @DemoDisableApi + @PostMapping("/add") + @ApiOperation(value = "新增命令模板") + @EventLog(EventType.ADD_TEMPLATE) + public Long add(@RequestBody CommandTemplateRequest request) { + Valid.notBlank(request.getName()); + Valid.notBlank(request.getValue()); + return commandTemplateService.addTemplate(request); + } + + @DemoDisableApi + @PostMapping("/update") + @ApiOperation(value = "修改命令模板") + @EventLog(EventType.UPDATE_TEMPLATE) + public Integer update(@RequestBody CommandTemplateRequest request) { + Valid.notNull(request.getId()); + Valid.notBlank(request.getValue()); + return commandTemplateService.updateTemplate(request); + } + + @PostMapping("/list") + @ApiOperation(value = "获取命令模板列表") + public DataGrid list(@RequestBody CommandTemplateRequest request) { + return commandTemplateService.listTemplate(request); + } + + @PostMapping("/detail") + @ApiOperation(value = "获取命令模板详情") + public CommandTemplateVO detail(@RequestBody CommandTemplateRequest request) { + Long id = Valid.notNull(request.getId()); + return commandTemplateService.templateDetail(id); + } + + @DemoDisableApi + @PostMapping("/delete") + @ApiOperation(value = "删除命令模板") + @EventLog(EventType.DELETE_TEMPLATE) + public Integer delete(@RequestBody CommandTemplateRequest request) { + List idList = Valid.notNull(request.getIdList()); + return commandTemplateService.deleteTemplate(idList); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/CommonController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/CommonController.java new file mode 100644 index 0000000..a7278ff --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/CommonController.java @@ -0,0 +1,45 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.io.StreamReaders; +import cn.orionsec.ops.OrionApplication; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.user.RoleType; +import cn.orionsec.ops.entity.dto.user.UserDTO; +import cn.orionsec.ops.service.api.CommonService; +import cn.orionsec.ops.utils.Currents; +import com.alibaba.fastjson.JSON; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +@Api(tags = "公共接口") +@RestController +@RestWrapper +@RequestMapping("/orion/api/common") +public class CommonController { + + @Resource + private CommonService commonService; + + @GetMapping("/menu") + @ApiOperation(value = "获取菜单") + public List getMenu() throws IOException { + UserDTO user = Currents.getUser(); + String menuFile = RoleType.of(user.getRoleType()).getMenuPath(); + InputStream menu = OrionApplication.class.getResourceAsStream(menuFile); + if (menu == null) { + return Lists.empty(); + } + return JSON.parseArray(new String(StreamReaders.readAllBytes(menu))); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/DataClearController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/DataClearController.java new file mode 100644 index 0000000..d7e6714 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/DataClearController.java @@ -0,0 +1,91 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.DataClearRange; +import cn.orionsec.ops.constant.DataClearType; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.entity.request.data.DataClearRequest; +import cn.orionsec.ops.service.api.DataClearService; +import cn.orionsec.ops.utils.EventParamsHolder; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +@Api(tags = "数据清理") +@RestController +@RestWrapper +@RequestMapping("/orion/api/data-clear") +public class DataClearController { + + @Resource + private DataClearService dataClearService; + + @PostMapping("/clear") + @ApiOperation(value = "清理数据") + @EventLog(EventType.DATA_CLEAR) + public Integer clearData(@RequestBody DataClearRequest request) { + // 检查参数 + DataClearType dataClearType = this.validParams(request); + // 设置日志参数 + EventParamsHolder.addParam(EventKeys.LABEL, dataClearType.getLabel()); + // 清理 + switch (dataClearType) { + case BATCH_EXEC: + return dataClearService.clearBatchExec(request); + case TERMINAL_LOG: + return dataClearService.clearTerminalLog(request); + case SCHEDULER_RECORD: + return dataClearService.clearSchedulerRecord(request); + case APP_BUILD: + Valid.notNull(request.getProfileId()); + return dataClearService.clearAppBuild(request); + case APP_RELEASE: + Valid.notNull(request.getProfileId()); + return dataClearService.clearAppRelease(request); + case APP_PIPELINE_EXEC: + Valid.notNull(request.getProfileId()); + return dataClearService.clearAppPipeline(request); + case USER_EVENT_LOG: + return dataClearService.clearEventLog(request); + case MACHINE_ALARM_HISTORY: + Valid.notNull(request.getMachineId()); + return dataClearService.clearMachineAlarmHistory(request); + default: + throw Exceptions.unsupported(); + } + } + + /** + * 验证参数 + * + * @param request request + * @return clear type + */ + private DataClearType validParams(DataClearRequest request) { + DataClearRange range = Valid.notNull(DataClearRange.of(request.getRange())); + switch (range) { + case DAY: + Valid.gte(request.getReserveDay(), 0); + break; + case TOTAL: + Valid.gte(request.getReserveTotal(), 0); + break; + case REL_ID: + Valid.notEmpty(request.getRelIdList()); + break; + default: + } + return Valid.notNull(DataClearType.of(request.getClearType())); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/DataExportController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/DataExportController.java new file mode 100644 index 0000000..9fa8cd7 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/DataExportController.java @@ -0,0 +1,35 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.ops.annotation.DemoDisableApi; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.constant.ExportType; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.entity.request.data.DataExportRequest; +import cn.orionsec.ops.handler.exporter.IDataExporter; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Api(tags = "数据导出") +@RestController +@RequestMapping("/orion/api/data-export") +public class DataExportController { + + @DemoDisableApi + @PostMapping("/export") + @ApiOperation(value = "导出数据") + @EventLog(EventType.DATA_EXPORT) + public void exportData(@RequestBody DataExportRequest request, HttpServletResponse response) throws IOException { + ExportType exportType = Valid.notNull(ExportType.of(request.getExportType())); + IDataExporter.create(exportType, request, response).doExport(); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/DataImportController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/DataImportController.java new file mode 100644 index 0000000..20eee99 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/DataImportController.java @@ -0,0 +1,108 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.io.Streams; +import cn.orionsec.kit.office.excel.Excels; +import cn.orionsec.kit.web.servlet.web.Servlets; +import cn.orionsec.ops.OrionApplication; +import cn.orionsec.ops.annotation.*; +import cn.orionsec.ops.constant.ImportType; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.event.EventKeys; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.entity.importer.DataImportDTO; +import cn.orionsec.ops.entity.request.data.DataImportRequest; +import cn.orionsec.ops.entity.vo.data.DataImportCheckVO; +import cn.orionsec.ops.handler.importer.checker.IDataChecker; +import cn.orionsec.ops.handler.importer.impl.IDataImporter; +import cn.orionsec.ops.service.api.DataImportService; +import cn.orionsec.ops.utils.EventParamsHolder; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.apache.poi.ss.usermodel.Workbook; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; + +@Api(tags = "数据导入") +@RestController +@RestWrapper +@RequestMapping("/orion/api/data-import") +public class DataImportController { + + @Resource + private DataImportService dataImportService; + + @IgnoreWrapper + @IgnoreLog + @IgnoreAuth + @GetMapping("/get-template") + @ApiOperation(value = "获取导入模板") + public void getTemplate(Integer type, HttpServletResponse response) throws IOException { + ImportType importType = Valid.notNull(ImportType.of(type)); + String templateName = importType.getTemplateName(); + Servlets.setAttachmentHeader(response, templateName); + // 读取文件 + InputStream in = OrionApplication.class.getResourceAsStream(importType.getTemplatePath()); + ServletOutputStream out = response.getOutputStream(); + if (in == null) { + out.write(Strings.bytes(Strings.format(MessageConst.FILE_NOT_FOUND, templateName))); + return; + } + Streams.transfer(in, out); + } + + @PostMapping("/check-data") + @ApiOperation(value = "检查导入信息") + public DataImportCheckVO checkImportData(@RequestParam("file") MultipartFile file, + @RequestParam("type") Integer type, + @RequestParam(name = "protectPassword", required = false) String protectPassword) throws IOException { + ImportType importType = Valid.notNull(ImportType.of(type)); + Workbook workbook; + if (Strings.isBlank(protectPassword)) { + workbook = Excels.openWorkbook(file.getInputStream()); + } else { + workbook = Excels.openWorkbook(file.getInputStream(), protectPassword); + } + // 检查数据 + return IDataChecker.create(importType, workbook).doCheck(); + } + + @DemoDisableApi + @PostMapping("/import") + @ApiOperation(value = "导入数据") + @EventLog(EventType.DATA_IMPORT) + public HttpWrapper importData(@RequestBody DataImportRequest request) { + String token = Valid.notNull(request.getImportToken()); + // 读取导入数据 + DataImportDTO importData = dataImportService.checkImportToken(token); + // 执行导入操作 + IDataImporter.create(importData).doImport(); + // 设置日志参数 + ImportType importType = ImportType.of(importData.getType()); + EventParamsHolder.addParam(EventKeys.TOKEN, token); + EventParamsHolder.addParam(EventKeys.TYPE, importType.getType()); + EventParamsHolder.addParam(EventKeys.LABEL, importType.getLabel()); + return HttpWrapper.ok(); + } + + @PostMapping("/cancel-import") + @ApiOperation(value = "取消导入") + public HttpWrapper cancelImportData(@RequestBody DataImportRequest request) { + String token = request.getImportToken(); + if (Strings.isBlank(token)) { + return HttpWrapper.ok(); + } + dataImportService.clearImportToken(token); + return HttpWrapper.ok(); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/EventLogController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/EventLogController.java new file mode 100644 index 0000000..c173f2c --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/EventLogController.java @@ -0,0 +1,33 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.entity.request.user.EventLogRequest; +import cn.orionsec.ops.entity.vo.user.UserEventLogVO; +import cn.orionsec.ops.service.api.UserEventLogService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +@Api(tags = "操作日志") +@RestController +@RestWrapper +@RequestMapping("/orion/api/event-log") +public class EventLogController { + + @Resource + private UserEventLogService userEventLogService; + + @PostMapping("/list") + @ApiOperation(value = "获取操作日志列表") + public DataGrid getLogList(@RequestBody EventLogRequest request) { + return userEventLogService.getLogList(request); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/FileDownloadController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/FileDownloadController.java new file mode 100644 index 0000000..c742487 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/FileDownloadController.java @@ -0,0 +1,52 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.exception.NotFoundException; +import cn.orionsec.kit.web.servlet.web.Servlets; +import cn.orionsec.ops.annotation.IgnoreAuth; +import cn.orionsec.ops.annotation.IgnoreWrapper; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.download.FileDownloadType; +import cn.orionsec.ops.entity.request.file.FileDownloadRequest; +import cn.orionsec.ops.service.api.FileDownloadService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Api(tags = "文件下载") +@RestController +@RestWrapper +@RequestMapping("/orion/api/file-download") +public class FileDownloadController { + + @Resource + private FileDownloadService fileDownloadService; + + @PostMapping("/token") + @ApiOperation(value = "检查并获取下载文件token") + public String getDownloadToken(@RequestBody FileDownloadRequest request) { + Long id = Valid.notNull(request.getId()); + FileDownloadType type = Valid.notNull(FileDownloadType.of(request.getType())); + return fileDownloadService.getDownloadToken(id, type); + } + + @IgnoreWrapper + @IgnoreAuth + @GetMapping("/{token}/exec") + @ApiOperation(value = "下载文件") + public void downloadLogFile(@PathVariable String token, HttpServletResponse response) throws IOException { + try { + fileDownloadService.execDownload(token, response); + } catch (NotFoundException e) { + // 文件未找到 + Servlets.transfer(response, Const.EMPTY.getBytes(), Const.UNKNOWN); + } + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/FileTailController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/FileTailController.java new file mode 100644 index 0000000..d2f30b0 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/FileTailController.java @@ -0,0 +1,168 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.kit.lang.utils.Charsets; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.lang.utils.io.Streams; +import cn.orionsec.kit.web.servlet.web.Servlets; +import cn.orionsec.ops.annotation.DemoDisableApi; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.constant.tail.FileTailType; +import cn.orionsec.ops.entity.request.file.FileTailRequest; +import cn.orionsec.ops.entity.vo.tail.FileTailConfigVO; +import cn.orionsec.ops.entity.vo.tail.FileTailVO; +import cn.orionsec.ops.service.api.FileTailService; +import cn.orionsec.ops.utils.Utils; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +@Api(tags = "日志文件tail") +@RestController +@RestWrapper +@RequestMapping("/orion/api/file-tail") +public class FileTailController { + + @Resource + private FileTailService fileTailService; + + @PostMapping("/token") + @ApiOperation(value = "检查并获取日志文件token") + public FileTailVO getTailToken(@RequestBody FileTailRequest request) { + FileTailType type = Valid.notNull(FileTailType.of(request.getType())); + Long relId = Valid.notNull(request.getRelId()); + return fileTailService.getTailToken(type, relId); + } + + @DemoDisableApi + @PostMapping("/add") + @ApiOperation(value = "添加日志文件") + @EventLog(EventType.ADD_TAIL_FILE) + public Long addTailFile(@RequestBody FileTailRequest request) { + Valid.notBlank(request.getName()); + Valid.notNull(request.getMachineId()); + Valid.notBlank(request.getPath()); + Valid.notNull(request.getOffset()); + Valid.notNull(request.getCharset()); + Valid.isTrue(Files1.isPath(request.getPath())); + Valid.isTrue(Charsets.isSupported(request.getCharset())); + return fileTailService.insertTailFile(request); + } + + @DemoDisableApi + @PostMapping("/update") + @ApiOperation(value = "修改日志文件") + @EventLog(EventType.UPDATE_TAIL_FILE) + public Integer updateTailFile(@RequestBody FileTailRequest request) { + Valid.notNull(request.getId()); + if (!Strings.isBlank(request.getPath())) { + Valid.isTrue(Files1.isPath(request.getPath())); + } + if (!Strings.isBlank(request.getCharset())) { + Valid.isTrue(Charsets.isSupported(request.getCharset())); + } + return fileTailService.updateTailFile(request); + } + + @PostMapping("/upload") + @ApiOperation(value = "上传日志文件") + @EventLog(EventType.UPLOAD_TAIL_FILE) + public HttpWrapper uploadFile(@RequestParam("files") List files) throws IOException { + // 检查文件 + Valid.notEmpty(files); + List requestFiles = Lists.newList(); + for (MultipartFile file : files) { + String fileName = file.getOriginalFilename(); + if (fileName == null) { + fileName = Utils.getRandomSuffix() + Const.DOT + Const.SUFFIX_LOG; + } + Path localAbsolutePath = Paths.get(SystemEnvAttr.TAIL_FILE_UPLOAD_PATH.getValue(), Utils.getRandomSuffix() + Const.DASHED + fileName); + Files1.touch(localAbsolutePath); + file.transferTo(localAbsolutePath); + // 设置文件数据 + FileTailRequest request = new FileTailRequest(); + request.setName(fileName); + request.setPath(Files1.getPath(localAbsolutePath.toString())); + requestFiles.add(request); + } + // 保存 + fileTailService.uploadTailFiles(requestFiles); + return HttpWrapper.ok(); + } + + @DemoDisableApi + @PostMapping("/delete") + @ApiOperation(value = "删除日志文件") + @EventLog(EventType.DELETE_TAIL_FILE) + public Integer deleteTailFile(@RequestBody FileTailRequest request) { + List idList = Valid.notEmpty(request.getIdList()); + return fileTailService.deleteTailFile(idList); + } + + @PostMapping("/list") + @ApiOperation(value = "获取日志文件列表") + public DataGrid tailFileList(@RequestBody FileTailRequest request) { + return fileTailService.tailFileList(request); + } + + @PostMapping("/detail") + @ApiOperation(value = "获取日志文件详情") + public FileTailVO tailFileDetail(@RequestBody FileTailRequest request) { + Long id = Valid.notNull(request.getId()); + return fileTailService.tailFileDetail(id); + } + + @PostMapping("/clean-ansi") + @ApiOperation(value = "清除ANSI码") + public void cleanAnsiCode(@RequestParam("file") MultipartFile file, HttpServletResponse response) throws IOException { + // 设置 http 响应头 + String fileName = file.getOriginalFilename(); + if (fileName == null) { + fileName = Utils.getRandomSuffix() + Const.DOT + Const.SUFFIX_LOG; + } + Servlets.setAttachmentHeader(response, fileName); + // 读取文件 + try (InputStream in = file.getInputStream()) { + byte[] bytes = Streams.toByteArray(in); + String clearValue = Utils.cleanStainAnsiCode(Strings.str(bytes)); + ServletOutputStream out = response.getOutputStream(); + out.write(Strings.bytes(clearValue)); + out.flush(); + } + } + + @PostMapping("/config") + @ApiOperation(value = "获取机器默认配置") + public FileTailConfigVO getMachineConfig(@RequestBody FileTailRequest request) { + Long machineId = Valid.notNull(request.getMachineId()); + return fileTailService.getMachineConfig(machineId); + } + + @PostMapping("/write") + @ApiOperation(value = "写入命令") + public void write(@RequestBody FileTailRequest request) { + String token = Valid.notBlank(request.getToken()); + String command = Valid.notEmpty(request.getCommand()); + fileTailService.writeCommand(token, command); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/HistoryValueController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/HistoryValueController.java new file mode 100644 index 0000000..2b94d59 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/HistoryValueController.java @@ -0,0 +1,48 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.ops.annotation.DemoDisableApi; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.history.HistoryValueType; +import cn.orionsec.ops.entity.request.history.HistoryValueRequest; +import cn.orionsec.ops.entity.vo.history.HistoryValueVO; +import cn.orionsec.ops.service.api.HistoryValueService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +@Api(tags = "历史值") +@RestController +@RestWrapper +@RequestMapping("/orion/api/history-value") +public class HistoryValueController { + + @Resource + private HistoryValueService historyValueService; + + @PostMapping("/list") + @ApiOperation(value = "历史值列表") + public DataGrid list(@RequestBody HistoryValueRequest request) { + Valid.notNull(request.getValueId()); + Valid.notNull(HistoryValueType.of(request.getValueType())); + return historyValueService.list(request); + } + + @DemoDisableApi + @PostMapping("/rollback") + @ApiOperation(value = "回滚历史值") + public HttpWrapper rollback(@RequestBody HistoryValueRequest request) { + Long id = Valid.notNull(request.getId()); + historyValueService.rollback(id); + return HttpWrapper.ok(); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineAlarmConfigController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineAlarmConfigController.java new file mode 100644 index 0000000..0241ac6 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineAlarmConfigController.java @@ -0,0 +1,84 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.entity.request.machine.MachineAlarmConfigRequest; +import cn.orionsec.ops.entity.request.machine.MachineAlarmHistoryRequest; +import cn.orionsec.ops.entity.vo.machine.MachineAlarmConfigWrapperVO; +import cn.orionsec.ops.entity.vo.machine.MachineAlarmHistoryVO; +import cn.orionsec.ops.service.api.MachineAlarmConfigService; +import cn.orionsec.ops.service.api.MachineAlarmHistoryService; +import cn.orionsec.ops.service.api.MachineAlarmService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +@Api(tags = "机器报警") +@RestController +@RestWrapper +@RequestMapping("/orion/api/machine-alarm") +public class MachineAlarmConfigController { + + @Resource + private MachineAlarmConfigService machineAlarmConfigService; + + @Resource + private MachineAlarmHistoryService machineAlarmHistoryService; + + @Resource + private MachineAlarmService machineAlarmService; + + @GetMapping("/get-config") + @ApiOperation(value = "获取报警配置") + public MachineAlarmConfigWrapperVO getAlarmConfig(@RequestParam("machineId") Long machineId) { + return machineAlarmConfigService.getAlarmConfigInfo(machineId); + } + + @PostMapping("/set-alarm-config") + @ApiOperation(value = "设置报警配置") + @EventLog(EventType.SET_MACHINE_ALARM_CONFIG) + public HttpWrapper setAlarmConfig(@RequestBody MachineAlarmConfigRequest request) { + Valid.notNull(request.getMachineId()); + Valid.gte(request.getAlarmThreshold(), 0D, MessageConst.INVALID_PARAM); + Valid.gte(request.getTriggerThreshold(), 0, MessageConst.INVALID_PARAM); + Valid.gte(request.getNotifySilence(), 0, MessageConst.INVALID_PARAM); + machineAlarmConfigService.setAlarmConfig(request); + return HttpWrapper.ok(); + } + + @PostMapping("/set-group-config") + @ApiOperation(value = "设置报警联系组") + @EventLog(EventType.SET_MACHINE_ALARM_GROUP) + public HttpWrapper setAlarmGroup(@RequestBody MachineAlarmConfigRequest request) { + Long machineId = Valid.notNull(request.getMachineId()); + List groupIdList = Valid.notEmpty(request.getGroupIdList()); + machineAlarmConfigService.setAlarmGroup(machineId, groupIdList); + return HttpWrapper.ok(); + } + + @PostMapping("/history") + @ApiOperation(value = "获取报警历史") + public DataGrid getAlarmHistory(@RequestBody MachineAlarmHistoryRequest request) { + Valid.notNull(request.getMachineId()); + return machineAlarmHistoryService.getAlarmHistory(request); + } + + @PostMapping("/trigger-alarm-notify") + @ApiOperation(value = "触发报警通知") + @EventLog(EventType.RENOTIFY_MACHINE_ALARM_GROUP) + public HttpWrapper triggerMachineAlarm(@RequestBody MachineAlarmHistoryRequest request) { + Long id = Valid.notNull(request.getId()); + machineAlarmService.triggerMachineAlarm(id); + return HttpWrapper.ok(); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineEnvController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineEnvController.java new file mode 100644 index 0000000..861c49d --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineEnvController.java @@ -0,0 +1,122 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.collect.MutableLinkedHashMap; +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.ops.annotation.DemoDisableApi; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.env.EnvViewType; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.entity.request.machine.MachineEnvRequest; +import cn.orionsec.ops.entity.vo.machine.MachineEnvVO; +import cn.orionsec.ops.service.api.MachineEnvService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; + +@Api(tags = "机器环境变量") +@RestController +@RestWrapper +@RequestMapping("/orion/api/machine-env") +public class MachineEnvController { + + @Resource + private MachineEnvService machineEnvService; + + @DemoDisableApi + @PostMapping("/add") + @ApiOperation(value = "添加环境变量") + public Long add(@RequestBody MachineEnvRequest request) { + Valid.notBlank(request.getKey()); + Valid.notNull(request.getValue()); + Valid.notNull(request.getMachineId()); + return machineEnvService.addEnv(request); + } + + @DemoDisableApi + @PostMapping("/update") + @ApiOperation(value = "修改环境变量") + public Integer update(@RequestBody MachineEnvRequest request) { + Valid.notNull(request.getId()); + Valid.notNull(request.getValue()); + return machineEnvService.updateEnv(request); + } + + @DemoDisableApi + @PostMapping("/delete") + @ApiOperation(value = "删除环境变量") + @EventLog(EventType.DELETE_MACHINE_ENV) + public Integer delete(@RequestBody MachineEnvRequest request) { + List idList = Valid.notEmpty(request.getIdList()); + return machineEnvService.deleteEnv(idList); + } + + @PostMapping("/list") + @ApiOperation(value = "获取环境变量列表") + public DataGrid list(@RequestBody MachineEnvRequest request) { + Valid.notNull(request.getMachineId()); + return machineEnvService.listEnv(request); + } + + @PostMapping("/detail") + @ApiOperation(value = "获取环境变量详情") + public MachineEnvVO detail(@RequestBody MachineEnvRequest request) { + Long id = Valid.notNull(request.getId()); + return machineEnvService.getEnvDetail(id); + } + + @DemoDisableApi + @PostMapping("/sync") + @ApiOperation(value = "同步环境变量") + @EventLog(EventType.SYNC_MACHINE_ENV) + public HttpWrapper sync(@RequestBody MachineEnvRequest request) { + Valid.notNull(request.getId()); + Valid.notNull(request.getMachineId()); + Valid.notEmpty(request.getTargetMachineIdList()); + machineEnvService.syncMachineEnv(request); + return HttpWrapper.ok(); + } + + @PostMapping("/view") + @ApiOperation(value = "获取环境变量视图") + public String view(@RequestBody MachineEnvRequest request) { + Valid.notNull(request.getMachineId()); + EnvViewType viewType = Valid.notNull(EnvViewType.of(request.getViewType())); + request.setLimit(Const.N_100000); + // 查询列表 + Map env = Maps.newLinkedMap(); + machineEnvService.listEnv(request).forEach(e -> env.put(e.getKey(), e.getValue())); + return viewType.toValue(env); + } + + @DemoDisableApi + @PostMapping("/view-save") + @ApiOperation(value = "保存环境变量视图") + public Integer viewSave(@RequestBody MachineEnvRequest request) { + Long machineId = Valid.notNull(request.getMachineId()); + String value = Valid.notBlank(request.getValue()); + EnvViewType viewType = Valid.notNull(EnvViewType.of(request.getViewType())); + try { + MutableLinkedHashMap result = viewType.toMap(value); + machineEnvService.saveEnv(machineId, result); + return result.size(); + } catch (Exception e) { + throw Exceptions.argument(MessageConst.PARSE_ERROR, e); + } + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineGroupController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineGroupController.java new file mode 100644 index 0000000..ebb2006 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineGroupController.java @@ -0,0 +1,86 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.common.TreeMoveType; +import cn.orionsec.ops.entity.request.machine.MachineGroupRelRequest; +import cn.orionsec.ops.entity.request.machine.MachineGroupRequest; +import cn.orionsec.ops.entity.vo.machine.MachineGroupTreeVO; +import cn.orionsec.ops.service.api.MachineGroupRelService; +import cn.orionsec.ops.service.api.MachineGroupService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +@Api(tags = "机器分组") +@RestController +@RestWrapper +@RequestMapping("/orion/api/machine-group") +public class MachineGroupController { + + @Resource + private MachineGroupService machineGroupService; + + @Resource + private MachineGroupRelService machineGroupRelService; + + @PostMapping("/add") + @ApiOperation(value = "添加分组") + public Long addGroup(@RequestBody MachineGroupRequest request) { + Valid.notNull(request.getParentId()); + Valid.notBlank(request.getName()); + return machineGroupService.addGroup(request); + } + + @PostMapping("/delete") + @ApiOperation(value = "删除分组") + public Integer deleteGroup(@RequestBody MachineGroupRequest request) { + List idList = Valid.notEmpty(request.getIdList()); + return machineGroupService.deleteGroup(idList); + } + + @PostMapping("/move") + @ApiOperation(value = "移动分组") + public HttpWrapper moveGroup(@RequestBody MachineGroupRequest request) { + Valid.allNotNull(request.getId(), request.getTargetId(), TreeMoveType.of(request.getMoveType())); + machineGroupService.moveGroup(request); + return HttpWrapper.ok(); + } + + @PostMapping("/rename") + @ApiOperation(value = "修改分组名称") + public Integer renameGroup(@RequestBody MachineGroupRequest request) { + Long id = Valid.notNull(request.getId()); + String name = Valid.notBlank(request.getName()); + return machineGroupService.renameGroup(id, name); + } + + @GetMapping("/tree") + @ApiOperation(value = "获取机器分组树") + public List getRootTree() { + return machineGroupService.getRootTree(); + } + + @PostMapping("/add-machine") + @ApiOperation(value = "组内添加机器") + public HttpWrapper addMachineRelByGroup(@RequestBody MachineGroupRelRequest request) { + Long groupId = Valid.notNull(request.getGroupId()); + List machineIdList = Valid.notEmpty(request.getMachineIdList()); + machineGroupRelService.addMachineRelByGroup(groupId, machineIdList); + return HttpWrapper.ok(); + } + + @PostMapping("/delete-machine") + @ApiOperation(value = "删除组内机器") + public Integer deleteByGroupMachineId(@RequestBody MachineGroupRelRequest request) { + List groupIdList = Valid.notEmpty(request.getGroupIdList()); + List machineIdList = Valid.notEmpty(request.getMachineIdList()); + return machineGroupRelService.deleteByGroupMachineId(groupIdList, machineIdList); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineInfoController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineInfoController.java new file mode 100644 index 0000000..e34f75c --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineInfoController.java @@ -0,0 +1,154 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.ops.annotation.DemoDisableApi; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.constant.machine.MachineAuthType; +import cn.orionsec.ops.entity.request.machine.MachineInfoRequest; +import cn.orionsec.ops.entity.vo.machine.MachineInfoVO; +import cn.orionsec.ops.service.api.MachineInfoService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.List; + +@Api(tags = "机器信息") +@RestController +@RestWrapper +@RequestMapping("/orion/api/machine") +public class MachineInfoController { + + @Resource + private MachineInfoService machineInfoService; + + @DemoDisableApi + @PostMapping("/add") + @ApiOperation(value = "添加机器") + @EventLog(EventType.ADD_MACHINE) + public Long add(@RequestBody MachineInfoRequest request) { + this.check(request); + MachineAuthType machineAuthTypeEnum = Valid.notNull(MachineAuthType.of(request.getAuthType())); + if (MachineAuthType.PASSWORD.equals(machineAuthTypeEnum)) { + Valid.notBlank(request.getPassword()); + } + return machineInfoService.addMachine(request); + } + + @DemoDisableApi + @PostMapping("/update") + @ApiOperation(value = "修改机器") + @EventLog(EventType.UPDATE_MACHINE) + public int update(@RequestBody MachineInfoRequest request) { + Valid.notNull(request.getId()); + this.check(request); + return machineInfoService.updateMachine(request); + } + + @DemoDisableApi + @PostMapping("/delete") + @ApiOperation(value = "删除机器") + @EventLog(EventType.DELETE_MACHINE) + public Integer delete(@RequestBody MachineInfoRequest request) { + List idList = Valid.notEmpty(request.getIdList()); + // 设置日志参数 + return machineInfoService.deleteMachine(idList); + } + + @DemoDisableApi + @PostMapping("/update-status") + @ApiOperation(value = "停用/启用机器") + @EventLog(EventType.CHANGE_MACHINE_STATUS) + public Integer status(@RequestBody MachineInfoRequest request) { + List idList = Valid.notEmpty(request.getIdList()); + Integer status = Valid.notNull(request.getStatus()); + Valid.in(status, Const.ENABLE, Const.DISABLE); + return machineInfoService.updateStatus(idList, status); + } + + @PostMapping("/list") + @ApiOperation(value = "获取机器列表") + public DataGrid list(@RequestBody MachineInfoRequest request) { + return machineInfoService.listMachine(request); + } + + @PostMapping("/detail") + @ApiOperation(value = "获取机器详情") + public MachineInfoVO detail(@RequestBody MachineInfoRequest request) { + Long id = Valid.notNull(request.getId()); + return machineInfoService.machineDetail(id); + } + + @DemoDisableApi + @PostMapping("/copy") + @ApiOperation(value = "复制机器") + @EventLog(EventType.COPY_MACHINE) + public Long copy(@RequestBody MachineInfoRequest request) { + Long id = Valid.notNull(request.getId()); + return machineInfoService.copyMachine(id); + } + + @PostMapping("/test-ping") + @ApiOperation(value = "尝试ping机器") + public HttpWrapper ping(@RequestBody MachineInfoRequest request) { + Long id = Valid.notNull(request.getId()); + machineInfoService.testPing(id); + return HttpWrapper.ok(); + } + + @PostMapping("/test-connect") + @ApiOperation(value = "尝试连接机器") + public HttpWrapper connect(@RequestBody MachineInfoRequest request) { + Long id = Valid.notNull(request.getId()); + machineInfoService.testConnect(id); + return HttpWrapper.ok(); + } + + @PostMapping("/direct-test-ping") + @ApiOperation(value = "直接尝试ping机器") + public HttpWrapper directPing(@RequestBody MachineInfoRequest request) { + String host = Valid.notBlank(request.getHost()); + machineInfoService.testPing(host); + return HttpWrapper.ok(); + } + + @PostMapping("/direct-test-connect") + @ApiOperation(value = "直接尝试连接机器") + public HttpWrapper directConnect(@RequestBody MachineInfoRequest request) { + Valid.allNotBlank(request.getHost(), request.getUsername()); + Integer sshPort = Valid.notNull(request.getSshPort()); + Valid.inRange(sshPort, 2, 65534, MessageConst.ABSENT_PARAM); + MachineAuthType authType = Valid.notNull(MachineAuthType.of(request.getAuthType())); + if (MachineAuthType.PASSWORD.equals(authType)) { + Valid.notBlank(request.getPassword()); + } else if (MachineAuthType.SECRET_KEY.equals(authType)) { + Valid.notNull(request.getKeyId()); + } + machineInfoService.testConnect(request); + return HttpWrapper.ok(); + } + + /** + * 合法校验 + */ + private void check(MachineInfoRequest request) { + Valid.notBlank(request.getHost()); + Integer sshPort = Valid.notNull(request.getSshPort()); + Valid.inRange(sshPort, 2, 65534, MessageConst.ABSENT_PARAM); + Valid.notBlank(request.getName()); + Valid.notBlank(request.getTag()); + Valid.notBlank(request.getUsername()); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineKeyController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineKeyController.java new file mode 100644 index 0000000..cfac87b --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineKeyController.java @@ -0,0 +1,85 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.ops.annotation.DemoDisableApi; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.entity.request.machine.MachineKeyRequest; +import cn.orionsec.ops.entity.vo.machine.MachineSecretKeyVO; +import cn.orionsec.ops.service.api.MachineKeyService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.List; + +@Api(tags = "机器密钥") +@RestController +@RestWrapper +@RequestMapping("/orion/api/machine-key") +public class MachineKeyController { + + @Resource + private MachineKeyService machineKeyService; + + @DemoDisableApi + @PostMapping("/add") + @ApiOperation(value = "添加机器密钥") + @EventLog(EventType.ADD_MACHINE_KEY) + public Long addKey(@RequestBody MachineKeyRequest request) { + Valid.notBlank(request.getName()); + Valid.notBlank(request.getFile()); + return machineKeyService.addSecretKey(request); + } + + @DemoDisableApi + @PostMapping("/update") + @ApiOperation(value = "更新机器密钥") + @EventLog(EventType.UPDATE_MACHINE_KEY) + public Integer updateKey(@RequestBody MachineKeyRequest request) { + Valid.notNull(request.getId()); + return machineKeyService.updateSecretKey(request); + } + + @DemoDisableApi + @PostMapping("/remove") + @ApiOperation(value = "删除机器密钥") + @EventLog(EventType.DELETE_MACHINE_KEY) + public Integer removeKey(@RequestBody MachineKeyRequest request) { + List idList = Valid.notEmpty(request.getIdList()); + return machineKeyService.removeSecretKey(idList); + } + + @PostMapping("/list") + @ApiOperation(value = "获取机器密钥列表") + public DataGrid listKeys(@RequestBody MachineKeyRequest request) { + return machineKeyService.listKeys(request); + } + + @PostMapping("/detail") + @ApiOperation(value = "获取机器密钥详情") + public MachineSecretKeyVO getKeyDetail(@RequestBody MachineKeyRequest request) { + Long id = Valid.notNull(request.getId()); + return machineKeyService.getKeyDetail(id); + } + + @DemoDisableApi + @PostMapping("/bind") + @ApiOperation(value = "绑定机器密钥") + @EventLog(EventType.BIND_MACHINE_KEY) + public HttpWrapper bindMachineKey(@RequestBody MachineKeyRequest request) { + Long id = Valid.notNull(request.getId()); + List machineIdList = Valid.notEmpty(request.getMachineIdList()); + machineKeyService.bindMachineKey(id, machineIdList); + return HttpWrapper.ok(); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineMonitorController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineMonitorController.java new file mode 100644 index 0000000..076db23 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineMonitorController.java @@ -0,0 +1,75 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Booleans; +import cn.orionsec.ops.annotation.DemoDisableApi; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.entity.request.machine.MachineMonitorRequest; +import cn.orionsec.ops.entity.vo.machine.MachineMonitorVO; +import cn.orionsec.ops.service.api.MachineMonitorService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +@Api(tags = "机器监控") +@RestController +@RestWrapper +@RequestMapping("/orion/api/monitor") +public class MachineMonitorController { + + @Resource + private MachineMonitorService machineMonitorService; + + @PostMapping("/list") + @ApiOperation(value = "查询监控列表") + public DataGrid getMonitorList(@RequestBody MachineMonitorRequest request) { + return machineMonitorService.getMonitorList(request); + } + + @GetMapping("/get-config") + @ApiOperation(value = "查询监控配置") + public MachineMonitorVO getMonitorConfig(@RequestParam Long machineId) { + return machineMonitorService.getMonitorConfig(machineId); + } + + @PostMapping("/set-config") + @ApiOperation(value = "设置监控配置") + @EventLog(EventType.UPDATE_MACHINE_MONITOR_CONFIG) + public MachineMonitorVO setMonitorConfig(@RequestBody MachineMonitorRequest request) { + Valid.notNull(request.getId()); + Valid.notBlank(request.getUrl()); + Valid.notBlank(request.getAccessToken()); + return machineMonitorService.updateMonitorConfig(request); + } + + @PostMapping("/test") + @ApiOperation(value = "测试连接监控") + public String testConnect(@RequestBody MachineMonitorRequest request) { + String url = Valid.notBlank(request.getUrl()); + String accessToken = Valid.notBlank(request.getAccessToken()); + return machineMonitorService.getMonitorVersion(url, accessToken); + } + + @DemoDisableApi + @PostMapping("/install") + @ApiOperation(value = "安装监控插件") + @EventLog(EventType.INSTALL_UPGRADE_MACHINE_MONITOR) + public MachineMonitorVO installMonitorAgent(@RequestBody MachineMonitorRequest request) { + Long machineId = Valid.notNull(request.getMachineId()); + return machineMonitorService.installMonitorAgent(machineId, Booleans.isTrue(request.getUpgrade())); + } + + @PostMapping("/check") + @ApiOperation(value = "检查监控插件状态") + public MachineMonitorVO checkMonitorStatus(@RequestBody MachineMonitorRequest request) { + Long machineId = Valid.notNull(request.getMachineId()); + return machineMonitorService.checkMonitorStatus(machineId); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineMonitorEndpointController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineMonitorEndpointController.java new file mode 100644 index 0000000..e6b18ec --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineMonitorEndpointController.java @@ -0,0 +1,95 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.entity.request.machine.MachineMonitorEndpointRequest; +import cn.orionsec.ops.service.api.MachineMonitorEndpointService; +import cn.orionsec.ops.utils.Valid; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; + +@Api(tags = "机器监控端点") +@RestController +@RestWrapper +@RequestMapping("/orion/api/monitor-endpoint") +public class MachineMonitorEndpointController { + + @Resource + private MachineMonitorEndpointService machineMonitorEndpointService; + + @GetMapping("/ping") + @ApiOperation(value = "ping") + public Integer sendPing(@RequestParam("machineId") Long machineId) { + return machineMonitorEndpointService.ping(machineId); + } + + @GetMapping("/metrics") + @ApiOperation(value = "获取机器基本指标") + public JSONObject getBaseMetrics(@RequestParam("machineId") Long machineId) { + return machineMonitorEndpointService.getBaseMetrics(machineId); + } + + @GetMapping("/load") + @ApiOperation(value = "获取系统负载") + public JSONObject getSystemLoad(@RequestParam("machineId") Long machineId) { + return machineMonitorEndpointService.getSystemLoad(machineId); + } + + @GetMapping("/top") + @ApiOperation(value = "获取top进程") + public JSONArray getTopProcesses(@RequestParam("machineId") Long machineId, String name) { + return machineMonitorEndpointService.getTopProcesses(machineId, name); + } + + @GetMapping("/disk-name") + @ApiOperation(value = "获取磁盘名称") + public JSONArray getCpuStatistics(@RequestParam("machineId") Long machineId) { + return machineMonitorEndpointService.getDiskName(machineId); + } + + @PostMapping("/chart-cpu") + @ApiOperation(value = "获取cpu图表") + public JSONObject getCpuStatistics(@RequestBody MachineMonitorEndpointRequest request) { + this.validChartParams(request); + return machineMonitorEndpointService.getCpuChart(request); + } + + @PostMapping("/chart-memory") + @ApiOperation(value = "获取内存图表") + public JSONObject getMemoryStatistics(@RequestBody MachineMonitorEndpointRequest request) { + this.validChartParams(request); + return machineMonitorEndpointService.getMemoryChart(request); + } + + @PostMapping("/chart-net") + @ApiOperation(value = "获取网络图表") + public JSONObject getNetStatistics(@RequestBody MachineMonitorEndpointRequest request) { + this.validChartParams(request); + return machineMonitorEndpointService.getNetChart(request); + } + + @PostMapping("/chart-disk") + @ApiOperation(value = "获取磁盘图表") + public JSONObject getDiskStatistics(@RequestBody MachineMonitorEndpointRequest request) { + this.validChartParams(request); + return machineMonitorEndpointService.getDiskChart(request); + } + + /** + * 验证参数 + * + * @param request request + */ + private void validChartParams(MachineMonitorEndpointRequest request) { + Valid.notNull(request.getMachineId()); + Valid.notNull(request.getGranularity()); + Valid.notNull(request.getStartRange()); + Valid.notNull(request.getEndRange()); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineProxyController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineProxyController.java new file mode 100644 index 0000000..7cebf34 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineProxyController.java @@ -0,0 +1,88 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.ops.annotation.DemoDisableApi; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.constant.machine.ProxyType; +import cn.orionsec.ops.entity.request.machine.MachineProxyRequest; +import cn.orionsec.ops.entity.vo.machine.MachineProxyVO; +import cn.orionsec.ops.service.api.MachineProxyService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.List; + +@Api(tags = "机器代理") +@RestController +@RestWrapper +@RequestMapping("/orion/api/machine-proxy") +public class MachineProxyController { + + @Resource + private MachineProxyService machineProxyService; + + @DemoDisableApi + @PostMapping("/add") + @ApiOperation(value = "添加机器代理") + @EventLog(EventType.ADD_MACHINE_PROXY) + public Long addProxy(@RequestBody MachineProxyRequest request) { + request.setId(null); + this.check(request); + if (!Strings.isBlank(request.getUsername())) { + Valid.notNull(request.getPassword()); + } + return machineProxyService.addProxy(request); + } + + @DemoDisableApi + @PostMapping("/update") + @ApiOperation(value = "修改机器代理") + @EventLog(EventType.UPDATE_MACHINE_PROXY) + public Integer update(@RequestBody MachineProxyRequest request) { + Valid.notNull(request.getId()); + this.check(request); + return machineProxyService.updateProxy(request); + } + + @PostMapping("/list") + @ApiOperation(value = "获取机器代理列表") + public DataGrid list(@RequestBody MachineProxyRequest request) { + return machineProxyService.listProxy(request); + } + + @PostMapping("/detail") + @ApiOperation(value = "获取机器代理详情") + public MachineProxyVO detail(@RequestBody MachineProxyRequest request) { + Long id = Valid.notNull(request.getId()); + return machineProxyService.getProxyDetail(id); + } + + @DemoDisableApi + @PostMapping("/delete") + @ApiOperation(value = "删除机器代理") + @EventLog(EventType.DELETE_MACHINE_PROXY) + public Integer delete(@RequestBody MachineProxyRequest request) { + List idList = Valid.notEmpty(request.getIdList()); + return machineProxyService.deleteProxy(idList); + } + + /** + * 合法校验 + */ + private void check(MachineProxyRequest request) { + Valid.notBlank(request.getHost()); + Valid.notNull(request.getPort()); + Valid.notNull(ProxyType.of(request.getType())); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineTerminalController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineTerminalController.java new file mode 100644 index 0000000..b8bb8be --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/MachineTerminalController.java @@ -0,0 +1,142 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.Strings; +import cn.orionsec.kit.lang.utils.codec.Base64s; +import cn.orionsec.kit.lang.utils.io.FileReaders; +import cn.orionsec.kit.net.host.ssh.TerminalType; +import cn.orionsec.ops.annotation.DemoDisableApi; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.RequireRole; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.ResultCode; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.constant.user.RoleType; +import cn.orionsec.ops.entity.request.machine.MachineTerminalLogRequest; +import cn.orionsec.ops.entity.request.machine.MachineTerminalManagerRequest; +import cn.orionsec.ops.entity.request.machine.MachineTerminalRequest; +import cn.orionsec.ops.entity.vo.machine.*; +import cn.orionsec.ops.handler.terminal.manager.TerminalSessionManager; +import cn.orionsec.ops.service.api.MachineTerminalService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; + +@Api(tags = "机器终端") +@RestController +@RestWrapper +@RequestMapping("/orion/api/terminal") +public class MachineTerminalController { + + @Resource + private MachineTerminalService machineTerminalService; + + @Resource + private TerminalSessionManager terminalSessionManager; + + @PostMapping("/access") + @ApiOperation(value = "获取终端accessToken") + @EventLog(EventType.OPEN_TERMINAL) + public TerminalAccessVO getTerminalAccessToken(@RequestBody MachineTerminalRequest request) { + Long machineId = Valid.notNull(request.getMachineId()); + return machineTerminalService.getAccessConfig(machineId); + } + + @GetMapping("/support/pty") + @ApiOperation(value = "获取支持的终端类型") + public String[] getSupportedPty() { + return Arrays.stream(TerminalType.values()) + .map(TerminalType::getType) + .toArray(String[]::new); + } + + @GetMapping("/get/{machineId}") + @ApiOperation(value = "获取终端配置") + public MachineTerminalVO getSetting(@PathVariable Long machineId) { + return machineTerminalService.getMachineConfig(machineId); + } + + @PostMapping("/update") + @ApiOperation(value = "修改终端配置") + @EventLog(EventType.UPDATE_TERMINAL_CONFIG) + public Integer updateSetting(@RequestBody MachineTerminalRequest request) { + Valid.notNull(request.getId()); + String terminalType = request.getTerminalType(); + if (!Strings.isBlank(terminalType)) { + Valid.notNull(TerminalType.of(terminalType), MessageConst.INVALID_PTY); + } + if (request.getEnableWebLink() != null) { + Valid.in(request.getEnableWebLink(), Const.ENABLE, Const.DISABLE); + } + return machineTerminalService.updateSetting(request); + } + + @PostMapping("/log/list") + @ApiOperation(value = "获取终端日志列表") + public DataGrid accessLogList(@RequestBody MachineTerminalLogRequest request) { + return machineTerminalService.listAccessLog(request); + } + + @PostMapping("/log/delete") + @ApiOperation(value = "删除终端日志") + @EventLog(EventType.DELETE_TERMINAL_LOG) + public Integer deleteLog(@RequestBody MachineTerminalLogRequest request) { + List idList = Valid.notEmpty(request.getIdList()); + return machineTerminalService.deleteTerminalLog(idList); + } + + @PostMapping("/log/screen") + @ApiOperation(value = "获取终端录屏文件 base64") + public String getLogScreen(@RequestBody MachineTerminalLogRequest request) { + Long id = Valid.notNull(request.getId()); + String path = machineTerminalService.getTerminalScreenFilePath(id); + if (path == null) { + throw Exceptions.httpWrapper(HttpWrapper.of(ResultCode.FILE_MISSING)); + } + Path file = Paths.get(path); + if (!Files.exists(file)) { + throw Exceptions.httpWrapper(HttpWrapper.of(ResultCode.FILE_MISSING)); + } + return Base64s.encodeToString(FileReaders.readAllBytesFast(path)); + } + + @PostMapping("/manager/session") + @ApiOperation(value = "获取终端会话列表") + @RequireRole(RoleType.ADMINISTRATOR) + public DataGrid sessionList(@RequestBody MachineTerminalManagerRequest request) { + return terminalSessionManager.getOnlineTerminal(request); + } + + @DemoDisableApi + @PostMapping("/manager/offline") + @ApiOperation(value = "强制下线终端会话") + @RequireRole(RoleType.ADMINISTRATOR) + @EventLog(EventType.FORCE_OFFLINE_TERMINAL) + public void forceOffline(@RequestBody MachineTerminalManagerRequest request) { + String token = Valid.notBlank(request.getToken()); + terminalSessionManager.forceOffline(token); + } + + @PostMapping("/manager/watcher") + @ApiOperation(value = "获取终端监视token") + @RequireRole(RoleType.ADMINISTRATOR) + public TerminalWatcherVO getTerminalWatcherToken(@RequestBody MachineTerminalManagerRequest request) { + String token = Valid.notBlank(request.getToken()); + Integer readonly = Valid.notNull(request.getReadonly()); + return terminalSessionManager.getWatcherToken(token, readonly); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/SchedulerController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/SchedulerController.java new file mode 100644 index 0000000..af952cc --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/SchedulerController.java @@ -0,0 +1,118 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.time.cron.Cron; +import cn.orionsec.kit.lang.utils.time.cron.CronSupport; +import cn.orionsec.ops.annotation.DemoDisableApi; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.entity.request.scheduler.SchedulerTaskRequest; +import cn.orionsec.ops.entity.vo.scheduler.CronNextVO; +import cn.orionsec.ops.entity.vo.scheduler.SchedulerTaskVO; +import cn.orionsec.ops.service.api.SchedulerTaskService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +@Api(tags = "调度任务") +@RestController +@RestWrapper +@RequestMapping("/orion/api/scheduler") +public class SchedulerController { + + @Resource + private SchedulerTaskService schedulerTaskService; + + @PostMapping("/cron-next") + @ApiOperation(value = "获取cron下几次执行时间") + public CronNextVO getCronNextTime(@RequestBody SchedulerTaskRequest request) { + String cron = Valid.notBlank(request.getExpression()).trim(); + Integer times = Valid.gt(request.getTimes(), 0); + CronNextVO next = new CronNextVO(); + try { + next.setNext(CronSupport.getNextTime(Cron.of(cron), times)); + next.setValid(true); + } catch (Exception e) { + next.setNext(Lists.empty()); + next.setValid(false); + } + return next; + } + + @DemoDisableApi + @PostMapping("/add") + @ApiOperation(value = "添加调度任务") + @EventLog(EventType.ADD_SCHEDULER_TASK) + public Long addTask(@RequestBody SchedulerTaskRequest request) { + Valid.allNotBlank(request.getName(), request.getCommand(), request.getExpression()); + Cron.of(request.getExpression()); + Valid.notNull(request.getSerializeType()); + Valid.notEmpty(request.getMachineIdList()); + return schedulerTaskService.addTask(request); + } + + @DemoDisableApi + @PostMapping("/update") + @ApiOperation(value = "修改调度任务") + @EventLog(EventType.UPDATE_SCHEDULER_TASK) + public Integer updateTask(@RequestBody SchedulerTaskRequest request) { + Valid.notNull(request.getId()); + Valid.allNotBlank(request.getName(), request.getCommand(), request.getExpression()); + Cron.of(request.getExpression()); + Valid.notEmpty(request.getMachineIdList()); + return schedulerTaskService.updateTask(request); + } + + @PostMapping("/get") + @ApiOperation(value = "获取调度任务详情") + public SchedulerTaskVO getTaskDetail(@RequestBody SchedulerTaskRequest request) { + Long id = Valid.notNull(request.getId()); + return schedulerTaskService.getTaskDetail(id); + } + + @PostMapping("/list") + @ApiOperation(value = "获取调度任务列表") + public DataGrid getTaskList(@RequestBody SchedulerTaskRequest request) { + return schedulerTaskService.getTaskList(request); + } + + @DemoDisableApi + @PostMapping("/update-status") + @ApiOperation(value = "更新调度任务状态") + @EventLog(EventType.UPDATE_SCHEDULER_TASK_STATUS) + public Integer updateTaskStatus(@RequestBody SchedulerTaskRequest request) { + Long id = Valid.notNull(request.getId()); + Integer status = Valid.in(request.getEnableStatus(), Const.ENABLE, Const.DISABLE); + return schedulerTaskService.updateTaskStatus(id, status); + } + + @DemoDisableApi + @PostMapping("/delete") + @ApiOperation(value = "删除调度任务") + @EventLog(EventType.DELETE_SCHEDULER_TASK) + public Integer deleteTask(@RequestBody SchedulerTaskRequest request) { + Long id = Valid.notNull(request.getId()); + return schedulerTaskService.deleteTask(id); + } + + @PostMapping("/manual-trigger") + @ApiOperation(value = "手动触发调度任务") + @EventLog(EventType.MANUAL_TRIGGER_SCHEDULER_TASK) + public HttpWrapper manualTriggerTask(@RequestBody SchedulerTaskRequest request) { + Long id = Valid.notNull(request.getId()); + schedulerTaskService.manualTriggerTask(id); + return HttpWrapper.ok(); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/SchedulerRecordController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/SchedulerRecordController.java new file mode 100644 index 0000000..bd83bfd --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/SchedulerRecordController.java @@ -0,0 +1,116 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.IgnoreLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.entity.request.scheduler.SchedulerTaskRecordRequest; +import cn.orionsec.ops.entity.vo.scheduler.SchedulerTaskMachineRecordStatusVO; +import cn.orionsec.ops.entity.vo.scheduler.SchedulerTaskMachineRecordVO; +import cn.orionsec.ops.entity.vo.scheduler.SchedulerTaskRecordStatusVO; +import cn.orionsec.ops.entity.vo.scheduler.SchedulerTaskRecordVO; +import cn.orionsec.ops.service.api.SchedulerTaskRecordService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.List; + +@Api(tags = "调度任务执行明细") +@RestController +@RestWrapper +@RequestMapping("/orion/api/scheduler-record") +public class SchedulerRecordController { + + @Resource + private SchedulerTaskRecordService schedulerTaskRecordService; + + @PostMapping("/list") + @ApiOperation(value = "获取调度任务执行列表") + public DataGrid listRecord(@RequestBody SchedulerTaskRecordRequest request) { + return schedulerTaskRecordService.listTaskRecord(request); + } + + @PostMapping("/detail") + @ApiOperation(value = "获取调度任务执行详情") + public SchedulerTaskRecordVO getRecordDetail(@RequestBody SchedulerTaskRecordRequest request) { + Long id = Valid.notNull(request.getId()); + return schedulerTaskRecordService.getDetailById(id); + } + + @PostMapping("/machines") + @ApiOperation(value = "查询调度任务执行机器") + public List listMachinesRecord(@RequestBody SchedulerTaskRecordRequest request) { + Long recordId = Valid.notNull(request.getRecordId()); + return schedulerTaskRecordService.listMachinesRecord(recordId); + } + + @IgnoreLog + @PostMapping("/list-status") + @ApiOperation(value = "查询调度任务执行状态") + public List listRecordStatus(@RequestBody SchedulerTaskRecordRequest request) { + List idList = Valid.notEmpty(request.getIdList()); + return schedulerTaskRecordService.listRecordStatus(idList, request.getMachineRecordIdList()); + } + + @IgnoreLog + @PostMapping("/machines-status") + @ApiOperation(value = "查询调度任务机器执行状态") + public List listMachineRecordStatus(@RequestBody SchedulerTaskRecordRequest request) { + List idList = Valid.notEmpty(request.getIdList()); + return schedulerTaskRecordService.listMachineRecordStatus(idList); + } + + @PostMapping("/delete") + @ApiOperation(value = "删除调度任务明细") + @EventLog(EventType.DELETE_TASK_RECORD) + public Integer deleteRecord(@RequestBody SchedulerTaskRecordRequest request) { + List idList = Valid.notEmpty(request.getIdList()); + return schedulerTaskRecordService.deleteTaskRecord(idList); + } + + @PostMapping("/terminate-all") + @ApiOperation(value = "停止执行调度任务") + @EventLog(EventType.TERMINATE_ALL_SCHEDULER_TASK) + public HttpWrapper terminateAll(@RequestBody SchedulerTaskRecordRequest request) { + Long id = Valid.notNull(request.getId()); + schedulerTaskRecordService.terminateAll(id); + return HttpWrapper.ok(); + } + + @PostMapping("/terminate-machine") + @ApiOperation(value = "停止单台机器执行调度任务") + @EventLog(EventType.TERMINATE_SCHEDULER_TASK_MACHINE) + public HttpWrapper terminateMachine(@RequestBody SchedulerTaskRecordRequest request) { + Long machineRecordId = Valid.notNull(request.getMachineRecordId()); + schedulerTaskRecordService.terminateMachine(machineRecordId); + return HttpWrapper.ok(); + } + + @PostMapping("/skip-machine") + @ApiOperation(value = "跳过单台机器执行调度任务") + @EventLog(EventType.SKIP_SCHEDULER_TASK_MACHINE) + public HttpWrapper skipMachine(@RequestBody SchedulerTaskRecordRequest request) { + Long machineRecordId = Valid.notNull(request.getMachineRecordId()); + schedulerTaskRecordService.skipMachine(machineRecordId); + return HttpWrapper.ok(); + } + + @PostMapping("/write-machine") + @ApiOperation(value = "写入命令") + public HttpWrapper writeMachine(@RequestBody SchedulerTaskRecordRequest request) { + Long machineRecordId = Valid.notNull(request.getMachineRecordId()); + String command = Valid.notEmpty(request.getCommand()); + schedulerTaskRecordService.writeMachine(machineRecordId, command); + return HttpWrapper.ok(); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/SftpController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/SftpController.java new file mode 100644 index 0000000..9e1acfe --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/SftpController.java @@ -0,0 +1,329 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.id.ObjectIds; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.collect.Lists; +import cn.orionsec.kit.lang.utils.io.Files1; +import cn.orionsec.kit.net.host.sftp.SftpErrorMessage; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.constant.sftp.SftpPackageType; +import cn.orionsec.ops.constant.system.SystemEnvAttr; +import cn.orionsec.ops.entity.dto.sftp.SftpSessionTokenDTO; +import cn.orionsec.ops.entity.dto.sftp.SftpUploadInfoDTO; +import cn.orionsec.ops.entity.request.sftp.*; +import cn.orionsec.ops.entity.vo.sftp.FileListVO; +import cn.orionsec.ops.entity.vo.sftp.FileOpenVO; +import cn.orionsec.ops.entity.vo.sftp.FileTransferLogVO; +import cn.orionsec.ops.service.api.SftpService; +import cn.orionsec.ops.utils.Currents; +import cn.orionsec.ops.utils.PathBuilders; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +@Api(tags = "sftp操作") +@RestController +@RestWrapper +@RequestMapping("/orion/api/sftp") +public class SftpController { + + @Resource + private SftpService sftpService; + + @PostMapping("/open") + @ApiOperation(value = "打开sftp") + @EventLog(EventType.OPEN_SFTP) + public FileOpenVO open(@RequestBody FileOpenRequest request) { + Long machineId = Valid.notNull(request.getMachineId()); + return sftpService.open(machineId); + } + + @PostMapping("/list") + @ApiOperation(value = "获取文件列表") + public FileListVO list(@RequestBody FileListRequest request) { + Valid.checkNormalize(request.getPath()); + try { + return sftpService.list(request); + } catch (RuntimeException e) { + throw this.convertError(e); + } + } + + @PostMapping("/list-dir") + @ApiOperation(value = "获取文件夹列表") + public FileListVO listDir(@RequestBody FileListRequest request) { + Valid.checkNormalize(request.getPath()); + try { + return sftpService.listDir(request); + } catch (RuntimeException e) { + throw this.convertError(e); + } + } + + @PostMapping("/mkdir") + @ApiOperation(value = "创建文件夹") + @EventLog(EventType.SFTP_MKDIR) + public String mkdir(@RequestBody FileMkdirRequest request) { + Valid.checkNormalize(request.getPath()); + return sftpService.mkdir(request); + } + + @PostMapping("/touch") + @ApiOperation(value = "创建文件") + @EventLog(EventType.SFTP_TOUCH) + public String touch(@RequestBody FileTouchRequest request) { + Valid.checkNormalize(request.getPath()); + return sftpService.touch(request); + } + + @PostMapping("/truncate") + @ApiOperation(value = "截断文件") + @EventLog(EventType.SFTP_TRUNCATE) + public void truncate(@RequestBody FileTruncateRequest request) { + Valid.checkNormalize(request.getPath()); + try { + sftpService.truncate(request); + } catch (RuntimeException e) { + throw this.convertError(e); + } + } + + @PostMapping("/move") + @ApiOperation(value = "移动文件") + @EventLog(EventType.SFTP_MOVE) + public String move(@RequestBody FileMoveRequest request) { + Valid.checkNormalize(request.getSource()); + Valid.notBlank(request.getTarget()); + try { + return sftpService.move(request); + } catch (RuntimeException e) { + throw this.convertError(e); + } + } + + @PostMapping("/remove") + @ApiOperation(value = "删除文件/文件夹") + @EventLog(EventType.SFTP_REMOVE) + public void remove(@RequestBody FileRemoveRequest request) { + List paths = Valid.notEmpty(request.getPaths()); + paths.forEach(Valid::checkNormalize); + boolean isSafe = paths.stream().noneMatch(Const.UNSAFE_FS_DIR::contains); + Valid.isSafe(isSafe); + try { + sftpService.remove(request); + } catch (RuntimeException e) { + throw this.convertError(e); + } + } + + @PostMapping("/chmod") + @ApiOperation(value = "修改权限") + @EventLog(EventType.SFTP_CHMOD) + public String chmod(@RequestBody FileChmodRequest request) { + Valid.checkNormalize(request.getPath()); + Valid.notNull(request.getPermission()); + try { + return sftpService.chmod(request); + } catch (RuntimeException e) { + throw this.convertError(e); + } + } + + @PostMapping("/chown") + @ApiOperation(value = "修改所有者") + @EventLog(EventType.SFTP_CHOWN) + public void chown(@RequestBody FileChownRequest request) { + Valid.checkNormalize(request.getPath()); + Valid.notNull(request.getUid()); + try { + sftpService.chown(request); + } catch (RuntimeException e) { + throw this.convertError(e); + } + } + + @PostMapping("/chgrp") + @ApiOperation(value = "修改所有组") + @EventLog(EventType.SFTP_CHGRP) + public void changeGroup(@RequestBody FileChangeGroupRequest request) { + Valid.checkNormalize(request.getPath()); + Valid.notNull(request.getGid()); + try { + sftpService.changeGroup(request); + } catch (RuntimeException e) { + throw this.convertError(e); + } + } + + @PostMapping("/check-present") + @ApiOperation(value = "检查文件是否存在") + public List checkFilePresent(@RequestBody FilePresentCheckRequest request) { + Valid.checkNormalize(request.getPath()); + Valid.notEmpty(request.getNames()); + Valid.checkUploadSize(request.getSize()); + return sftpService.checkFilePresent(request); + } + + @PostMapping("/upload/token") + @ApiOperation(value = "获取上传文件accessToken") + public String getUploadAccessToken(@RequestBody FileUploadRequest request) { + return sftpService.getUploadAccessToken(request); + } + + @PostMapping("/upload/exec") + @ApiOperation(value = "上传文件") + @EventLog(EventType.SFTP_UPLOAD) + public void uploadFile(@RequestParam("accessToken") String accessToken, @RequestParam("files") List files) throws IOException { + // 检查文件 + Valid.notBlank(accessToken); + Valid.notEmpty(files); + // 检查token + SftpUploadInfoDTO uploadInfo = sftpService.checkUploadAccessToken(accessToken); + Long machineId = uploadInfo.getMachineId(); + String remotePath = uploadInfo.getRemotePath(); + + List requestFiles = Lists.newList(); + for (MultipartFile file : files) { + // 传输文件到本地 + String fileToken = ObjectIds.nextId(); + String localPath = PathBuilders.getSftpUploadFilePath(fileToken); + Path localAbsolutePath = Paths.get(SystemEnvAttr.SWAP_PATH.getValue(), localPath); + Files1.touch(localAbsolutePath); + file.transferTo(localAbsolutePath); + + // 提交任务 + FileUploadRequest request = new FileUploadRequest(); + request.setMachineId(machineId); + request.setFileToken(fileToken); + request.setLocalPath(localPath); + request.setRemotePath(Files1.getPath(remotePath, file.getOriginalFilename())); + request.setSize(file.getSize()); + requestFiles.add(request); + } + sftpService.upload(machineId, requestFiles); + } + + @PostMapping("/download/exec") + @ApiOperation(value = "下载文件") + @EventLog(EventType.SFTP_DOWNLOAD) + public void downloadFile(@RequestBody FileDownloadRequest request) { + List paths = Valid.notEmpty(request.getPaths()); + paths.forEach(Valid::checkNormalize); + sftpService.download(request); + } + + @PostMapping("/package-download/exec") + @ApiOperation(value = "打包下载文件") + @EventLog(EventType.SFTP_DOWNLOAD) + public void packageDownloadFile(@RequestBody FileDownloadRequest request) { + List paths = Valid.notEmpty(request.getPaths()); + paths.forEach(Valid::checkNormalize); + sftpService.packageDownload(request); + } + + @GetMapping("/transfer/{fileToken}/pause") + @ApiOperation(value = "暂停文件传输") + public void transferPause(@PathVariable("fileToken") String fileToken) { + sftpService.transferPause(fileToken); + } + + @GetMapping("/transfer/{fileToken}/resume") + @ApiOperation(value = "恢复文件传输") + public void transferResume(@PathVariable("fileToken") String fileToken) { + sftpService.transferResume(fileToken); + } + + @GetMapping("/transfer/{fileToken}/retry") + @ApiOperation(value = "传输失败重试") + public void transferRetry(@PathVariable("fileToken") String fileToken) { + sftpService.transferRetry(fileToken); + } + + @GetMapping("/transfer/{fileToken}/re-upload") + @ApiOperation(value = "重新上传文件") + public void transferReUpload(@PathVariable("fileToken") String fileToken) { + sftpService.transferReUpload(fileToken); + } + + @GetMapping("/transfer/{fileToken}/re-download") + @ApiOperation(value = "重新下载文件") + public void transferReDownload(@PathVariable("fileToken") String fileToken) { + sftpService.transferReDownload(fileToken); + } + + @GetMapping("/transfer/{sessionToken}/pause-all") + @ApiOperation(value = "暂停所有传输") + public void transferPauseAll(@PathVariable("sessionToken") String sessionToken) { + sftpService.transferPauseAll(sessionToken); + } + + @GetMapping("/transfer/{sessionToken}/resume-all") + @ApiOperation(value = "恢复所有传输") + public void transferResumeAll(@PathVariable("sessionToken") String sessionToken) { + sftpService.transferResumeAll(sessionToken); + } + + @GetMapping("/transfer/{sessionToken}/retry-all") + @ApiOperation(value = "失败重试所有") + public void transferRetryAll(@PathVariable("sessionToken") String sessionToken) { + sftpService.transferRetryAll(sessionToken); + } + + @GetMapping("/transfer/{sessionToken}/list") + @ApiOperation(value = "获取传输列表") + public List transferList(@PathVariable("sessionToken") String sessionToken) { + SftpSessionTokenDTO tokenInfo = sftpService.getTokenInfo(sessionToken); + Valid.isTrue(Currents.getUserId().equals(tokenInfo.getUserId())); + return sftpService.transferList(tokenInfo.getMachineId()); + } + + @GetMapping("/transfer/{fileToken}/remove") + @ApiOperation(value = "删除单个传输记录 (包含进行中的)") + public void transferRemove(@PathVariable("fileToken") String fileToken) { + sftpService.transferRemove(fileToken); + } + + @GetMapping("/transfer/{sessionToken}/clear") + @ApiOperation(value = "清空全部传输记录 (不包含进行中的)") + public Integer transferClear(@PathVariable("sessionToken") String sessionToken) { + SftpSessionTokenDTO tokenInfo = sftpService.getTokenInfo(sessionToken); + Valid.isTrue(Currents.getUserId().equals(tokenInfo.getUserId())); + return sftpService.transferClear(tokenInfo.getMachineId()); + } + + @GetMapping("/transfer/{sessionToken}/{packageType}/package") + @ApiOperation(value = "传输打包全部已完成未删除的文件") + @EventLog(EventType.SFTP_PACKAGE) + public void transferPackage(@PathVariable("sessionToken") String sessionToken, @PathVariable("packageType") Integer packageType) { + SftpPackageType sftpPackageType = Valid.notNull(SftpPackageType.of(packageType)); + sftpService.transferPackage(sessionToken, sftpPackageType); + } + + /** + * 检测文件是否存在 + * + * @param e e + * @return RuntimeException + */ + private RuntimeException convertError(RuntimeException e) { + if (SftpErrorMessage.NO_SUCH_FILE.isCause(e)) { + return Exceptions.argument(MessageConst.NO_SUCH_FILE); + } else { + return e; + } + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/StatisticsController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/StatisticsController.java new file mode 100644 index 0000000..eb93a3f --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/StatisticsController.java @@ -0,0 +1,120 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.ops.annotation.IgnoreLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.entity.request.app.AppBuildStatisticsRequest; +import cn.orionsec.ops.entity.request.app.AppPipelineTaskStatisticsRequest; +import cn.orionsec.ops.entity.request.app.AppReleaseStatisticsRequest; +import cn.orionsec.ops.entity.request.home.HomeStatisticsRequest; +import cn.orionsec.ops.entity.request.scheduler.SchedulerTaskStatisticsRequest; +import cn.orionsec.ops.entity.vo.app.*; +import cn.orionsec.ops.entity.vo.home.HomeStatisticsVO; +import cn.orionsec.ops.entity.vo.scheduler.SchedulerTaskRecordStatisticsVO; +import cn.orionsec.ops.service.api.StatisticsService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.List; + +@Api(tags = "统计操作") +@RestController +@RestWrapper +@RequestMapping("/orion/api/statistics") +public class StatisticsController { + + @Resource + private StatisticsService statisticsService; + + @PostMapping("/home") + @ApiOperation(value = "首页统计") + public HomeStatisticsVO homeStatistics(@RequestBody HomeStatisticsRequest request) { + return statisticsService.homeStatistics(request); + } + + @PostMapping("/scheduler-task") + @ApiOperation(value = "调度任务统计") + public SchedulerTaskRecordStatisticsVO schedulerTaskStatistics(@RequestBody SchedulerTaskStatisticsRequest request) { + Long taskId = Valid.notNull(request.getTaskId()); + return statisticsService.schedulerTaskStatistics(taskId); + } + + @IgnoreLog + @PostMapping("/app-build/view") + @ApiOperation(value = "获取应用构建统计视图") + public ApplicationBuildStatisticsViewVO appBuildStatisticsView(@RequestBody AppBuildStatisticsRequest request) { + Long appId = Valid.notNull(request.getAppId()); + Long profileId = Valid.notNull(request.getProfileId()); + return statisticsService.appBuildStatisticsView(appId, profileId); + } + + @PostMapping("/app-build/metrics") + @ApiOperation(value = "获取应用构建统计指标") + public ApplicationBuildStatisticsMetricsWrapperVO appBuildStatisticsMetrics(@RequestBody AppBuildStatisticsRequest request) { + Long appId = Valid.notNull(request.getAppId()); + Long profileId = Valid.notNull(request.getProfileId()); + return statisticsService.appBuildStatisticsMetrics(appId, profileId); + } + + @PostMapping("/app-build/chart") + @ApiOperation(value = "获取应用构建统计折线图") + public List appBuildStatisticsChart(@RequestBody AppBuildStatisticsRequest request) { + Long appId = Valid.notNull(request.getAppId()); + Long profileId = Valid.notNull(request.getProfileId()); + return statisticsService.appBuildStatisticsChart(appId, profileId); + } + + @IgnoreLog + @PostMapping("/app-release/view") + @ApiOperation(value = "获取应用发布统计视图") + public ApplicationReleaseStatisticsViewVO appReleaseStatisticsView(@RequestBody AppReleaseStatisticsRequest request) { + Long appId = Valid.notNull(request.getAppId()); + Long profileId = Valid.notNull(request.getProfileId()); + return statisticsService.appReleaseStatisticView(appId, profileId); + } + + @PostMapping("/app-release/metrics") + @ApiOperation(value = "获取应用发布统计指标") + public ApplicationReleaseStatisticsMetricsWrapperVO appReleaseStatisticsMetrics(@RequestBody AppReleaseStatisticsRequest request) { + Long appId = Valid.notNull(request.getAppId()); + Long profileId = Valid.notNull(request.getProfileId()); + return statisticsService.appReleaseStatisticMetrics(appId, profileId); + } + + @PostMapping("/app-release/chart") + @ApiOperation(value = "获取应用发布统计折线图") + public List appReleaseStatisticsChart(@RequestBody AppReleaseStatisticsRequest request) { + Long appId = Valid.notNull(request.getAppId()); + Long profileId = Valid.notNull(request.getProfileId()); + return statisticsService.appReleaseStatisticChart(appId, profileId); + } + + @IgnoreLog + @PostMapping("/app-pipeline/view") + @ApiOperation(value = "获取应用流水线统计视图") + public ApplicationPipelineTaskStatisticsViewVO appReleaseStatisticsView(@RequestBody AppPipelineTaskStatisticsRequest request) { + Long pipelineId = Valid.notNull(request.getPipelineId()); + return statisticsService.appPipelineTaskStatisticView(pipelineId); + } + + @PostMapping("/app-pipeline/metrics") + @ApiOperation(value = "获取应用流水线统计指标") + public ApplicationPipelineTaskStatisticsMetricsWrapperVO appReleaseStatisticsMetrics(@RequestBody AppPipelineTaskStatisticsRequest request) { + Long pipelineId = Valid.notNull(request.getPipelineId()); + return statisticsService.appPipelineTaskStatisticMetrics(pipelineId); + } + + @PostMapping("/app-pipeline/chart") + @ApiOperation(value = "获取应用流水线统计折线图") + public List appReleaseStatisticsChart(@RequestBody AppPipelineTaskStatisticsRequest request) { + Long pipelineId = Valid.notNull(request.getPipelineId()); + return statisticsService.appPipelineTaskStatisticChart(pipelineId); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/SystemController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/SystemController.java new file mode 100644 index 0000000..485e3d8 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/SystemController.java @@ -0,0 +1,120 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.kit.web.servlet.web.Servlets; +import cn.orionsec.ops.annotation.DemoDisableApi; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.RequireRole; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.CnConst; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.PropertiesConst; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.constant.system.SystemCleanType; +import cn.orionsec.ops.constant.system.SystemConfigKey; +import cn.orionsec.ops.constant.user.RoleType; +import cn.orionsec.ops.entity.request.system.ConfigIpListRequest; +import cn.orionsec.ops.entity.request.system.SystemFileCleanRequest; +import cn.orionsec.ops.entity.request.system.SystemOptionRequest; +import cn.orionsec.ops.entity.vo.system.*; +import cn.orionsec.ops.service.api.SystemService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.util.List; + +@Api(tags = "系统设置") +@RestController +@RestWrapper +@RequestMapping("/orion/api/system") +public class SystemController { + + @Resource + private SystemService systemService; + + @GetMapping("/ip-info") + @ApiOperation(value = "获取ip信息") + public IpListConfigVO getIpInfo(HttpServletRequest request) { + return systemService.getIpInfo(Servlets.getRemoteAddr(request)); + } + + @DemoDisableApi + @PostMapping("/config-ip") + @ApiOperation(value = "配置ip过滤器列表") + @EventLog(EventType.CONFIG_IP_LIST) + @RequireRole(RoleType.ADMINISTRATOR) + public HttpWrapper configIpList(@RequestBody ConfigIpListRequest request) { + systemService.configIpFilterList(request); + return HttpWrapper.ok(); + } + + @DemoDisableApi + @PostMapping("/clean-system-file") + @ApiOperation(value = "清理系统文件") + @EventLog(EventType.CLEAN_SYSTEM_FILE) + @RequireRole(RoleType.ADMINISTRATOR) + public HttpWrapper cleanSystemFile(@RequestBody SystemFileCleanRequest request) { + SystemCleanType cleanType = Valid.notNull(SystemCleanType.of(request.getCleanType())); + systemService.cleanSystemFile(cleanType); + return HttpWrapper.ok(); + } + + @GetMapping("/get-system-analysis") + @ApiOperation(value = "获取系统分析信息") + public SystemAnalysisVO getSystemAnalysis() { + return systemService.getSystemAnalysis(); + } + + @DemoDisableApi + @GetMapping("/re-analysis") + @ApiOperation(value = "重新进行系统统计分析") + @EventLog(EventType.RE_ANALYSIS_SYSTEM) + @RequireRole(RoleType.ADMINISTRATOR) + public SystemAnalysisVO reAnalysisSystem() { + systemService.analysisSystemSpace(); + return systemService.getSystemAnalysis(); + } + + @DemoDisableApi + @PostMapping("/update-system-option") + @ApiOperation(value = "修改系统配置项") + @EventLog(EventType.UPDATE_SYSTEM_OPTION) + @RequireRole(RoleType.ADMINISTRATOR) + public HttpWrapper updateSystemOption(@RequestBody SystemOptionRequest request) { + SystemConfigKey key = Valid.notNull(SystemConfigKey.of(request.getOption())); + String value = key.getValue(Valid.notBlank(request.getValue())); + systemService.updateSystemOption(key.getEnv(), value); + return HttpWrapper.ok(); + } + + @GetMapping("/get-system-options") + @ApiOperation(value = "获取系统配置项") + @RequireRole(RoleType.ADMINISTRATOR) + public SystemOptionVO getSystemOptions() { + return systemService.getSystemOptions(); + } + + @GetMapping("/get-thread-metrics") + @ApiOperation(value = "获取线程池指标") + @RequireRole(RoleType.ADMINISTRATOR) + public List getThreadMetrics() { + return systemService.getThreadPoolMetrics(); + } + + @GetMapping("/about") + @ApiOperation(value = "获取应用信息") + public SystemAboutVO getSystemAbout() { + return SystemAboutVO.builder() + .orionKitVersion(Const.ORION_KIT_VERSION) + .orionOpsVersion(PropertiesConst.ORION_OPS_VERSION) + .author(Const.ORION_AUTHOR) + .authorCn(CnConst.ORION_OPS_AUTHOR) + .build(); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/SystemEnvController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/SystemEnvController.java new file mode 100644 index 0000000..5565c25 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/SystemEnvController.java @@ -0,0 +1,107 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.collect.MutableLinkedHashMap; +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Exceptions; +import cn.orionsec.kit.lang.utils.collect.Maps; +import cn.orionsec.ops.annotation.DemoDisableApi; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.MessageConst; +import cn.orionsec.ops.constant.env.EnvViewType; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.entity.request.system.SystemEnvRequest; +import cn.orionsec.ops.entity.vo.system.SystemEnvVO; +import cn.orionsec.ops.service.api.SystemEnvService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.util.List; +import java.util.Map; +@Api(tags = "系统环境变量") +@RestController +@RestWrapper +@RequestMapping("/orion/api/system-env") +public class SystemEnvController { + + @Resource + private SystemEnvService systemEnvService; + + @DemoDisableApi + @PostMapping("/add") + @ApiOperation(value = "添加环境变量") + @EventLog(EventType.ADD_SYSTEM_ENV) + public Long add(@RequestBody SystemEnvRequest request) { + Valid.notBlank(request.getKey()); + Valid.notNull(request.getValue()); + return systemEnvService.addEnv(request); + } + + @DemoDisableApi + @PostMapping("/update") + @ApiOperation(value = "修改环境变量") + @EventLog(EventType.UPDATE_SYSTEM_ENV) + public Integer update(@RequestBody SystemEnvRequest request) { + Valid.notNull(request.getId()); + Valid.notNull(request.getValue()); + return systemEnvService.updateEnv(request); + } + + @DemoDisableApi + @PostMapping("/delete") + @ApiOperation(value = "删除环境变量") + @EventLog(EventType.DELETE_SYSTEM_ENV) + public Integer delete(@RequestBody SystemEnvRequest request) { + List idList = Valid.notEmpty(request.getIdList()); + return systemEnvService.deleteEnv(idList); + } + + @PostMapping("/list") + @ApiOperation(value = "获取环境变量列表") + public DataGrid list(@RequestBody SystemEnvRequest request) { + return systemEnvService.listEnv(request); + } + + @PostMapping("/detail") + @ApiOperation(value = "获取环境变量详情") + public SystemEnvVO detail(@RequestBody SystemEnvRequest request) { + Long id = Valid.notNull(request.getId()); + return systemEnvService.getEnvDetail(id); + } + + @PostMapping("/view") + @ApiOperation(value = "获取环境变量视图") + public String view(@RequestBody SystemEnvRequest request) { + EnvViewType viewType = Valid.notNull(EnvViewType.of(request.getViewType())); + request.setLimit(Const.N_100000); + // 查询列表 + Map env = Maps.newLinkedMap(); + systemEnvService.listEnv(request).forEach(e -> env.put(e.getKey(), e.getValue())); + return viewType.toValue(env); + } + + @DemoDisableApi + @PostMapping("/view-save") + @ApiOperation(value = "保存环境变量视图") + @EventLog(EventType.SAVE_SYSTEM_ENV) + public Integer viewSave(@RequestBody SystemEnvRequest request) { + String value = Valid.notBlank(request.getValue()); + EnvViewType viewType = Valid.notNull(EnvViewType.of(request.getViewType())); + try { + MutableLinkedHashMap result = viewType.toMap(value); + systemEnvService.saveEnv(result); + return result.size(); + } catch (Exception e) { + throw Exceptions.argument(MessageConst.PARSE_ERROR, e); + } + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/UserController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/UserController.java new file mode 100644 index 0000000..f7c047d --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/UserController.java @@ -0,0 +1,121 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.kit.lang.utils.Objects1; +import cn.orionsec.ops.annotation.DemoDisableApi; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.RequireRole; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.Const; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.constant.user.RoleType; +import cn.orionsec.ops.entity.request.user.UserInfoRequest; +import cn.orionsec.ops.entity.vo.user.UserInfoVO; +import cn.orionsec.ops.service.api.UserService; +import cn.orionsec.ops.utils.Currents; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +@Api(tags = "用户") +@RestController +@RestWrapper +@RequestMapping("/orion/api/user") +public class UserController { + + @Resource + private UserService userService; + + @PostMapping("/list") + @ApiOperation(value = "获取用户列表") + public DataGrid list(@RequestBody UserInfoRequest request) { + return userService.userList(request); + } + + @PostMapping("/detail") + @ApiOperation(value = "获取用户详情") + public UserInfoVO detail(@RequestBody UserInfoRequest request) { + return userService.userDetail(request); + } + + @DemoDisableApi + @PostMapping("/add") + @ApiOperation(value = "添加用户") + @RequireRole(RoleType.ADMINISTRATOR) + @EventLog(EventType.ADD_USER) + public Long addUser(@RequestBody UserInfoRequest request) { + this.check(request); + Valid.notBlank(request.getPassword()); + request.setId(null); + return userService.addUser(request); + } + + @DemoDisableApi + @PostMapping("/update") + @ApiOperation(value = "修改用户信息") + @EventLog(EventType.UPDATE_USER) + public Integer update(@RequestBody UserInfoRequest request) { + Integer roleType = request.getRole(); + if (roleType != null) { + Valid.notNull(RoleType.of(roleType)); + } + request.setId(Objects1.def(request.getId(), Currents::getUserId)); + return userService.updateUser(request); + } + + @DemoDisableApi + @PostMapping("/update-avatar") + @ApiOperation(value = "修改用户头像") + public Integer updateAvatar(@RequestBody UserInfoRequest request) { + String avatar = Valid.notBlank(request.getAvatar()); + return userService.updateAvatar(avatar); + } + + @DemoDisableApi + @PostMapping("/delete") + @ApiOperation(value = "删除用户") + @RequireRole(RoleType.ADMINISTRATOR) + @EventLog(EventType.DELETE_USER) + public Integer deleteUser(@RequestBody UserInfoRequest request) { + Long id = Valid.notNull(request.getId()); + return userService.deleteUser(id); + } + + @DemoDisableApi + @PostMapping("/update-status") + @ApiOperation(value = "停用/启用用户") + @RequireRole(RoleType.ADMINISTRATOR) + @EventLog(EventType.CHANGE_USER_STATUS) + public Integer updateUserStatus(@RequestBody UserInfoRequest request) { + Long id = Valid.notNull(request.getId()); + Integer status = Valid.in(Valid.notNull(request.getStatus()), Const.ENABLE, Const.DISABLE); + return userService.updateStatus(id, status); + } + + @PostMapping("/unlock") + @ApiOperation(value = "解锁用户") + @RequireRole(RoleType.ADMINISTRATOR) + @EventLog(EventType.UNLOCK_USER) + public Integer unlockUser(@RequestBody UserInfoRequest request) { + Long id = Valid.notNull(request.getId()); + return userService.unlockUser(id); + } + + /** + * 检查参数 + */ + private void check(UserInfoRequest request) { + Valid.notBlank(request.getUsername()); + Valid.notBlank(request.getNickname()); + Valid.notNull(RoleType.of(request.getRole())); + Valid.notBlank(request.getPhone()); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/WebSideMessageController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/WebSideMessageController.java new file mode 100644 index 0000000..2c89d31 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/WebSideMessageController.java @@ -0,0 +1,93 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.annotation.IgnoreLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.entity.request.message.WebSideMessageRequest; +import cn.orionsec.ops.entity.vo.message.WebSideMessagePollVO; +import cn.orionsec.ops.entity.vo.message.WebSideMessageVO; +import cn.orionsec.ops.service.api.WebSideMessageService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +@Api(tags = "站内信") +@RestController +@RestWrapper +@RequestMapping("/orion/api/message") +public class WebSideMessageController { + + @Resource + private WebSideMessageService webSideMessageService; + + @GetMapping("/unread-count") + @ApiOperation(value = "获取站内信未读数量") + public Integer getUnreadCount() { + return webSideMessageService.getUnreadCount(); + } + + @GetMapping("/set-all-read") + @ApiOperation(value = "设置站内信全部已读") + public Integer setAllRead() { + return webSideMessageService.setAllRead(); + } + + @PostMapping("/read") + @ApiOperation(value = "设置已读站内信") + public Integer readMessage(@RequestBody WebSideMessageRequest request) { + List idList = Valid.notNull(request.getIdList()); + return webSideMessageService.readMessage(idList); + } + + @GetMapping("/delete-all-read") + @ApiOperation(value = "删除全部已读站内信") + public Integer deleteAllRead() { + return webSideMessageService.deleteAllRead(); + } + + @PostMapping("/delete") + @ApiOperation(value = "删除站内信") + public Integer deleteMessage(@RequestBody WebSideMessageRequest request) { + List idList = Valid.notNull(request.getIdList()); + return webSideMessageService.deleteMessage(idList); + } + + @PostMapping("/detail") + @ApiOperation(value = "获取站内信详情") + public WebSideMessageVO getMessageDetail(@RequestBody WebSideMessageRequest request) { + Long id = Valid.notNull(request.getId()); + return webSideMessageService.getMessageDetail(id); + } + + @PostMapping("/list") + @ApiOperation(value = "获取站内信列表") + public DataGrid getMessageList(@RequestBody WebSideMessageRequest request) { + return webSideMessageService.getMessageList(request); + } + + @PostMapping("/get-new-message") + @ApiOperation(value = "获取最新站内信") + public WebSideMessagePollVO getNewMessage(@RequestBody WebSideMessageRequest request) { + return webSideMessageService.getNewMessage(request); + } + + @PostMapping("/get-more-message") + @ApiOperation(value = "获取更多站内信") + public List getMoreMessage(@RequestBody WebSideMessageRequest request) { + Valid.notNull(request.getMaxId()); + return webSideMessageService.getMoreMessage(request); + } + + @IgnoreLog + @PostMapping("/poll-new-message") + @ApiOperation(value = "轮询最新站内信") + public WebSideMessagePollVO pollWebSideMessage(@RequestBody WebSideMessageRequest request) { + return webSideMessageService.pollWebSideMessage(request.getMaxId()); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/WebhookConfigController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/WebhookConfigController.java new file mode 100644 index 0000000..6bfda43 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/controller/WebhookConfigController.java @@ -0,0 +1,83 @@ + +package cn.orionsec.ops.controller; + +import cn.orionsec.kit.lang.define.wrapper.DataGrid; +import cn.orionsec.ops.annotation.DemoDisableApi; +import cn.orionsec.ops.annotation.EventLog; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.event.EventType; +import cn.orionsec.ops.constant.webhook.WebhookType; +import cn.orionsec.ops.entity.request.webhook.WebhookConfigRequest; +import cn.orionsec.ops.entity.vo.webhook.WebhookConfigVO; +import cn.orionsec.ops.service.api.WebhookConfigService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +@Api(tags = "webhook配置") +@RestController +@RestWrapper +@RequestMapping("/orion/api/webhook-config") +public class WebhookConfigController { + + @Resource + private WebhookConfigService webhookConfigService; + + @PostMapping("/list") + @ApiOperation(value = "查询列表") + public DataGrid getWebhookList(@RequestBody WebhookConfigRequest request) { + return webhookConfigService.getWebhookList(request); + } + + @PostMapping("/get") + @ApiOperation(value = "获取详情") + public WebhookConfigVO getWebhookDetail(@RequestBody WebhookConfigRequest request) { + Long id = Valid.notNull(request.getId()); + return webhookConfigService.getWebhookDetail(id); + } + + @DemoDisableApi + @PostMapping("/add") + @ApiOperation(value = "添加 webhook") + @EventLog(EventType.ADD_WEBHOOK) + public Long addWebhook(@RequestBody WebhookConfigRequest request) { + this.checkParams(request); + return webhookConfigService.addWebhook(request); + } + + @DemoDisableApi + @PostMapping("/update") + @ApiOperation(value = "更新 webhook") + @EventLog(EventType.UPDATE_WEBHOOK) + public Integer updateWebhook(@RequestBody WebhookConfigRequest request) { + Valid.notNull(request.getId()); + this.checkParams(request); + return webhookConfigService.updateWebhook(request); + } + + @DemoDisableApi + @PostMapping("/delete") + @ApiOperation(value = "删除 webhook") + @EventLog(EventType.DELETE_WEBHOOK) + public Integer deleteWebhook(@RequestBody WebhookConfigRequest request) { + Long id = Valid.notNull(request.getId()); + return webhookConfigService.deleteWebhook(id); + } + + /** + * 检查参数 + * + * @param request request + */ + private void checkParams(WebhookConfigRequest request) { + Valid.notNull(WebhookType.of(request.getType())); + Valid.allNotBlank(request.getName(), request.getUrl()); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/expose/MachineAlarmExposeController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/expose/MachineAlarmExposeController.java new file mode 100644 index 0000000..38c2cfd --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/expose/MachineAlarmExposeController.java @@ -0,0 +1,46 @@ + +package cn.orionsec.ops.expose; + +import cn.orionsec.kit.lang.define.wrapper.HttpWrapper; +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.machine.MachineAlarmType; +import cn.orionsec.ops.entity.request.machine.MachineAlarmRequest; +import cn.orionsec.ops.entity.vo.machine.MachineAlarmConfigVO; +import cn.orionsec.ops.service.api.MachineAlarmConfigService; +import cn.orionsec.ops.service.api.MachineAlarmService; +import cn.orionsec.ops.utils.Valid; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +@Api(tags = "暴露服务-机器报警") +@RestController +@RestWrapper +@RequestMapping("/orion/expose-api/machine-alarm") +public class MachineAlarmExposeController { + + @Resource + private MachineAlarmConfigService machineAlarmConfigService; + + @Resource + private MachineAlarmService machineAlarmService; + + @GetMapping("/get-config") + @ApiOperation(value = "获取报警配置") + public List getAlarmConfig(@RequestParam("machineId") Long machineId) { + return machineAlarmConfigService.getAlarmConfig(machineId); + } + + @PostMapping("/trigger-alarm") + @ApiOperation(value = "触发机器报警") + public HttpWrapper triggerMachineAlarm(@RequestBody MachineAlarmRequest request) { + Valid.allNotNull(request.getMachineId(), request.getAlarmTime(), + request.getAlarmValue(), MachineAlarmType.of(request.getType())); + machineAlarmService.triggerMachineAlarm(request); + return HttpWrapper.ok(); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/expose/MachineMonitorExposeController.java b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/expose/MachineMonitorExposeController.java new file mode 100644 index 0000000..ec18850 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/java/cn/orionsec/ops/expose/MachineMonitorExposeController.java @@ -0,0 +1,35 @@ + +package cn.orionsec.ops.expose; + +import cn.orionsec.ops.annotation.RestWrapper; +import cn.orionsec.ops.constant.monitor.MonitorStatus; +import cn.orionsec.ops.entity.domain.MachineMonitorDO; +import cn.orionsec.ops.service.api.MachineMonitorService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +@Api(tags = "暴露服务-机器监控") +@RestController +@RestWrapper +@RequestMapping("/orion/expose-api/machine-monitor") +public class MachineMonitorExposeController { + + @Resource + private MachineMonitorService machineMonitorService; + + @GetMapping("/started") + @ApiOperation(value = "监控启动回调") + public Integer getAlarmConfig(@RequestParam("machineId") Long machineId, @RequestParam("version") String version) { + MachineMonitorDO update = new MachineMonitorDO(); + update.setMonitorStatus(MonitorStatus.RUNNING.getStatus()); + update.setAgentVersion(version); + return machineMonitorService.updateMonitorConfigByMachineId(machineId, update); + } + +} diff --git a/orion-ops-api/orion-ops-web/src/main/resources/application-dev.properties b/orion-ops-api/orion-ops-web/src/main/resources/application-dev.properties new file mode 100644 index 0000000..8250363 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/resources/application-dev.properties @@ -0,0 +1,10 @@ +# redis +spring.redis.host=${REDIS_HOST:192.168.88.101} +spring.redis.port=${REDIS_PORT:6379} +spring.redis.password=${REDIS_PASSWORD:Redis#data} +# mysql +spring.datasource.url=jdbc:mysql://${MYSQL_HOST:192.168.88.101}:3306/orion-ops?useUnicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai&autoReconnect=true +spring.datasource.username=${MYSQL_USER:root} +spring.datasource.password=${MYSQL_PASSWORD:Mysql#data} +# mybatis +mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl diff --git a/orion-ops-api/orion-ops-web/src/main/resources/application-test.properties b/orion-ops-api/orion-ops-web/src/main/resources/application-test.properties new file mode 100644 index 0000000..8250363 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/resources/application-test.properties @@ -0,0 +1,10 @@ +# redis +spring.redis.host=${REDIS_HOST:192.168.88.101} +spring.redis.port=${REDIS_PORT:6379} +spring.redis.password=${REDIS_PASSWORD:Redis#data} +# mysql +spring.datasource.url=jdbc:mysql://${MYSQL_HOST:192.168.88.101}:3306/orion-ops?useUnicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=Asia/Shanghai&autoReconnect=true +spring.datasource.username=${MYSQL_USER:root} +spring.datasource.password=${MYSQL_PASSWORD:Mysql#data} +# mybatis +mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl diff --git a/orion-ops-api/orion-ops-web/src/main/resources/application.properties b/orion-ops-api/orion-ops-web/src/main/resources/application.properties new file mode 100644 index 0000000..6e64a97 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/resources/application.properties @@ -0,0 +1,49 @@ +spring.application.name=orion-ops +server.port=9119 +spring.data.cassandra.request.timeout=10000 +spring.profiles.active=dev +# redis +spring.redis.jedis.pool.max-active=8 +spring.redis.jedis.pool.max-wait=-1 +spring.redis.jedis.pool.max-idle=8 +spring.redis.jedis.pool.min-idle=0 +spring.redis.timeout=3000 +# datasource +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.type=com.alibaba.druid.pool.DruidDataSource +# mybatis +mybatis-plus.configuration.map-underscore-to-camel-case=true +mybatis-plus.mapper-locations=classpath*:mapper/*Mapper.xml +mybatis-plus.global-config.db-config.logic-delete-field=deleted +mybatis-plus.global-config.db-config.logic-not-delete-value=1 +mybatis-plus.global-config.db-config.logic-delete-value=2 +# tomcat +spring.servlet.multipart.max-file-size=2048MB +spring.servlet.multipart.max-request-size=2096MB +server.tomcat.connection-timeout=18000000 +# log +spring.output.ansi.enabled=detect +logging.file.path=${user.home}/orion/logs/orion-ops +logging.file.name=${logging.file.path}/orion-ops.log +logging.logback.rollingpolicy.clean-history-on-start=false +logging.logback.rollingpolicy.file-name-pattern=${logging.file.path}/rolling/orion-ops-rolling-%d{yyyy-MM-dd}.%i.log +logging.logback.rollingpolicy.max-history=30 +logging.logback.rollingpolicy.max-file-size=64MB +logging.logback.rollingpolicy.total-size-cap=0B +# app +app.version=1.3.1 +login.token.header=O-Login-Token +value.mix.secret.key=${SECRET_KEY:orion_ops} +demo.mode=${DEMO_MODE:false} + +log.interceptor.expression=execution (* cn.orionsec.ops.controller.*.*(..)) && !@annotation(cn.orionsec.ops.annotation.IgnoreLog) + +log.interceptor.ignore.fields=avatar,password,beforePassword,protectPassword,commandLine,metrics + +expose.api.access.header=accessToken +expose.api.access.secret=ops_access + +machine.monitor.latest.version=1.1.1 +machine.monitor.default.url=http://{}:9220 +machine.monitor.default.access.header=accessToken +machine.monitor.default.access.token=agent_access diff --git a/orion-ops-api/orion-ops-web/src/main/resources/banner.txt b/orion-ops-api/orion-ops-web/src/main/resources/banner.txt new file mode 100644 index 0000000..e08d80e --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/resources/banner.txt @@ -0,0 +1,9 @@ + + _____ ____ ______ _____ __ __ _____ ____ ____ +/\ __`\/\ _`\ /\__ _\/\ __`\/\ \/\ \ /\ __`\/\ _`\ /\ _`\ +\ \ \/\ \ \ \L\ \/_/\ \/\ \ \/\ \ \ `\\ \ \ \ \/\ \ \ \L\ \ \,\L\_\ + \ \ \ \ \ \ , / \ \ \ \ \ \ \ \ \ , ` \ ______\ \ \ \ \ \ ,__/\/_\__ \ + \ \ \_\ \ \ \\ \ \_\ \_\ \ \_\ \ \ \`\ \/\______\ \ \_\ \ \ \/ /\ \L\ \ + \ \_____\ \_\ \_\/\_____\ \_____\ \_\ \_\/______/\ \_____\ \_\ \ `\____\ + \/_____/\/_/\/ /\/_____/\/_____/\/_/\/_/ \/_____/\/_/ \/_____/ + diff --git a/orion-ops-api/orion-ops-web/src/main/resources/config/spring-common.xml b/orion-ops-api/orion-ops-web/src/main/resources/config/spring-common.xml new file mode 100644 index 0000000..be31586 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/resources/config/spring-common.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/orion-ops-api/orion-ops-web/src/main/resources/menu/menu-admin.json b/orion-ops-api/orion-ops-web/src/main/resources/menu/menu-admin.json new file mode 100644 index 0000000..4915090 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/resources/menu/menu-admin.json @@ -0,0 +1,187 @@ +[ + { + "name": "控制台", + "path": "/console", + "icon": "dashboard" + }, + { + "name": "机器管理", + "icon": "desktop", + "children": [ + { + "name": "机器列表", + "path": "/machine/list" + }, + { + "name": "机器密钥", + "path": "/machine/key" + }, + { + "name": "机器监控", + "path": "/machine/monitor/list" + }, + { + "name": "环境变量", + "path": "/machine/env" + }, + { + "name": "机器代理", + "path": "/machine/proxy" + }, + { + "name": "终端日志", + "path": "/terminal/logs" + }, + { + "name": "终端会话", + "path": "/terminal/session" + } + ] + }, + { + "name": "执行管理", + "icon": "apartment", + "children": [ + { + "name": "批量执行", + "path": "/batch/exec/add" + }, + { + "name": "执行记录", + "path": "/batch/exec/list" + }, + { + "name": "批量上传", + "path": "/batch/upload" + }, + { + "name": "日志面板", + "path": "/log/list" + } + ] + }, + { + "name": "调度任务", + "icon": "schedule", + "children": [ + { + "name": "任务列表", + "path": "/scheduler/list" + }, + { + "name": "执行记录", + "path": "/scheduler/record" + }, + { + "name": "执行统计", + "path": "/scheduler/statistics" + } + ] + }, + { + "name": "应用管理", + "icon": "appstore", + "children": [ + { + "name": "环境管理", + "path": "/app/profile" + }, + { + "name": "应用列表", + "path": "/app/list" + }, + { + "name": "流水线配置", + "path": "/app/pipeline/list" + }, + { + "name": "环境变量", + "path": "/app/env" + }, + { + "name": "版本仓库", + "path": "/app/repo" + } + ] + }, + { + "name": "构建发布", + "icon": "deployment-unit", + "children": [ + { + "name": "应用构建", + "path": "/app/build/list" + }, + { + "name": "应用发布", + "path": "/app/release/list" + }, + { + "name": "流水线任务", + "path": "/app/pipeline/task" + }, + { + "name": "构建统计", + "path": "/app/build/statistics" + }, + { + "name": "发布统计", + "path": "/app/release/statistics" + }, + { + "name": "流水线统计", + "path": "/app/pipeline/statistics" + } + ] + }, + { + "name": "用户中心", + "icon": "user", + "children": [ + { + "name": "用户列表", + "path": "/user/list" + }, + { + "name": "报警联系组", + "path": "/alarm/group/list" + }, + { + "name": "个人信息", + "path": "/user/detail" + }, + { + "name": "操作日志", + "path": "/user/event/logs" + } + ] + }, + { + "name": "信息管理", + "icon": "container", + "children": [ + { + "name": "模板配置", + "path": "/template/list" + }, + { + "name": "webhook 配置", + "path": "/webhook/list" + } + ] + }, + { + "name": "系统管理", + "icon": "control", + "children": [ + { + "name": "系统变量", + "path": "/system/env" + }, + { + "name": "系统设置", + "path": "/system/setting" + } + ] + } +] \ No newline at end of file diff --git a/orion-ops-api/orion-ops-web/src/main/resources/menu/menu-dev.json b/orion-ops-api/orion-ops-web/src/main/resources/menu/menu-dev.json new file mode 100644 index 0000000..b10eea1 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/resources/menu/menu-dev.json @@ -0,0 +1,171 @@ +[ + { + "name": "控制台", + "path": "/console", + "icon": "dashboard" + }, + { + "name": "机器管理", + "icon": "desktop", + "children": [ + { + "name": "机器列表", + "path": "/machine/list" + }, + { + "name": "机器密钥", + "path": "/machine/key" + }, + { + "name": "机器监控", + "path": "/machine/monitor/list" + }, + { + "name": "环境变量", + "path": "/machine/env" + }, + { + "name": "机器代理", + "path": "/machine/proxy" + }, + { + "name": "终端日志", + "path": "/terminal/logs" + } + ] + }, + { + "name": "执行管理", + "icon": "apartment", + "children": [ + { + "name": "批量执行", + "path": "/batch/exec/add" + }, + { + "name": "执行记录", + "path": "/batch/exec/list" + }, + { + "name": "批量上传", + "path": "/batch/upload" + }, + { + "name": "日志面板", + "path": "/log/list" + } + ] + }, + { + "name": "调度任务", + "icon": "schedule", + "children": [ + { + "name": "任务列表", + "path": "/scheduler/list" + }, + { + "name": "执行记录", + "path": "/scheduler/record" + }, + { + "name": "执行统计", + "path": "/scheduler/statistics" + } + ] + }, + { + "name": "应用管理", + "icon": "appstore", + "children": [ + { + "name": "应用列表", + "path": "/app/list" + }, + { + "name": "流水线配置", + "path": "/app/pipeline/list" + }, + { + "name": "环境变量", + "path": "/app/env" + }, + { + "name": "版本仓库", + "path": "/app/repo" + } + ] + }, + { + "name": "构建发布", + "icon": "deployment-unit", + "children": [ + { + "name": "应用构建", + "path": "/app/build/list" + }, + { + "name": "应用发布", + "path": "/app/release/list" + }, + { + "name": "流水线任务", + "path": "/app/pipeline/task" + }, + { + "name": "构建统计", + "path": "/app/build/statistics" + }, + { + "name": "发布统计", + "path": "/app/release/statistics" + }, + { + "name": "流水线统计", + "path": "/app/pipeline/statistics" + } + ] + }, + { + "name": "用户中心", + "icon": "user", + "children": [ + { + "name": "用户列表", + "path": "/user/list" + }, + { + "name": "报警联系组", + "path": "/alarm/group/list" + }, + { + "name": "个人信息", + "path": "/user/detail" + } + ] + }, + { + "name": "信息管理", + "icon": "container", + "children": [ + { + "name": "模板配置", + "path": "/template/list" + }, + { + "name": "webhook 配置", + "path": "/webhook/list" + } + ] + }, + { + "name": "系统管理", + "icon": "control", + "children": [ + { + "name": "系统变量", + "path": "/system/env" + } + ] + } +] \ No newline at end of file diff --git a/orion-ops-api/orion-ops-web/src/main/resources/menu/menu-opt.json b/orion-ops-api/orion-ops-web/src/main/resources/menu/menu-opt.json new file mode 100644 index 0000000..b10eea1 --- /dev/null +++ b/orion-ops-api/orion-ops-web/src/main/resources/menu/menu-opt.json @@ -0,0 +1,171 @@ +[ + { + "name": "控制台", + "path": "/console", + "icon": "dashboard" + }, + { + "name": "机器管理", + "icon": "desktop", + "children": [ + { + "name": "机器列表", + "path": "/machine/list" + }, + { + "name": "机器密钥", + "path": "/machine/key" + }, + { + "name": "机器监控", + "path": "/machine/monitor/list" + }, + { + "name": "环境变量", + "path": "/machine/env" + }, + { + "name": "机器代理", + "path": "/machine/proxy" + }, + { + "name": "终端日志", + "path": "/terminal/logs" + } + ] + }, + { + "name": "执行管理", + "icon": "apartment", + "children": [ + { + "name": "批量执行", + "path": "/batch/exec/add" + }, + { + "name": "执行记录", + "path": "/batch/exec/list" + }, + { + "name": "批量上传", + "path": "/batch/upload" + }, + { + "name": "日志面板", + "path": "/log/list" + } + ] + }, + { + "name": "调度任务", + "icon": "schedule", + "children": [ + { + "name": "任务列表", + "path": "/scheduler/list" + }, + { + "name": "执行记录", + "path": "/scheduler/record" + }, + { + "name": "执行统计", + "path": "/scheduler/statistics" + } + ] + }, + { + "name": "应用管理", + "icon": "appstore", + "children": [ + { + "name": "应用列表", + "path": "/app/list" + }, + { + "name": "流水线配置", + "path": "/app/pipeline/list" + }, + { + "name": "环境变量", + "path": "/app/env" + }, + { + "name": "版本仓库", + "path": "/app/repo" + } + ] + }, + { + "name": "构建发布", + "icon": "deployment-unit", + "children": [ + { + "name": "应用构建", + "path": "/app/build/list" + }, + { + "name": "应用发布", + "path": "/app/release/list" + }, + { + "name": "流水线任务", + "path": "/app/pipeline/task" + }, + { + "name": "构建统计", + "path": "/app/build/statistics" + }, + { + "name": "发布统计", + "path": "/app/release/statistics" + }, + { + "name": "流水线统计", + "path": "/app/pipeline/statistics" + } + ] + }, + { + "name": "用户中心", + "icon": "user", + "children": [ + { + "name": "用户列表", + "path": "/user/list" + }, + { + "name": "报警联系组", + "path": "/alarm/group/list" + }, + { + "name": "个人信息", + "path": "/user/detail" + } + ] + }, + { + "name": "信息管理", + "icon": "container", + "children": [ + { + "name": "模板配置", + "path": "/template/list" + }, + { + "name": "webhook 配置", + "path": "/webhook/list" + } + ] + }, + { + "name": "系统管理", + "icon": "control", + "children": [ + { + "name": "系统变量", + "path": "/system/env" + } + ] + } +] \ No newline at end of file diff --git a/orion-ops-api/pom.xml b/orion-ops-api/pom.xml new file mode 100644 index 0000000..d3d82b0 --- /dev/null +++ b/orion-ops-api/pom.xml @@ -0,0 +1,187 @@ + + + + org.springframework.boot + spring-boot-starter-parent + 2.5.1 + + + + orion-ops-api + cn.orionsec.ops + orion-ops-api + 1.3.1 + pom + 4.0.0 + https://github.com/lijiahangmax/orion-ops + + + orion-ops-model + orion-ops-common + orion-ops-mapping + orion-ops-dao + orion-ops-runner + orion-ops-service + orion-ops-web + orion-ops-data + + + + 1.8 + 1.18.20 + 2.5.1 + 3.4.0 + 8.0.25 + 3.6.0 + 2.3 + 1.1.20 + 2.9.2 + 2.0.5 + 2.0.1 + 2.0.0 + 1.9.7 + + + + + + + org.springframework.boot + spring-boot-starter-web + ${spring.boot.version} + + + + + org.springframework.boot + spring-boot-starter-websocket + ${spring.boot.version} + + + + + com.baomidou + mybatis-plus-boot-starter + ${mybatis.plus.version} + + + com.baomidou + mybatis-plus-generator + ${mybatis.plus.version} + + + org.apache.velocity + velocity-engine-core + ${velocity.version} + + + + + org.springframework.boot + spring-boot-devtools + ${spring.boot.version} + runtime + true + + + + + mysql + mysql-connector-java + ${mysql.connector.version} + runtime + + + com.alibaba + druid-spring-boot-starter + ${druid.version} + + + + + org.projectlombok + lombok + ${lombok.version} + + + + + org.springframework.boot + spring-boot-starter-test + ${spring.boot.version} + test + + + + + org.springframework.boot + spring-boot-starter-data-redis + ${spring.boot.version} + + + io.lettuce + lettuce-core + + + + + redis.clients + jedis + ${jedis.version} + + + + + org.aspectj + aspectjweaver + ${aspectj.version} + + + + + io.springfox + springfox-swagger2 + ${swagger.version} + + + io.springfox + springfox-swagger-ui + ${swagger.version} + + + + + com.github.xiaoymin + knife4j-micro-spring-boot-starter + ${knife4j.version} + + + com.github.xiaoymin + knife4j-spring-ui + ${knife4j.version} + + + + + com.aliyun + alibaba-dingtalk-service-sdk + ${dingding.service.version} + + + + + cn.orionsec.kit + orion-all + ${orion.kit.version} + + + orion-log + cn.orionsec.kit + + + + + + + diff --git a/orion-ops-vue/.editorconfig b/orion-ops-vue/.editorconfig new file mode 100644 index 0000000..7053c49 --- /dev/null +++ b/orion-ops-vue/.editorconfig @@ -0,0 +1,5 @@ +[*.{js,jsx,ts,tsx,vue}] +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/orion-ops-vue/.env b/orion-ops-vue/.env new file mode 100644 index 0000000..22ae45c --- /dev/null +++ b/orion-ops-vue/.env @@ -0,0 +1,3 @@ +VUE_APP_BASE_API='/orion/api' +VUE_APP_WATERMARK=true +VUE_APP_DEMO_MODE=false diff --git a/orion-ops-vue/.env.dev b/orion-ops-vue/.env.dev new file mode 100644 index 0000000..22ae45c --- /dev/null +++ b/orion-ops-vue/.env.dev @@ -0,0 +1,3 @@ +VUE_APP_BASE_API='/orion/api' +VUE_APP_WATERMARK=true +VUE_APP_DEMO_MODE=false diff --git a/orion-ops-vue/.env.production b/orion-ops-vue/.env.production new file mode 100644 index 0000000..22ae45c --- /dev/null +++ b/orion-ops-vue/.env.production @@ -0,0 +1,3 @@ +VUE_APP_BASE_API='/orion/api' +VUE_APP_WATERMARK=true +VUE_APP_DEMO_MODE=false diff --git a/orion-ops-vue/.gitignore b/orion-ops-vue/.gitignore new file mode 100644 index 0000000..403adbc --- /dev/null +++ b/orion-ops-vue/.gitignore @@ -0,0 +1,23 @@ +.DS_Store +node_modules +/dist + + +# local env files +.env.local +.env.*.local + +# Log files +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/orion-ops-vue/babel.config.js b/orion-ops-vue/babel.config.js new file mode 100644 index 0000000..e955840 --- /dev/null +++ b/orion-ops-vue/babel.config.js @@ -0,0 +1,5 @@ +module.exports = { + presets: [ + '@vue/cli-plugin-babel/preset' + ] +} diff --git a/orion-ops-vue/jsconfig.json b/orion-ops-vue/jsconfig.json new file mode 100644 index 0000000..4aafc5f --- /dev/null +++ b/orion-ops-vue/jsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "esnext", + "baseUrl": "./", + "moduleResolution": "node", + "paths": { + "@/*": [ + "src/*" + ] + }, + "lib": [ + "esnext", + "dom", + "dom.iterable", + "scripthost" + ] + } +} diff --git a/orion-ops-vue/package.json b/orion-ops-vue/package.json new file mode 100644 index 0000000..751131b --- /dev/null +++ b/orion-ops-vue/package.json @@ -0,0 +1,60 @@ +{ + "name": "orion-ops-vue", + "version": "1.3.1", + "private": true, + "scripts": { + "serve": "vue-cli-service serve --mode dev", + "build": "vue-cli-service build --mode production", + "build:demo": "set VUE_APP_DEMO_MODE=true&& npm run build", + "lint": "vue-cli-service lint" + }, + "dependencies": { + "@antv/g2": "^4.1.48", + "ant-design-vue": "^1.7.8", + "asciinema-player": "^3.0.1", + "axios": "^0.23.0", + "core-js": "^3.8.3", + "js-md5": "^0.7.3", + "lodash": "^4.17.21", + "vue": "^2.6.14", + "vue-router": "^3.5.1", + "vue2-ace-editor": "^0.0.15", + "xterm": "^4.14.1", + "xterm-addon-fit": "^0.5.0", + "xterm-addon-search": "^0.8.1", + "xterm-addon-web-links": "^0.4.0" + }, + "devDependencies": { + "@babel/core": "^7.12.16", + "@babel/eslint-parser": "^7.12.16", + "@vue/cli-plugin-babel": "~5.0.0", + "@vue/cli-plugin-eslint": "~5.0.0", + "@vue/cli-plugin-router": "~5.0.0", + "@vue/cli-service": "~5.0.0", + "eslint": "^7.32.0", + "eslint-plugin-vue": "^8.0.3", + "less": "^4.0.0", + "less-loader": "^8.0.0", + "vue-template-compiler": "^2.6.14" + }, + "eslintConfig": { + "root": true, + "env": { + "node": true + }, + "extends": [ + "plugin:vue/essential", + "eslint:recommended" + ], + "parserOptions": { + "parser": "@babel/eslint-parser" + }, + "rules": {} + }, + "eslintIgnore": ["*"], + "browserslist": [ + "> 1%", + "last 2 versions", + "not dead" + ] +} diff --git a/orion-ops-vue/public/favicon.ico b/orion-ops-vue/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..f52e00e18f825809ae7bd77e3c76c02a84d64d8c GIT binary patch literal 41662 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 b^pV%I!5n!(X0EJX;%Mv3+e?xmlYRal9=XQA literal 0 HcmV?d00001 diff --git a/orion-ops-vue/public/index.html b/orion-ops-vue/public/index.html new file mode 100644 index 0000000..4059238 --- /dev/null +++ b/orion-ops-vue/public/index.html @@ -0,0 +1,13 @@ + + + + + + + + orion-ops + + +

+ + diff --git a/orion-ops-vue/src/App.vue b/orion-ops-vue/src/App.vue new file mode 100644 index 0000000..d123007 --- /dev/null +++ b/orion-ops-vue/src/App.vue @@ -0,0 +1,25 @@ + + + diff --git a/orion-ops-vue/src/assets/left-top-bg.png b/orion-ops-vue/src/assets/left-top-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..e096177699a446c7683157e598e2a0551267154b GIT binary patch literal 37305 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 T!FOq}qA2B?>I!-C#?Sr_P&s-- literal 0 HcmV?d00001 diff --git a/orion-ops-vue/src/assets/login-btn.png b/orion-ops-vue/src/assets/login-btn.png new file mode 100644 index 0000000000000000000000000000000000000000..0c7b5df2f3aab4c64c3b3ade5e24ef4a75046f00 GIT binary patch literal 71586 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

|zTP+r_BqgGJ^h@tCoy1QOR=I_L*|@& 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 literal 0 HcmV?d00001 diff --git a/orion-ops-vue/src/assets/logo.svg b/orion-ops-vue/src/assets/logo.svg new file mode 100644 index 0000000..7f589e1 --- /dev/null +++ b/orion-ops-vue/src/assets/logo.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + diff --git a/orion-ops-vue/src/assets/logo_100.png b/orion-ops-vue/src/assets/logo_100.png new file mode 100644 index 0000000000000000000000000000000000000000..33179b21fd2f48ad376a9c0e90174da86972be51 GIT binary patch literal 4121 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 QRpSBL>UwH5Dz=gT2ij`#AOHXW literal 0 HcmV?d00001 diff --git a/orion-ops-vue/src/assets/logo_horizontal.png b/orion-ops-vue/src/assets/logo_horizontal.png new file mode 100644 index 0000000000000000000000000000000000000000..34cc408e4b845ceea35651b438460b094e5bed90 GIT binary patch literal 50857 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

I~IxH%>6+w6!{2oYqB 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 literal 0 HcmV?d00001 diff --git a/orion-ops-vue/src/assets/right-bottom-bg.png b/orion-ops-vue/src/assets/right-bottom-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..3f13825054a6d52c14652a6d85b094e13cd31015 GIT binary patch literal 23598 zcmeFZby(G3w=cSABt(yAl

zZth%5-`~6UzI&f@p8M~)Jo*50%^39^bBxazU&0@&$`RsG;Xxn}LIrtgbqE9_4FW+= z#YG1rSLfUhz_)8~c|8{h1pg-TKN=+I10@84wPUTR>#D1wEd0#T9%^dtXa<9N+QY$Y z2t-uE6K?v<7UoKC2D7ww5M$V?X<(qYHWy>i;ZxyMflI-xtmVC(VH)15n$Ns#p9z^W zNQl#mdJ2OL>|w5^^q%&14lcr;VhpH#g~89rVGag*)D%}+F$PKGf%Lj6kLjfxoniF+ zP)_z|oSfYB0zyzOeqLTdK{k4BPHs*PP5}-sPIhi?VNQNwJ|6nNUkuW|7TkVm%m~H0^{&Bg>!I0Igu=(CYnF{XCB9 z8F#?OUxEC$vt2a3;4lt#n2V#E^D~%?J0SBek{h_NlrzlK)zMkg(b4W-ihBGnlIgj* zp`7$AI@S*6jvg-e|HA^9w5cmhi~*@Nb}lY<9!^azUSXg(ZUI(KL19kLKT}m4&8;oG z{)ef2y#Hw`Fd1`GSJVIPWbanwr4%FJ( zT-cP0lh52-kdvK9kdvRCUyzrR-OQX1#%>PdW{a{ZsD z!QAl~*yG>inG5l9^75MTv74E4a2??08bMbKsa0&{*1bMi5|FxT%vo&}i zP3`{Uu1KoP!HzHg{co+1<9{OF#nk*Kal=!tNs5%`d_g;vodwCgaHrB z!GL6j1L=$YD23y{>I?PRfACxXnhu;H^2dL?F!<#kKMr#MTRQ_Uo#^u`7Xs0mSCE#} z^!%_f=@od_!)y5}`*Tjrul&vUUu_c!V)Mn3gk1c;qnfVc;2!?Cga7>cBVGE?H!f1U z<815DQ%-B>5I6DzH$uxAp1^3K#vf?y+!e}RhxL>!9+sG;m#bH%pKn|!Kt(uSP9}4$8cJVF6)DR`u0eW_ zE;=_SCnu=@k(7mHL;-1JR;-%9t;XgxP0BLWBxotJ7g^B{WO>4_LWD0Rs+2DaL$j)Ou^9a zQd&}nV(d7F9iJ@CE%evDV@DWcN=#$d$Ji+hS;l)~DG6;M3@-wy8M3UQY(H<>quu_J zBP=RdU@w=xua3wg<3uGTm|bv)8}tmW0O*3~;nFZJCJ;ujT2E!tJGv>d@Uf&Tol zmYX@_;D|wAeEdSq^gEnQr)8V)cN5$A&fR6r(c_R)3N(SIS=QuuE|J(*WPyz=uVOmP zZ-z_vg}BbIeD+=C;ZE>4DG~ALN}m3q4h{5|X(Ws>rEE33v+@A)@})WVX#Kd?;f&wz zkqLRUZ2oKWg^vw)5dz-MPLORm>mIj~wRE6^oE^^(;zm!kB4sm-3k~A63I&qs z`yTNpBQo430?u?!dh(~H7=-Azj@d{3Qz|^rAR{F3U{k%}3B&r2J(xx#u^pP+iLZZ% zQ#M*2c-QIYQ^~g4Ja!Hst<&$uDC*SE7ZyoN0|PNF_`E9Aw6uttZsJS1A`oL$owp5umpFt!aV^A}rH*pp~iaWTo22%O2ao&4{)=wmV}G<~E_9 zZ$TNFdatbIOS}E_;X)_1Ku7PDPt{IYiIYp$$S<}NiRs;iO~{Ne-wOJ0ymQsBwXciy z27uk@#n;Kby<_a&N?nWUzlB3KYYgH$H%$(kBNp=;?sQpSjUg6z0$6=g?Jhgzj@rN8 z1V(?x2MMgvZ90gyP9o~6Xlc3Y$^`L$)iw`)%1#AsWO`uJIt+R8wCpPM)#3TpKeFtHXTY)2)$N9z=tmT1WbLWK$B8 zW0e9ncFNw2{Et<3iK)A@h)GCb9B$>MlhvwHcG!6yX5%fQ)cVI|vzIH9M};RzBV9}R zthYry8&}!m@2iO~`QL-gK+y}-*t%?n*0K@8dk)Xjtdg=2-h1h;wEv=Z zkx9s7^fYRGap!j7<>5*Ci?cid2>r}y=FmB&me#El2qr6Cbj2y3%A$|a%U3IQPicRARmSC7@4HpE;!#5`iT5$*D_)tsKanA~KnV&hHf zRcjpaSh?>G&p`j|_o3^N36MZ0UGaT|T*p`%BkUKFc`}TEeZE}#y7dy)1&V3<)zY6S z@d4*JUTZtcq9X6(D<&9sT|0hR|Zto^AVyiIse$cv8yKN8m55Q{p~Osl0Y z+kaQqUKs60IKJV*AAH5&=$&g`!a9BM>!x$t2JJ|TV+!O24G?_3wvAMYIA)$x{twNt z1rN53*ZNf}>OwWTGH(;=4gKej%&nxQ7vcokLnsP;y9|x%cfD5UBHr{gML?P($+1#d zUJdYeg_=k67yT$ZWejDUffjZN=kki^9G@HQhHb^wY`xK$EZV)_6~p1H_&{m$7tb$Q zG01g#ZMffn4Zf26p%sShhZcH?R90&nJF|lk(emsQciktSu8QI%RJWx@2sM=+iy!t^X6Dk&mbdD%%>D;aicmp!)U#E3iP zE+1v7@_Vhta~yd0ImpF99@2w{PJzilb@+Z9`Nf~dPCTqP@2a3O=i5lHa+jP0OSI_u z2b`_b=c3iw+1$<!3a)q`I&rS&b$YF8?OwXgZo8E85p7+((}7YlfjK0w=t(Dxh-zC+ zTw*+j`VM(SWK>j$c^7{X`7gwQuYdUv32{;u7&|eH`IepfMlFD$9X$qPw_f5cXRANN zDvBJ-gpjD5CfZ@#y;yhVDNo~Va82p5l`2-u9a|H2$|SUx(@K)VCs`FLAg35;BRaq# znL5RTOx(!XuWgas0o zS8y6d4!r#wpj&<3<5s+(p`jOSU;U^By1GnA8vV#*rnW7vb<1Ca=whLbI3{Hg5)ld4 zHkRc1`VVswOe>-f6SZ*C=+7pA@lf+~dySE+AiF)ksHWZvkaZ~&)0J=YG@(*QTOqh zir8!&pwp@~`2~m$^9OI`IiI4fJgPG;)XoikV(pgHGD<3Qi51Ees3ntble?mG-N~TR%-dJ zN}6HTNN+Pq_gv5iq>je5V_36F&2 zkzY4=l^ox76}s*vL1viI9k$uJl2SQ_ZOYQ{(S{jR24!Wr{rWu+Gc#ya2jq#utIe5E z@08k2m6+~o_7fz`Juc?NbuJYbQ`3}8U?D)0goI&UQ}!Nq21M&|VYlBf)7|{G zs7K!WwxbiK(@v(-c^XMafdlaeA!d{*<)U-=waP#_5IN8&A5Hp~s16~VUDb)i?ha~j z^W4jn`KtN7TxN(d?%n#A*J|xQ*h^dzl=JYbwIOh7G=a~`gS@X_nNs#ht)i9QCyxL^ z(X@Fu->3(9DU9y$TQ44#WRY(%gaA>L!#Mw4ia@RVuWm%N&h2=L(&=cajJP?oL-J_< zq*schheQ}4v#|WXBI$ZX<%*^o0@KT~9=6fW*UlY5M9Q|5+six&D!|7rl+7TCXeE^i zZ<|L(vssAhENjUTEXa1aI#eL208Qn6mNg|=3#Z*UQ8hU|o>{RjNGO;zsta(8A`0yc zcE73XlMcSixzq8PQ9Y)+b6dVI4lU3UxAK$5SIy2v^?Yso>g)7LD3vqEcBJ9&JRW-E zr`Gk{&w1rmVj_9X)B<1lXS?5wHSS$l6rG2AjIu3Mfw;h4URo#N{U zC~HuvSU%5Kl`baWw1DRK_yw7!mht}Ju8TnXbuNmBsds0vdrR_8P8jej9@1-S(cbFV zKOis7ymzuGu=w0(qDr6Pp1^sKp-cYuJb&ON$PwOKCxtW{W;6w0(R02^2Wf&d1BvUM zNkQTJCa14|E*jOZgXevzqWB$tnhA*W%_(Jzb#bflAPnF-DBp5V5%Wwvzes4!c*1)3 zme=xD`)Oo~Aq_+n?EKm~$>n2S^%tN68&SG!H40s=jQ%eph7`;x8&MR9lqiBQN(mCtgO7>i;h}!uKMU7`tWzT@9LnLpx?Ef@3R=Y!2vsG zS&%+H^nz(}@+DgL@OwAH0d;iIXL>h^vcAvn7&`niDY5S&I;^6RT8+L)>loMq?^1dfM5azNNu_3@vtdvg?8?(&{<1`4bBJ7;}v36yFXNR z_pXc`eDm!lCU*JwlrDQKM5{y~m8yi*fn}M7&@ykEs;zie{Nrar^Gkc*+#{w2oU2I7 zRX?3=#%GqCCDd)3c_AFzoyQsug-?D?cTO=UH|+L{Ij_C2`@P^#2!eHO_{FoNtd`x4 z?QPtT*8)@Lc8<5V<#R3ByYRG_9qOPSQ?r*axwZwJX{hCJ$uq91OWEv^=@Y*rTL?0l zym&?#r>zDAP?S%GW-G~^=xX)D?fB=ZYiEydX}WD)#|DJ`{U*8UAt8&S#}18TW%*wI)?Xj8q61iviT%JQtm}cqG~&VWWUu@z+M+AH_{8pj45`Pcynf!@>%1+u!LOcWRvbYH z0TGEcpM<2ARwy&131nsY;-LrLlgnmFNr_H-Hjd|fdKLbQ!8pY2M`Ls)B4UdcVjwL9 zDT2OVvZj_S2}YCcfr2aL#&WjMQPlOCLt(Ebd>wIJ{ddU{tnB62c$?l@KpsVbOh-1O zf8QzI@j0i}fr8yZ$ARA2JdGLGEqSH9)`UZQk%i>|r^VoMc9ZkhuC9VkeA4sxAZgd) ziTwuF#u?=j&`ht}l3WAqciR|n)U4y3va-0dXTu@ z=^=LTV#3L%?O;2Z8FB|CoC>*dV{h_^(QFNKxrXkX&?<>!XhW+td!X7+7BV>!rjNyL zQIU{%EfPA$Q=j|@JGnzpNk`nRoJJ%|^5G6ei zx_ijP!Qrz$i<=?~S^{~#oh1wSsWF}E0tWwa1CZR^?tRStz5d%L0DemQk ziQ=;59JY>W|I*2yr`s6LSHv}JvQNx?jS{x7AOY9me(5>-x!E~Ghy3yw+o3I%eY3HM zY+8|VZ@oJl0eg~FXVcnVqrVhRe(2P2SnS0!Wv8J%{DNFoaT)rQvRi){a@;!0v$EZB zFt}Z$Tv}nJpW9JU4>h^;?dmWa%JJP9G&_W&@AOPgHvCOtvaIv5?US;s_2PREAi5TM z@uLizt!7**_^o&aFGaUbXlp%tgLmIHy!jfdl2W~>R7Y1j{^TCyIxcwVVmjhu-4rur z{yuiS$mE~3U~Tets3U+g3bvsod5#E*(4JtO6rG?bLO z<#si5wOTkI9ZZty;uEyMtr}IBH2lfu?JlrK{p*$K)@RtW?g#{Er}#fA@DBfyB)a=% z!cVRcW_U5cgmDW(Z}-kQF&O(iwzs!@O%@=80J*s{T0)qIb>JUV*;N`=Jpu(7r&pA2yvYh}MV{1Q; zth=)kTTAfCsmQ3VvJot}iRYcv)bWa~#-IDEk3K#AO1b(;(r8e?VXO$&wPZhDUOIMz zaGwM?e-P$=`<87W^Cu1IH80tJ0HOOjg6LT-2lqcAAremFcZg4Dc(hqLxpns7Rqbr| zjik3XRzmH^eGWyv+h{!|{f_E*2D1(7?5tQWG$hb`d@8@JPABd4aLyS9veqi=+u1q^zuC|`g3{Q2|QS(0hRHO)Hc?W>nGLJJ5LlZ(CG zPHNw}yM~&%Lzk;Q71O?Z?6I-5AA0f-W4-lrmQ%a&CZomAl0C}M=s;f3SiVtJ<(n%T z!gC{B8bskKTpu2qd}GVi)HghsdH5I1dsVLc=IQ_~k>k|0M#-4|YL@oKX26Zo{h46m zfEB6RA_EGgV#(?4xyA>trZ4KIDXFR5DmWW8{)r7Nl@kAEKYgVT*H6ZUI>3&;lnQsx z+PzAD9ToM}=JvjW%Ey>!KivngUdPgB3AK9_Ldg{zaf*w?=R&ugPS_WVc!MZM}^C;}toH zow7pz76f(O3|7KOQymI>l#fU}c#JhBmX~Zx9!RLtAG2{^G@hD?EUb%bw@;7_M#9D8iL+#G#)#>zQti%y|^}|L& zl0jYy@+G}aEZ;QivA(_{x~f%3IY90^0SI$5I=re=zSI&{;yg;`+!|NnpV8LZq?CH` zGeP{MqxZzC4H&#c{idbK(lYJyLl8>0alUr0T7dt&5om0c#4q@Mv*yD5W6sFp%4ZsH z*{p1H3q{2U^{+4OBXSfT+#h{%EA>sJf%!5tQJ2Cse`WNj{)pph{^|-p)iJ5#^~CN0 zN8jrRIKxcnJL{y-CtL(AC++tj_ODRYi8!j6ZBpr<&o6o>TnD!_b2odHb%&Ct_QCxZ z-LQJ)_gsqv-qKUTHppwsmup-&f^Zuzzf}dvQ+)DHmyJBSZGNXR$ELa<)|NyeQ~hLQ z>O4v#I}5$4roh(Z>Wrhnq}LJcy6{DvlP7y0Z=~Z-NMM%u{-=g(4<=u|wAP`(Ba>7%!3OB6$aULps!QLAE&$JTl8)=6*#M?L4t zqoQFF#vv44M-iWIp#> z`5t~VIqhmV#(f4Qp+|>8AQ$FNPCLmJ{vs<6w0H6y{YI8!hXTK$c4n@aJmiRbMdC`9 zd)B<%;5+R{jBSK9%z|3BFn;A}(08viv;U36)gEz3c^rDbET}N1Ins!F9?vwjNb2+# zB(4rtOr1tc>O3!d83-tuu3Szz3xa;)F*%(Z5xj%e5y84ekeAKY%RZ|hU$Tsf3GP95o+qNOw zl8TqyX)gEUhb#sg#P&L<(FR_oA=ct_jGT^)j)ZHMWJD^iRyi6hJn5^>M=Gvb6|H6J5KQ(DQj5#xYTRfZ^zG zr2M3|u%L><_%JBVA(Bt&enRkh8K@GkG@sK-=)O-h*5zJGc#V}&Qe@7?c8g=wZz?2N zfYOAbqW&Ez%vEf1qhwo0n)|8(leZ`L?nF!Dkbzy1lY4zDbVLU%yXJd>+zVV~3 zfPWKK&3OaIu%JFE*j9)rYX`RiwKO`=9h`A^T?tq*6qhh8yG{L4!zb^#x)@=N6 z0QnV@WgQ&6YR-MjRfev_zD7Sk$zq7N+Z&NDgUs>@5a9sSwDf{c_Z1n!Kdt+9&fXg_ zsyn5vk8cJ*P5-4FY&gi95Z@VU>bhT`%NDPg ziH(h|H@xA9bJ+E^B|pV5kFxNpJ^R(-y%EQ=bc%-U-plpqu+UJ$25yk#JFxsNH+sMx z-vt{Z{ts8>aEDj+Cjk0=EwxIr;Gh8Re|7&lS4H%OpJLQ)+k=G$v9hZM-$euWma?t0 zSLwAE8x4mDfx3HCw;80b{p%{%tHeV8l(W_@pH*0qR&{o>B%Nh-zi)>A?&;a^td#Ti z!(3)ZpM~(FfO&o$?#+|sEze$`%b9^?iNYf#v;u%1{p9U_WLhvg)jJ3g&SX(!irp9# z*Bs=HXl<^ldE)aKaoR2)nmOUmCw5Ow><;@_VX8RS!SeKZ_C9f##nprUi%{*2))AUS{ndE5J_u*NN{GQ;YB?<>^w|8`kf*@%|# zv$8ncKR0XT9lYAQI=5ltf8$v$V5FmEl>>2Zva`Yz*PX?+~-+ zp>X9Z3_*9`1R!*BSH)mr8p4}95s2cvlHlKQ=C?C&Wg9w_atyO|E$)IHC60ixbX5BN zEMNSkkSNL)*R#z0eZIsr!)_9afkCHR+!5&>!RL5v`$N za;F8f*Ks9UI02OOvBI2?fPjEF`dC{%8$2fB5{SJXO%yJ5UVB$mGheZaaj}Fl^p~xA zVMO}Ntni$U#z2Csdmo9arPFj!P*!4ZuP|GkRZ@J1xgHyusCwZ@L~9UCFWUvSR&Y|tnIVCw{Gx8KTHES=5G zwV=jb2=2WY5D;k2;W|P5f&&q`ikWlUv}Rr#@prBTj>O|k3-eZv*p#WN)f_bKJ|{vW zpBXRbhpdo*vd*IO*CCB!-PZu7vJjdVwqtTRF2#mfTi^dJWlGS3YjZ&f5e7xJ|6185 zO6~wPUoZ*8!6aj@XuGjIgH*bVv^4x@6-HTNSvoJ)K#y~cDAvy)s zd=SU^4h_=tr)vGok^4Gnn3~C#F?dUCxzs@~Iz$Crw?*2z@k2JFQX_7vE|8#fYtYN0 zQbU3)QL{4M?$wI^vK?uyjUzacL(C3s51&_WSNcTX8uaVZM4+soOI~*1E;m1uK{*!& z1qrgK_-}NHG4`u=YTp}7;BbN`jor(O%9vW7KY7bc7kl9Y+f7@r9QRh@XBO#604WrO zG21o$<-Q6~S^het?;z%Gg@!qU0m_T8M24n9?NA%{=g17^qe`*quvPn@T6^$O$Do=b z!cn}NZ0R#6vk%=3tymDBgT@y3jl(R(MkP=ZElo;I%Xu)(9Qd>qREb1E$lB|dvl@Jv zJJRCT{3u_09$!ar2vM>uSvBvTn6h@D&Zm^(sR{mpM+qdVP@lJ@DLFVVh>+FbdX=09 zSJwh(zUGYWOHsvqt9cajtryooOxOKsDrgHyOmsgfVODF`si*h2eCR*xRXx57#@WJBuR=$H%e&iN@8SUW&zl08N0I)}E8qPf@Jk0$#v{W)ysFx|yJeyzKO& zkGy}|4Y2uRQg>>sM$U!jJ`1yFiwogtN+h6ThFk0&00kbU(l&2 zXo**D?c9)a2RG!iE&Mc-iK%H2U zqCak4ZgDU5om#*na=%-~*dVe&+zjI*9^{pB>k}=?(nVIq2D8nI-)DuGVd+SN47%^0 zsM5%_O8+lI3kZ{I)i0;k{nRf{ITlODnRC%L0P@;4d#3TVbj9)>qz8VyAHuBhN}f9r zgyAG_b=iL46-dz4o4j%XM}ISu6u%tdHX*1c$`ivhyb|;c$N}|K(_=jX2nk5?K&_iG z(K#{8+EiE7q9ss*im+<$Op;CYuL1(hP>*|X* zr`PmU1|JOs`uqSD$=~RpGD}HGP#(%Rmx#fA^V9VDZ6UU!DKb|Q-DkL!_0Gqa=KrO* zE!?R5=nYdZsDT6CKIWTA;R3yDzY<=0iuUa0ODIP8aZ=@J$jZ=FORO*y#e?Q2e(o5j<4etaZBQuz zSkn~1np|AG!iL8|2OBT%I;a~A^0I#67#wYkBCF?)nv_&jW}ZP3b_SbQrhPJ= zplOYbn+q2dQnBej(mAJ$)=xwmu>9n@O#zR9>4h|ZaGB13`6VYwmH0$k%LCwI{?II@ z8{ykm$8(!H17A(qwBKI=ZDjldM zP=rfxU3&?UxifvaCl}kJH8EmXEo(RIWKih@=Onb_KsvW&#an0sg(XX=E}_K$Dx` za*&XQ7GDCjN~U^2FA;$B?o6KM87JUu;aB5EkGcu@^8#)212KWFDNr)wXduH%mm4hY z?=Qa8uMrz}Rtta%P(`;HRizamo;{nV=|D6v)5Q%lBV=%l@V0TP9(Z;&Sh69D>)g3j zfsX&w)}PZ2N;7bYix-!cYC;pLH(>*AesIpo2EB&qN$UR9r>x3!c-826gqRbL0WJ&L z$~w$#OiiaU+=U^QgS_@uR)RnG9BMdGUr#ARO*O|-Gif7Q)xUxcLcBwo89oPh?UYe! zSCabo=2#T<^fRIDyN=j|Ei4I2%8gi}>A>)wR^JwJ7zZLrxEj&Q)#~E51>Jty-|TDp zt$3*jWuAcYt|%P>RFV5Fw@Jigs`tqpTkRGZUe2XOJp2WqpC}W0^oOOB75wE1 zADv$KS!^3mZ_#2y(gIXI8oE5+T5z)VA!h8mheQLOpUfIb>de&0N<}$MV2C224SLGZ zI}+ZUD-1#Kg{OUF^lQi)3-eiTi&;U3$*{`=M(v+jN7J;fSZIkKNU_nIG>~WmI;WSs zDh+=1%RtReXK$tzhU?a-VfYnQGEsRmqkZM}4UlI-8rb6&+a&O+Up`c!BdSK1ra}#h zod@b__sL+c;i6F(GXT6{rh;!Om7YO^IaB>wHLf%Ta+V40Z;n9?NeU8lIlb>8pkxk# z9Iw@vd3ft-D7S=opnlITWkFszB(o12|BftQ`g}Gfu6_xB|A7>g;ibq=W>S0THo2TSV1CZ%nD6qDYvfk+?NT^g_B%<^=(3 zh zs!3bEc|~deT^Nxw_tIFb6@= z-y%_SV>KN_Tw!r_4VwOXaiNB<@U=mhFim2&p8!)o|PPs4Htk6B?qBo{J-)c}!VSZp`t ziZ`t60j)IfA0Y@(^?*(^gZ4s*!x%yxW3z0eVBDJ`vpDxYFoIen#FYI-h^RAMrY2l{W9rpLx~p%LT~3h9Y}jF`zj< z-}w?sqUQqo?U~}&$oCNU#?LEIl83>k=lCx{cNQ9x^ zhT5gCx%#?yA;r2#S<<_%e^2pAU3Xh`GOPR0u*V$m(KoB@6?&RY4Bh4dgG@DP9}m(T zp(}L~vq=fz>E{cDVW*rks>opF?)$@;je5j?Xiiz5W-Xt*7{|e0 zJV_8+emlrh=e~~(<78*3E1w-m51c~5Zwk@O`^IjOR%0%7tAm%AZ!M2H>?InqBXr~6 zDHXg6E<+(?YTgs3?n6r2;se39DH}bWWG#}>ZqkYzc2QhjoVY|UCz3_f z#Rc(2Bhj`a?_1a7J32Qbbi_SZ9&#*VDnwo_^oqD2xZVfc-$in7ltbnGIn005)|jc- zrJnf65yRm;FHnT*4#w&x2S7OgP^5Hrq)<1t=rix+)jy;{w>2N=GmK{OiebFmBjCO^ zy#LQuYnf|-ie^3#}+AjG(s@^ zY7puzwE3JIYaa!T3xE{Bu)gca;rB1!=ik(DUa)^l9nUX@C4Zk|H?F4YdY73w#o9b3 zgA^2X-G?psrfU%_?6e$BM|yp^KnziBH#KlE^vXpnK9~>bysTp9R6Pzfj}*WF3ZZSQ7H?dy#W!#Hv7|=3 zwoGf$rOh@+M5yFY+NH!Lk5-3t^nJyCll(LFpIZ-0WHYvO;4g))fGC z%F;^_lc}fF5BBy{=+^yH^*$x&7iFO&_0R+7ePmQe&m?ZQdOT;ouPZkAU>N?)a}*kbj~E};%m_|hR5MZi!wRoa|H6*0U7Ym|6%w!nyyF3C7&n!aN-EP> zI%N>5CZ(IMQtzI*wD@HINJ*iS(57@~SBuKfBK6v!hWSu8yt_^J_GX(Xo6vP;HQs6U z@_A7&3rEX@2f#@dAR`#7-u-thmYKTNcHP~M-Q5jqbHSN8^^O+neRf%zDz6E33Vbi( z!wKy#qV_{LVzN`W^JW4CF(*`ugCaIj>(z1()!(`{*Mdfpu8I$&B)4c|3CG837eH%l z1^|`ijJ|TPsis(69uIG%vwWl-C$A$XIu51-56f6lDI@-#eI2(1Yi!tfZBBZ7VZ92p zvgoY(5zDk0wXw~^VaN4#buFm}N_9Ff_`;BCq9$4wsgW=}ps2X$ax+S(s8h1b+|vj= zdrUD1kL*#4#y}cso&|LJ9Cu8cROZmF=%eC@4E3>MJ>0S_BqXNg6HLV2)%~lkGB%D6 z8;&iijxzK?aB9bZd?{1Y=4!E;ZrW!>Ez6=|a3}H+lNj4bZQkOyWs|-p;mVhWry995 zSU9{$Z}*hX<-}oXs3W~F%a-A=>F#YI?>U~xs6*3`j;Hz#rz#5B2KDr@aSZ|N zOOb_cnb15Tq?4ez6{`F3W8YbxYPsCK%jrjp#a}rj{I=`Lym4|H?!2Xro4$}5Eg6G^ zk2EOAy0|Tmtk_(<%4ZI^`oa87Xr!|xsrfx8*kwpZ;TgNzjR-d*k-g>S^Wf->i+ucE zpR0#9wh0x7lZ}rrB`*7|ysZv&hzPrGpN||j_^r*SUPrOV|>+OOxMx2`5Xj!%gtd3hyx9QE7H zMs<1IRwkbOwSZm-E?<`Yd8)=SQoFa`-8N0nI{wzgBn%a1C8*K~{Q5yxMXZ+op}k*#V=+l}`uFKah^|LIy|ThXQ4B%_1! zzjqSf0mmxse4yhn4i)im^FEPIpTwvk@A65VIKok*JI**6$oM#wY3rWB&v*7wOs)Q; zy`y(5(q?evr*f+C;)BRgn%~o-kqMn zUFE_|L=oSegaRzvwwlLKHFx`JXm5>X`No^o`}cn;z!dBZc#T5CV0MPX`HmH3r5zz6 z_?2DcN@^Rd7sN(gs;}$4MUPxI=?I<_(0ma08((d17#>U3C_kkSc|y3;bl7K%CRp(3WNT2cD0(bw{EkpMi`XM3TiViN z^B7hw@{VY3ek4E^*DQif`3d<9(|dZ4i&YZEzrL%K9YrJJ<|3LxrD(#<>$&sL`Rt*y z0e6+qaC+FMek;l$jkq=QOlNba)yDI=6#e{M$_LzhWs1a731M{O<-g0n|K@u^gfu?f zlopx^=r2Uv+__ZiNm%?<4$G^Ys4r_T$!|xsx{`byNmx6`tTrmPks0SVs#@LuX~SAM z8S)F1Eh0d_E?95uAy#Q`!%G#fRhnnkTI2IlSuKA!ScI2xuQjT0yd%7=k=vgU9gv>1 zw1nqCj~9||%9~1mm}ggzjBt0JlGFgD^{)6{W4WljPNe9Kpl~i z&5l=}nCP^w{+Lv#ux0foHNT?wmxP!i{y!hrB>xQJu3P(D)NLRvhAZrn4>)lIM71RllG0GG`q$N znY_@d(*fDL*>}&M6G{22bgLvAJ@6XGYR9@=ef~(RS&3hm-2`K{YUjH3eRjosrsDSm z1f47I^N{bNwZ$>aAvm#J`Z*TbqeTEBZ zR-e*j=JLYInL^O$km<^GJd%|rvbVPm6YpT6D0qaK#qzR!osG2;mk!D52tn9cA4B2jlf;JXi zRKouvh%Q{HlEFDi!4wx2COzIZB`PRDaQKY!B?~z2?n7Pg)5F1=(;#IgG0`WT+MAN7 zoDJ0hoogfK%^V3`N_3MUPbzU~SW&`Sx*!cP66wA9*3}!{HQYTl8A4`=8HKz z{+gKy^f*daLTf?>#@F#I3yZw(BzpH8)X|2Bbg>v>qM|q!Prn#F?nKeE!ugvZJ~scu zR^jqY0dD2ZdE=CW#?PFXsLlr#&$^EF_4{~pJ9rKtn*8@5TcU1%Fk%*O6QyP({sq*` zTc`uqu)K0>9;BN<2ipHLEa(zdAg5i*;zWf|&xr z70FtnO27&H4=1TEa2azmvx%CFL8~VZ1OKw;0`J{u($aEcc(vj;e*?9n#O~g~%)qCp zhXsF&$Xn3kMRUoH6S3UvKxSat-(mF4s=K7|Uf%kS&;2ZLk?3AvQ6Fd!?Q;!F^sEa) z+Qkc`$?JNK1dnsicvl4(u$2Tw{A<@N7RK0aA2_op^-U%QfbXFrkt%w&bH^w0**G}Z z>U5K?DS;7ze-7(DBFEZsR{!1HoGzz>{Q<>cM3yC2W%>{))(-Cs^ah-S;EyzIQ}So9 zN#IHm_o533K<5iESn?!G7dWjxY3;ZrzeK}Kf81aFFwB}Z+KbCb)^0T2n_-|t>8!a% z#o~T8nPDNiLsczkt`)5ooI&NXx}FssY;W%4F{nR?1KR9E{sj%du;$i}7iA7lws-Sz zs!2fuHd3RrlL{6X7#R5}or@(oR7WfI5d`WFucOo@!@a82sKTgUF!RH#c*3eDOL5J$&i;D|3buM@Mv{$tVqn0u6N_ zL()Z+6pTwR{=2;o|_B{6n&F zDT`n<_(*(nNcG%Se@3&gIDvje5ubk2V;!SB z@XHlCnjz|SJZJ`dRsyN6J2ALla6Bf3x3uBcDz~YT0=|AN2gk$8>dQx(2`qimnLpF! zT4^TS(FPyNnJvm?s0evb&IyAfWVWLu{X?5^1ZH7HGNMO5JFsn3B%;g2#}2Eb}*RmaZ^ zGQ)q9(82WW5WPUoQjAju4b>=lox%8ndAyAWRsuPeG$xAneqLgI(lJC6Ep2zJ*(MIg zKdWQ5pJEA&PLYP_vhfQixm;{w8vf%bNrv|%94WApGmr&eTc&e&2!oz)XgmBD{Ll2>>1 zC@WL>s#$Y!ZBC`Jj7=LpD}a)G`r1Z8dHMbRDclg)Jdl=lF2H^N2K!3J(=JF)k^&J3Ez|a*C3*6Pqj)x!DNI z+^oYv$dS6--|U!&inW!BZAQ6?r7=2l+l=9anz?CaZo>81oX7P~T-Scv_Wgbz-|zc# z_kMprujk7W>7{8?0G|4(@VK^W-?Qx-{Smeu2^Oc~U<(cGW|(i2P_Z10JUGMHi?ans zKNTAMpomU7Nb4T7sm0*>n8S(u007$ndJiO=P`p)s5+*GnV=@1-N*0aCS=#(Is)FjtHuSSZ#)WGAc@B=bS{r+Ys$gTYL|$29v$ zwhONhAa20e#Z_HZUHvid^iRm|G8Iz;6c55sOxD-kisH)-s{m6M5(&`Kmy0i4e%8kU zC>^S#q~tG4(_EeXdL*3e#`hZ?3_7&3gRjOY;<&I^BpqXCev-UQBhQByJC#+{EwjSL z9?i{dr@v1mZA6)W3zs<4D8`c8QT08hPVxPnG8FRz$A$M>e z=HG=_NNy|PLt#W=!%sX*^qcSs4;!4%Rf_7(?@Z;rY=o@5AIENW5_l@3$3Lq~&(C-N z{r3pM>S1JuWycofg=~(0CQTh!mVk!Lbsm8rl5W-^vj0AypJ%+h|#v1WiWpvW$| zNpK>bMyQXU6>hQ!)syd)?S#R&Pri^h;qzIEnxj>Wbfhf+=l9u0b*CR80Uf%Y&HO$8 zD{F&l_KKVf26MeQNrqzv7?ea!%Z}Ca;zxnH_Jp^hYW~Zfo7HV?r-!7cHnxkJT3W0b z=%TOB+XZpgy^9LXt)riKKV!vTxl!&ONFcnQC?o?W5~L?BEu|h69+fPs+I={*;JA?i zKwT2Q+$}D!@!#X5Pj_0`2n;CBSYtw6cxV_S-Ok z6k|TSj(0tEyZ6j0Ec?HwD%cSIi(3PSitkMcs%;(HDTRle+K4k*zz z9S5Fcd@0Z)X`_y+8**G+SlGiW2D{RP$+?4@25QTuxeE#LXc0s z0LvAK-OiM|d({Nn%jOO^8&Y~IB{5LwcE-p*^9`y|9F>;9peIm_X{%eJQ~W`;UlTN+&HZnJ|+|jL5m~p`U5P%iTLhFVN0|IPRS^L z6J)1NEe%0|JykPg?FTuXsF%r06pf}{tr1*q0O}4P)DQ^AgIQ;ah-=)A zLVD3^tC){3Vto$5VB$fyPA6<+U!`Nc!hY{@;dP6>NVR}7>a|)p->M#VhN^nz)7yTr zvDYTJXpWbx_t`Ko2CN%Z>bV&i{uKw6ny>?YS19hg!h&eMKUc0;GXS-e!F+zVPzTM- zr38t$zJC<51&DX21yHINJ-#_P<4DPikeKkV7w{e1whneF!fg{;Z9JJejl{K-!;(<2 zz7@xu>Z6+EUrj9S%TDrMW$0KHuPn}uv2${2_dTPC>{lgCwu%IRP2D}{pN|Oj-NAR_QK?YlblKEw0rrcqa8S5bWYr0(H`d>4hAk!AW`Z15CP>9E@=Au`T3u| z=}cpeSLz0VQU~cRCF`1nJK5NGbMJfdEM+spG0XG!B%B|!-&grMvtX?)TTFyDYt6tB z&W2gEBaV`LHEclW<949dTUsgdUTlI!tVSU+)@AVc%ydo>@HmOCb}9?|wpi8H2GD-1 zxW;f%2;~Mp%T+yc)%&($KVY(M>SGY0uBqYYFEZjfoltj|Y^=8^BzY5?DIfJxvM=a!|4<0D!So3?vZ*QMZ)nLaQ zK9F?=6+3sR9<`}N(U?xjnAimXrhM#MYqTVW-=6yUl%C6>l!vJiKUx>Ls28 z`SIW1EF^1J<-NTIaB(G~6iNKc@zBz@sbc)lN>?JhAV0r2-&#=l3!K9 z{xAf=RhuG?YmgSZS7q?-);%<}2>hx3%9`MT-asRoR|J}rhr`&J149BZ)c2^{znWMj zens+llzTza0?Z!0wJ!E+NilygqoxFq1R5G50=8V-1TvVtM4fo~IN3bgfsL0X>McHT z-ccmBjUy~%u1&_d!K=;sEoz@*np*)@cB`yuS*o*#$cHZO+6p8z0su!6m!NuTb^d|!+48>xX#jfQzh1SYb1g&Jv$OA#RdwQ6`w1t(jWr-;Ln{qz_(I#Cl>464g{wm= zaJS+DmD>B$pKIfXqHO^arY>`N%mWDN?1&RizD)^P*G!kJug1+_fB#b;<&oB!4rBsh z&F*^e7_2C9uVn|11q9jqZMia|v&{-^!#4@DGp@ozCHQsEUeFkQub0(NB1irJ&S5U) zcD3;^tJ+%=O6^J_Gx>AJKIrTmTF$^Kqm%Bwn}Ex9H$T~iP73-V^(_s}a&~49qO~(y zuJLO^hHFAtWMi1O)=xtAE^yx0#F^ocE0ld%!Y%xoH|wjmiL@sFx!bX#-Vi2gdU4J- z+lB3Wr}G$?3?oIy;Z{|R3eibyc%MMF6`gPwzesvi8w53q z>0l2|ieF8Run~ub%qp}jB)hrW<|$a}Z2y?sHgrM?(0jqG3OE@s7_6Pk1G52|0`zo3pa_=)q%X$ii%6rlJkbu$koBl#Y<<) zF5;iO4U!+|awP{ry)2RzC`gq|J+Rw!Q4;|^J(lP`>8B7siKJy|iR>($tS#YW5F)~g z&I7`UzZvOZrWOBMn8?pa($CI=VX0PWdGK E2ZGz85dZ)H literal 0 HcmV?d00001 diff --git a/orion-ops-vue/src/components/app/AddAppEnvModal.vue b/orion-ops-vue/src/components/app/AddAppEnvModal.vue new file mode 100644 index 0000000..19400cc --- /dev/null +++ b/orion-ops-vue/src/components/app/AddAppEnvModal.vue @@ -0,0 +1,154 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AddAppForm.vue b/orion-ops-vue/src/components/app/AddAppForm.vue new file mode 100644 index 0000000..fb2c8f1 --- /dev/null +++ b/orion-ops-vue/src/components/app/AddAppForm.vue @@ -0,0 +1,176 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AddAppModal.vue b/orion-ops-vue/src/components/app/AddAppModal.vue new file mode 100644 index 0000000..de32a1b --- /dev/null +++ b/orion-ops-vue/src/components/app/AddAppModal.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AddAppProfileModal.vue b/orion-ops-vue/src/components/app/AddAppProfileModal.vue new file mode 100644 index 0000000..1a6f50b --- /dev/null +++ b/orion-ops-vue/src/components/app/AddAppProfileModal.vue @@ -0,0 +1,162 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AddPipelineModal.vue b/orion-ops-vue/src/components/app/AddPipelineModal.vue new file mode 100644 index 0000000..11c47ee --- /dev/null +++ b/orion-ops-vue/src/components/app/AddPipelineModal.vue @@ -0,0 +1,269 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AddRepositoryModal.vue b/orion-ops-vue/src/components/app/AddRepositoryModal.vue new file mode 100644 index 0000000..1453a67 --- /dev/null +++ b/orion-ops-vue/src/components/app/AddRepositoryModal.vue @@ -0,0 +1,261 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppAutoComplete.vue b/orion-ops-vue/src/components/app/AppAutoComplete.vue new file mode 100644 index 0000000..c1841d0 --- /dev/null +++ b/orion-ops-vue/src/components/app/AppAutoComplete.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppBuildConfigForm.vue b/orion-ops-vue/src/components/app/AppBuildConfigForm.vue new file mode 100644 index 0000000..2cf1e10 --- /dev/null +++ b/orion-ops-vue/src/components/app/AppBuildConfigForm.vue @@ -0,0 +1,328 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppBuildDetailDrawer.vue b/orion-ops-vue/src/components/app/AppBuildDetailDrawer.vue new file mode 100644 index 0000000..7ac112b --- /dev/null +++ b/orion-ops-vue/src/components/app/AppBuildDetailDrawer.vue @@ -0,0 +1,256 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppBuildModal.vue b/orion-ops-vue/src/components/app/AppBuildModal.vue new file mode 100644 index 0000000..e69ea31 --- /dev/null +++ b/orion-ops-vue/src/components/app/AppBuildModal.vue @@ -0,0 +1,393 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppBuildStatisticsCharts.vue b/orion-ops-vue/src/components/app/AppBuildStatisticsCharts.vue new file mode 100644 index 0000000..1e5b0ba --- /dev/null +++ b/orion-ops-vue/src/components/app/AppBuildStatisticsCharts.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppBuildStatisticsMetrics.vue b/orion-ops-vue/src/components/app/AppBuildStatisticsMetrics.vue new file mode 100644 index 0000000..3e53fec --- /dev/null +++ b/orion-ops-vue/src/components/app/AppBuildStatisticsMetrics.vue @@ -0,0 +1,124 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppBuildStatisticsViews.vue b/orion-ops-vue/src/components/app/AppBuildStatisticsViews.vue new file mode 100644 index 0000000..ada7feb --- /dev/null +++ b/orion-ops-vue/src/components/app/AppBuildStatisticsViews.vue @@ -0,0 +1,338 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppPipelineDetailViewDrawer.vue b/orion-ops-vue/src/components/app/AppPipelineDetailViewDrawer.vue new file mode 100644 index 0000000..7ec2c41 --- /dev/null +++ b/orion-ops-vue/src/components/app/AppPipelineDetailViewDrawer.vue @@ -0,0 +1,83 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppPipelineExecAuditModal.vue b/orion-ops-vue/src/components/app/AppPipelineExecAuditModal.vue new file mode 100644 index 0000000..168b20f --- /dev/null +++ b/orion-ops-vue/src/components/app/AppPipelineExecAuditModal.vue @@ -0,0 +1,83 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppPipelineExecBuildModal.vue b/orion-ops-vue/src/components/app/AppPipelineExecBuildModal.vue new file mode 100644 index 0000000..3045bbb --- /dev/null +++ b/orion-ops-vue/src/components/app/AppPipelineExecBuildModal.vue @@ -0,0 +1,239 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppPipelineExecModal.vue b/orion-ops-vue/src/components/app/AppPipelineExecModal.vue new file mode 100644 index 0000000..4b75c1d --- /dev/null +++ b/orion-ops-vue/src/components/app/AppPipelineExecModal.vue @@ -0,0 +1,415 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppPipelineExecReleaseModal.vue b/orion-ops-vue/src/components/app/AppPipelineExecReleaseModal.vue new file mode 100644 index 0000000..1690d05 --- /dev/null +++ b/orion-ops-vue/src/components/app/AppPipelineExecReleaseModal.vue @@ -0,0 +1,226 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppPipelineExecTimedModal.vue b/orion-ops-vue/src/components/app/AppPipelineExecTimedModal.vue new file mode 100644 index 0000000..fa471e0 --- /dev/null +++ b/orion-ops-vue/src/components/app/AppPipelineExecTimedModal.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppPipelineSelector.vue b/orion-ops-vue/src/components/app/AppPipelineSelector.vue new file mode 100644 index 0000000..50d4345 --- /dev/null +++ b/orion-ops-vue/src/components/app/AppPipelineSelector.vue @@ -0,0 +1,73 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppPipelineStatisticsCharts.vue b/orion-ops-vue/src/components/app/AppPipelineStatisticsCharts.vue new file mode 100644 index 0000000..518d0ac --- /dev/null +++ b/orion-ops-vue/src/components/app/AppPipelineStatisticsCharts.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppPipelineStatisticsMetrics.vue b/orion-ops-vue/src/components/app/AppPipelineStatisticsMetrics.vue new file mode 100644 index 0000000..7e57bca --- /dev/null +++ b/orion-ops-vue/src/components/app/AppPipelineStatisticsMetrics.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppPipelineStatisticsViews.vue b/orion-ops-vue/src/components/app/AppPipelineStatisticsViews.vue new file mode 100644 index 0000000..b817edd --- /dev/null +++ b/orion-ops-vue/src/components/app/AppPipelineStatisticsViews.vue @@ -0,0 +1,347 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppPipelineTaskDetailDrawer.vue b/orion-ops-vue/src/components/app/AppPipelineTaskDetailDrawer.vue new file mode 100644 index 0000000..066267f --- /dev/null +++ b/orion-ops-vue/src/components/app/AppPipelineTaskDetailDrawer.vue @@ -0,0 +1,226 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppProfileChecker.vue b/orion-ops-vue/src/components/app/AppProfileChecker.vue new file mode 100644 index 0000000..aa58fe5 --- /dev/null +++ b/orion-ops-vue/src/components/app/AppProfileChecker.vue @@ -0,0 +1,162 @@ + + + + diff --git a/orion-ops-vue/src/components/app/AppReleaseAuditModal.vue b/orion-ops-vue/src/components/app/AppReleaseAuditModal.vue new file mode 100644 index 0000000..9c6a4c4 --- /dev/null +++ b/orion-ops-vue/src/components/app/AppReleaseAuditModal.vue @@ -0,0 +1,83 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppReleaseConfigForm.vue b/orion-ops-vue/src/components/app/AppReleaseConfigForm.vue new file mode 100644 index 0000000..9b108ae --- /dev/null +++ b/orion-ops-vue/src/components/app/AppReleaseConfigForm.vue @@ -0,0 +1,457 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppReleaseDetailDrawer.vue b/orion-ops-vue/src/components/app/AppReleaseDetailDrawer.vue new file mode 100644 index 0000000..d6c863c --- /dev/null +++ b/orion-ops-vue/src/components/app/AppReleaseDetailDrawer.vue @@ -0,0 +1,253 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppReleaseMachineDetailDrawer.vue b/orion-ops-vue/src/components/app/AppReleaseMachineDetailDrawer.vue new file mode 100644 index 0000000..65a3c77 --- /dev/null +++ b/orion-ops-vue/src/components/app/AppReleaseMachineDetailDrawer.vue @@ -0,0 +1,226 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppReleaseModal.vue b/orion-ops-vue/src/components/app/AppReleaseModal.vue new file mode 100644 index 0000000..5c4db0f --- /dev/null +++ b/orion-ops-vue/src/components/app/AppReleaseModal.vue @@ -0,0 +1,414 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppReleaseStatisticsCharts.vue b/orion-ops-vue/src/components/app/AppReleaseStatisticsCharts.vue new file mode 100644 index 0000000..7b4abfc --- /dev/null +++ b/orion-ops-vue/src/components/app/AppReleaseStatisticsCharts.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppReleaseStatisticsMetrics.vue b/orion-ops-vue/src/components/app/AppReleaseStatisticsMetrics.vue new file mode 100644 index 0000000..16c571a --- /dev/null +++ b/orion-ops-vue/src/components/app/AppReleaseStatisticsMetrics.vue @@ -0,0 +1,124 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppReleaseStatisticsViews.vue b/orion-ops-vue/src/components/app/AppReleaseStatisticsViews.vue new file mode 100644 index 0000000..bb48edd --- /dev/null +++ b/orion-ops-vue/src/components/app/AppReleaseStatisticsViews.vue @@ -0,0 +1,466 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppReleaseTimedModal.vue b/orion-ops-vue/src/components/app/AppReleaseTimedModal.vue new file mode 100644 index 0000000..aa69d40 --- /dev/null +++ b/orion-ops-vue/src/components/app/AppReleaseTimedModal.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/AppSelector.vue b/orion-ops-vue/src/components/app/AppSelector.vue new file mode 100644 index 0000000..2510b3f --- /dev/null +++ b/orion-ops-vue/src/components/app/AppSelector.vue @@ -0,0 +1,64 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/PipelineAutoComplete.vue b/orion-ops-vue/src/components/app/PipelineAutoComplete.vue new file mode 100644 index 0000000..c6cdbaf --- /dev/null +++ b/orion-ops-vue/src/components/app/PipelineAutoComplete.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/orion-ops-vue/src/components/app/ProfileAutoComplete.vue b/orion-ops-vue/src/components/app/ProfileAutoComplete.vue new file mode 100644 index 0000000..10c00c7 --- /dev/null +++ b/orion-ops-vue/src/components/app/ProfileAutoComplete.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/orion-ops-vue/src/components/clear/AppBuildClearModal.vue b/orion-ops-vue/src/components/clear/AppBuildClearModal.vue new file mode 100644 index 0000000..8321adf --- /dev/null +++ b/orion-ops-vue/src/components/clear/AppBuildClearModal.vue @@ -0,0 +1,181 @@ + + + + + diff --git a/orion-ops-vue/src/components/clear/AppPipelineClearModal.vue b/orion-ops-vue/src/components/clear/AppPipelineClearModal.vue new file mode 100644 index 0000000..8318504 --- /dev/null +++ b/orion-ops-vue/src/components/clear/AppPipelineClearModal.vue @@ -0,0 +1,197 @@ + + + + + diff --git a/orion-ops-vue/src/components/clear/AppReleaseClearModal.vue b/orion-ops-vue/src/components/clear/AppReleaseClearModal.vue new file mode 100644 index 0000000..a7d5716 --- /dev/null +++ b/orion-ops-vue/src/components/clear/AppReleaseClearModal.vue @@ -0,0 +1,197 @@ + + + + + diff --git a/orion-ops-vue/src/components/clear/BatchExecClearModal.vue b/orion-ops-vue/src/components/clear/BatchExecClearModal.vue new file mode 100644 index 0000000..82f35f7 --- /dev/null +++ b/orion-ops-vue/src/components/clear/BatchExecClearModal.vue @@ -0,0 +1,173 @@ + + + + + diff --git a/orion-ops-vue/src/components/clear/EventLogClearModal.vue b/orion-ops-vue/src/components/clear/EventLogClearModal.vue new file mode 100644 index 0000000..3c96468 --- /dev/null +++ b/orion-ops-vue/src/components/clear/EventLogClearModal.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/orion-ops-vue/src/components/clear/MachineAlarmHistoryClearModal.vue b/orion-ops-vue/src/components/clear/MachineAlarmHistoryClearModal.vue new file mode 100644 index 0000000..1da1be8 --- /dev/null +++ b/orion-ops-vue/src/components/clear/MachineAlarmHistoryClearModal.vue @@ -0,0 +1,151 @@ + + + + + diff --git a/orion-ops-vue/src/components/clear/SchedulerRecordClearModal.vue b/orion-ops-vue/src/components/clear/SchedulerRecordClearModal.vue new file mode 100644 index 0000000..9a1f121 --- /dev/null +++ b/orion-ops-vue/src/components/clear/SchedulerRecordClearModal.vue @@ -0,0 +1,151 @@ + + + + + diff --git a/orion-ops-vue/src/components/clear/TerminalLogClearModal.vue b/orion-ops-vue/src/components/clear/TerminalLogClearModal.vue new file mode 100644 index 0000000..fea8a59 --- /dev/null +++ b/orion-ops-vue/src/components/clear/TerminalLogClearModal.vue @@ -0,0 +1,161 @@ + + + + + diff --git a/orion-ops-vue/src/components/common/RightClickMenu.vue b/orion-ops-vue/src/components/common/RightClickMenu.vue new file mode 100644 index 0000000..e358568 --- /dev/null +++ b/orion-ops-vue/src/components/common/RightClickMenu.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/orion-ops-vue/src/components/content/AddTemplateModal.vue b/orion-ops-vue/src/components/content/AddTemplateModal.vue new file mode 100644 index 0000000..26469d1 --- /dev/null +++ b/orion-ops-vue/src/components/content/AddTemplateModal.vue @@ -0,0 +1,153 @@ + + + + + diff --git a/orion-ops-vue/src/components/content/AddWebhookModal.vue b/orion-ops-vue/src/components/content/AddWebhookModal.vue new file mode 100644 index 0000000..46fd2ea --- /dev/null +++ b/orion-ops-vue/src/components/content/AddWebhookModal.vue @@ -0,0 +1,159 @@ + + + + + diff --git a/orion-ops-vue/src/components/content/EnvHistoryModal.vue b/orion-ops-vue/src/components/content/EnvHistoryModal.vue new file mode 100644 index 0000000..50d0d14 --- /dev/null +++ b/orion-ops-vue/src/components/content/EnvHistoryModal.vue @@ -0,0 +1,233 @@ + + + + + diff --git a/orion-ops-vue/src/components/content/TemplateSelector.vue b/orion-ops-vue/src/components/content/TemplateSelector.vue new file mode 100644 index 0000000..cebc0bd --- /dev/null +++ b/orion-ops-vue/src/components/content/TemplateSelector.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/orion-ops-vue/src/components/editor/Editor.vue b/orion-ops-vue/src/components/editor/Editor.vue new file mode 100644 index 0000000..d56f9c3 --- /dev/null +++ b/orion-ops-vue/src/components/editor/Editor.vue @@ -0,0 +1,108 @@ + + + + + diff --git a/orion-ops-vue/src/components/exec/ExecTaskDetailModal.vue b/orion-ops-vue/src/components/exec/ExecTaskDetailModal.vue new file mode 100644 index 0000000..d4ddd5a --- /dev/null +++ b/orion-ops-vue/src/components/exec/ExecTaskDetailModal.vue @@ -0,0 +1,110 @@ + + + + + diff --git a/orion-ops-vue/src/components/export/AppProfileExportModal.vue b/orion-ops-vue/src/components/export/AppProfileExportModal.vue new file mode 100644 index 0000000..c2edd68 --- /dev/null +++ b/orion-ops-vue/src/components/export/AppProfileExportModal.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/orion-ops-vue/src/components/export/AppRepositoryExportModal.vue b/orion-ops-vue/src/components/export/AppRepositoryExportModal.vue new file mode 100644 index 0000000..e8bfaa0 --- /dev/null +++ b/orion-ops-vue/src/components/export/AppRepositoryExportModal.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/orion-ops-vue/src/components/export/ApplicationExportModal.vue b/orion-ops-vue/src/components/export/ApplicationExportModal.vue new file mode 100644 index 0000000..4400461 --- /dev/null +++ b/orion-ops-vue/src/components/export/ApplicationExportModal.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/orion-ops-vue/src/components/export/CommandTemplateExportModal.vue b/orion-ops-vue/src/components/export/CommandTemplateExportModal.vue new file mode 100644 index 0000000..16353b5 --- /dev/null +++ b/orion-ops-vue/src/components/export/CommandTemplateExportModal.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/orion-ops-vue/src/components/export/EventLogExportExportModal.vue b/orion-ops-vue/src/components/export/EventLogExportExportModal.vue new file mode 100644 index 0000000..ebb8b48 --- /dev/null +++ b/orion-ops-vue/src/components/export/EventLogExportExportModal.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/orion-ops-vue/src/components/export/MachineAlarmHistoryExportModal.vue b/orion-ops-vue/src/components/export/MachineAlarmHistoryExportModal.vue new file mode 100644 index 0000000..d5b2837 --- /dev/null +++ b/orion-ops-vue/src/components/export/MachineAlarmHistoryExportModal.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/orion-ops-vue/src/components/export/MachineExportModal.vue b/orion-ops-vue/src/components/export/MachineExportModal.vue new file mode 100644 index 0000000..da0b5b4 --- /dev/null +++ b/orion-ops-vue/src/components/export/MachineExportModal.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/orion-ops-vue/src/components/export/MachineProxyExportModal.vue b/orion-ops-vue/src/components/export/MachineProxyExportModal.vue new file mode 100644 index 0000000..c3012eb --- /dev/null +++ b/orion-ops-vue/src/components/export/MachineProxyExportModal.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/orion-ops-vue/src/components/export/TailFileExportModal.vue b/orion-ops-vue/src/components/export/TailFileExportModal.vue new file mode 100644 index 0000000..4fbd85c --- /dev/null +++ b/orion-ops-vue/src/components/export/TailFileExportModal.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/orion-ops-vue/src/components/export/TerminalLogExportModal.vue b/orion-ops-vue/src/components/export/TerminalLogExportModal.vue new file mode 100644 index 0000000..80c5a7c --- /dev/null +++ b/orion-ops-vue/src/components/export/TerminalLogExportModal.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/orion-ops-vue/src/components/export/WebhookExportModal.vue b/orion-ops-vue/src/components/export/WebhookExportModal.vue new file mode 100644 index 0000000..8a244df --- /dev/null +++ b/orion-ops-vue/src/components/export/WebhookExportModal.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/orion-ops-vue/src/components/import/DataImportModal.vue b/orion-ops-vue/src/components/import/DataImportModal.vue new file mode 100644 index 0000000..025f40b --- /dev/null +++ b/orion-ops-vue/src/components/import/DataImportModal.vue @@ -0,0 +1,241 @@ + + + + + diff --git a/orion-ops-vue/src/components/layout/Header.vue b/orion-ops-vue/src/components/layout/Header.vue new file mode 100644 index 0000000..87ef679 --- /dev/null +++ b/orion-ops-vue/src/components/layout/Header.vue @@ -0,0 +1,158 @@ + + + + + diff --git a/orion-ops-vue/src/components/layout/HeaderProfileSelect.vue b/orion-ops-vue/src/components/layout/HeaderProfileSelect.vue new file mode 100644 index 0000000..25443c7 --- /dev/null +++ b/orion-ops-vue/src/components/layout/HeaderProfileSelect.vue @@ -0,0 +1,76 @@ + + + + + diff --git a/orion-ops-vue/src/components/layout/HeaderUser.vue b/orion-ops-vue/src/components/layout/HeaderUser.vue new file mode 100644 index 0000000..d2c9f67 --- /dev/null +++ b/orion-ops-vue/src/components/layout/HeaderUser.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/orion-ops-vue/src/components/layout/Layout.vue b/orion-ops-vue/src/components/layout/Layout.vue new file mode 100644 index 0000000..cd8214a --- /dev/null +++ b/orion-ops-vue/src/components/layout/Layout.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/orion-ops-vue/src/components/layout/Menu.vue b/orion-ops-vue/src/components/layout/Menu.vue new file mode 100644 index 0000000..abd61d5 --- /dev/null +++ b/orion-ops-vue/src/components/layout/Menu.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/orion-ops-vue/src/components/layout/WebSideMessageDrawer.vue b/orion-ops-vue/src/components/layout/WebSideMessageDrawer.vue new file mode 100644 index 0000000..5f59125 --- /dev/null +++ b/orion-ops-vue/src/components/layout/WebSideMessageDrawer.vue @@ -0,0 +1,530 @@ + + + + + diff --git a/orion-ops-vue/src/components/log/AddLogFileModal.vue b/orion-ops-vue/src/components/log/AddLogFileModal.vue new file mode 100644 index 0000000..c9781c4 --- /dev/null +++ b/orion-ops-vue/src/components/log/AddLogFileModal.vue @@ -0,0 +1,263 @@ + + + + + diff --git a/orion-ops-vue/src/components/log/AppActionLogAppender.vue b/orion-ops-vue/src/components/log/AppActionLogAppender.vue new file mode 100644 index 0000000..0028257 --- /dev/null +++ b/orion-ops-vue/src/components/log/AppActionLogAppender.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/orion-ops-vue/src/components/log/AppActionLogAppenderModal.vue b/orion-ops-vue/src/components/log/AppActionLogAppenderModal.vue new file mode 100644 index 0000000..f810e8f --- /dev/null +++ b/orion-ops-vue/src/components/log/AppActionLogAppenderModal.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/orion-ops-vue/src/components/log/AppBuildLogAppender.vue b/orion-ops-vue/src/components/log/AppBuildLogAppender.vue new file mode 100644 index 0000000..f5968f4 --- /dev/null +++ b/orion-ops-vue/src/components/log/AppBuildLogAppender.vue @@ -0,0 +1,238 @@ + + + + + diff --git a/orion-ops-vue/src/components/log/AppBuildLogAppenderModal.vue b/orion-ops-vue/src/components/log/AppBuildLogAppenderModal.vue new file mode 100644 index 0000000..b612a12 --- /dev/null +++ b/orion-ops-vue/src/components/log/AppBuildLogAppenderModal.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/orion-ops-vue/src/components/log/AppReleaseLogAppender.vue b/orion-ops-vue/src/components/log/AppReleaseLogAppender.vue new file mode 100644 index 0000000..9a5c3ec --- /dev/null +++ b/orion-ops-vue/src/components/log/AppReleaseLogAppender.vue @@ -0,0 +1,335 @@ + + + + + diff --git a/orion-ops-vue/src/components/log/AppReleaseLogAppenderModal.vue b/orion-ops-vue/src/components/log/AppReleaseLogAppenderModal.vue new file mode 100644 index 0000000..9a6ad51 --- /dev/null +++ b/orion-ops-vue/src/components/log/AppReleaseLogAppenderModal.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/orion-ops-vue/src/components/log/AppReleaseMachineLogAppender.vue b/orion-ops-vue/src/components/log/AppReleaseMachineLogAppender.vue new file mode 100644 index 0000000..409e3c0 --- /dev/null +++ b/orion-ops-vue/src/components/log/AppReleaseMachineLogAppender.vue @@ -0,0 +1,205 @@ + + + + + diff --git a/orion-ops-vue/src/components/log/AppReleaseMachineLogAppenderModal.vue b/orion-ops-vue/src/components/log/AppReleaseMachineLogAppenderModal.vue new file mode 100644 index 0000000..87519c9 --- /dev/null +++ b/orion-ops-vue/src/components/log/AppReleaseMachineLogAppenderModal.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/orion-ops-vue/src/components/log/ExecLoggerAppender.vue b/orion-ops-vue/src/components/log/ExecLoggerAppender.vue new file mode 100644 index 0000000..b7a6b04 --- /dev/null +++ b/orion-ops-vue/src/components/log/ExecLoggerAppender.vue @@ -0,0 +1,181 @@ + + + + + diff --git a/orion-ops-vue/src/components/log/ExecLoggerAppenderModal.vue b/orion-ops-vue/src/components/log/ExecLoggerAppenderModal.vue new file mode 100644 index 0000000..855e998 --- /dev/null +++ b/orion-ops-vue/src/components/log/ExecLoggerAppenderModal.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/orion-ops-vue/src/components/log/FileAnsiCleanModal.vue b/orion-ops-vue/src/components/log/FileAnsiCleanModal.vue new file mode 100644 index 0000000..dab6939 --- /dev/null +++ b/orion-ops-vue/src/components/log/FileAnsiCleanModal.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/orion-ops-vue/src/components/log/LogAppender.vue b/orion-ops-vue/src/components/log/LogAppender.vue new file mode 100644 index 0000000..007e507 --- /dev/null +++ b/orion-ops-vue/src/components/log/LogAppender.vue @@ -0,0 +1,389 @@ + + + + + diff --git a/orion-ops-vue/src/components/log/LoggerViewModal.vue b/orion-ops-vue/src/components/log/LoggerViewModal.vue new file mode 100644 index 0000000..6f3372c --- /dev/null +++ b/orion-ops-vue/src/components/log/LoggerViewModal.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/orion-ops-vue/src/components/log/SchedulerTaskLogAppender.vue b/orion-ops-vue/src/components/log/SchedulerTaskLogAppender.vue new file mode 100644 index 0000000..48269fb --- /dev/null +++ b/orion-ops-vue/src/components/log/SchedulerTaskLogAppender.vue @@ -0,0 +1,301 @@ + + + + + diff --git a/orion-ops-vue/src/components/log/SchedulerTaskLogAppenderModal.vue b/orion-ops-vue/src/components/log/SchedulerTaskLogAppenderModal.vue new file mode 100644 index 0000000..b11a7df --- /dev/null +++ b/orion-ops-vue/src/components/log/SchedulerTaskLogAppenderModal.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/orion-ops-vue/src/components/log/SchedulerTaskMachineLogAppender.vue b/orion-ops-vue/src/components/log/SchedulerTaskMachineLogAppender.vue new file mode 100644 index 0000000..26ec748 --- /dev/null +++ b/orion-ops-vue/src/components/log/SchedulerTaskMachineLogAppender.vue @@ -0,0 +1,184 @@ + + + + + diff --git a/orion-ops-vue/src/components/log/SchedulerTaskMachineLogAppenderModal.vue b/orion-ops-vue/src/components/log/SchedulerTaskMachineLogAppenderModal.vue new file mode 100644 index 0000000..1749b3e --- /dev/null +++ b/orion-ops-vue/src/components/log/SchedulerTaskMachineLogAppenderModal.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/orion-ops-vue/src/components/log/UploadLogFileModal.vue b/orion-ops-vue/src/components/log/UploadLogFileModal.vue new file mode 100644 index 0000000..6672a89 --- /dev/null +++ b/orion-ops-vue/src/components/log/UploadLogFileModal.vue @@ -0,0 +1,102 @@ + + + + + diff --git a/orion-ops-vue/src/components/machine/AddMachineEnvModal.vue b/orion-ops-vue/src/components/machine/AddMachineEnvModal.vue new file mode 100644 index 0000000..93e711f --- /dev/null +++ b/orion-ops-vue/src/components/machine/AddMachineEnvModal.vue @@ -0,0 +1,151 @@ + + + + + diff --git a/orion-ops-vue/src/components/machine/AddMachineKeyModal.vue b/orion-ops-vue/src/components/machine/AddMachineKeyModal.vue new file mode 100644 index 0000000..f6e41e0 --- /dev/null +++ b/orion-ops-vue/src/components/machine/AddMachineKeyModal.vue @@ -0,0 +1,184 @@ + + + + + diff --git a/orion-ops-vue/src/components/machine/AddMachineModal.vue b/orion-ops-vue/src/components/machine/AddMachineModal.vue new file mode 100644 index 0000000..e5cd519 --- /dev/null +++ b/orion-ops-vue/src/components/machine/AddMachineModal.vue @@ -0,0 +1,449 @@ + + + + + diff --git a/orion-ops-vue/src/components/machine/AddMachineProxyModal.vue b/orion-ops-vue/src/components/machine/AddMachineProxyModal.vue new file mode 100644 index 0000000..b7f032c --- /dev/null +++ b/orion-ops-vue/src/components/machine/AddMachineProxyModal.vue @@ -0,0 +1,197 @@ + + + + + diff --git a/orion-ops-vue/src/components/machine/MachineAutoComplete.vue b/orion-ops-vue/src/components/machine/MachineAutoComplete.vue new file mode 100644 index 0000000..89f8b5b --- /dev/null +++ b/orion-ops-vue/src/components/machine/MachineAutoComplete.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/orion-ops-vue/src/components/machine/MachineChecker.vue b/orion-ops-vue/src/components/machine/MachineChecker.vue new file mode 100644 index 0000000..a22db35 --- /dev/null +++ b/orion-ops-vue/src/components/machine/MachineChecker.vue @@ -0,0 +1,295 @@ + + + + diff --git a/orion-ops-vue/src/components/machine/MachineDetailModal.vue b/orion-ops-vue/src/components/machine/MachineDetailModal.vue new file mode 100644 index 0000000..2d90175 --- /dev/null +++ b/orion-ops-vue/src/components/machine/MachineDetailModal.vue @@ -0,0 +1,125 @@ + + + + + diff --git a/orion-ops-vue/src/components/machine/MachineEditableTree.vue b/orion-ops-vue/src/components/machine/MachineEditableTree.vue new file mode 100644 index 0000000..d9e6cd0 --- /dev/null +++ b/orion-ops-vue/src/components/machine/MachineEditableTree.vue @@ -0,0 +1,546 @@ + + + + + diff --git a/orion-ops-vue/src/components/machine/MachineKeyBindModal.vue b/orion-ops-vue/src/components/machine/MachineKeyBindModal.vue new file mode 100644 index 0000000..03e1b6b --- /dev/null +++ b/orion-ops-vue/src/components/machine/MachineKeyBindModal.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/orion-ops-vue/src/components/machine/MachineListMenu.vue b/orion-ops-vue/src/components/machine/MachineListMenu.vue new file mode 100644 index 0000000..302e0f8 --- /dev/null +++ b/orion-ops-vue/src/components/machine/MachineListMenu.vue @@ -0,0 +1,140 @@ + + + + + diff --git a/orion-ops-vue/src/components/machine/MachineMonitorAlarmConfig.vue b/orion-ops-vue/src/components/machine/MachineMonitorAlarmConfig.vue new file mode 100644 index 0000000..2df33f1 --- /dev/null +++ b/orion-ops-vue/src/components/machine/MachineMonitorAlarmConfig.vue @@ -0,0 +1,224 @@ + + + + + diff --git a/orion-ops-vue/src/components/machine/MachineMonitorAlarmHistory.vue b/orion-ops-vue/src/components/machine/MachineMonitorAlarmHistory.vue new file mode 100644 index 0000000..1c8ca61 --- /dev/null +++ b/orion-ops-vue/src/components/machine/MachineMonitorAlarmHistory.vue @@ -0,0 +1,252 @@ + + + + + diff --git a/orion-ops-vue/src/components/machine/MachineMonitorChart.vue b/orion-ops-vue/src/components/machine/MachineMonitorChart.vue new file mode 100644 index 0000000..64f000a --- /dev/null +++ b/orion-ops-vue/src/components/machine/MachineMonitorChart.vue @@ -0,0 +1,1050 @@ + + + + + diff --git a/orion-ops-vue/src/components/machine/MachineMonitorConfigModal.vue b/orion-ops-vue/src/components/machine/MachineMonitorConfigModal.vue new file mode 100644 index 0000000..7854bcd --- /dev/null +++ b/orion-ops-vue/src/components/machine/MachineMonitorConfigModal.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/orion-ops-vue/src/components/machine/MachineMonitorSummary.vue b/orion-ops-vue/src/components/machine/MachineMonitorSummary.vue new file mode 100644 index 0000000..952c450 --- /dev/null +++ b/orion-ops-vue/src/components/machine/MachineMonitorSummary.vue @@ -0,0 +1,697 @@ + + + + + diff --git a/orion-ops-vue/src/components/machine/MachineMultiSelector.vue b/orion-ops-vue/src/components/machine/MachineMultiSelector.vue new file mode 100644 index 0000000..91229be --- /dev/null +++ b/orion-ops-vue/src/components/machine/MachineMultiSelector.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/orion-ops-vue/src/components/machine/MachineMultiTableSelectorModal.vue b/orion-ops-vue/src/components/machine/MachineMultiTableSelectorModal.vue new file mode 100644 index 0000000..9b673c4 --- /dev/null +++ b/orion-ops-vue/src/components/machine/MachineMultiTableSelectorModal.vue @@ -0,0 +1,184 @@ + + + + + diff --git a/orion-ops-vue/src/components/machine/MachineMultiTreeTableSelectorModal.vue b/orion-ops-vue/src/components/machine/MachineMultiTreeTableSelectorModal.vue new file mode 100644 index 0000000..4e7545d --- /dev/null +++ b/orion-ops-vue/src/components/machine/MachineMultiTreeTableSelectorModal.vue @@ -0,0 +1,243 @@ + + + + + diff --git a/orion-ops-vue/src/components/machine/MachineSelector.vue b/orion-ops-vue/src/components/machine/MachineSelector.vue new file mode 100644 index 0000000..61997b2 --- /dev/null +++ b/orion-ops-vue/src/components/machine/MachineSelector.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/orion-ops-vue/src/components/preview/EditorPreview.vue b/orion-ops-vue/src/components/preview/EditorPreview.vue new file mode 100644 index 0000000..ca2313c --- /dev/null +++ b/orion-ops-vue/src/components/preview/EditorPreview.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/orion-ops-vue/src/components/preview/TextPreview.vue b/orion-ops-vue/src/components/preview/TextPreview.vue new file mode 100644 index 0000000..83a3e63 --- /dev/null +++ b/orion-ops-vue/src/components/preview/TextPreview.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/orion-ops-vue/src/components/scheduler/AddSchedulerTask.vue b/orion-ops-vue/src/components/scheduler/AddSchedulerTask.vue new file mode 100644 index 0000000..3fc295c --- /dev/null +++ b/orion-ops-vue/src/components/scheduler/AddSchedulerTask.vue @@ -0,0 +1,330 @@ + + + + + diff --git a/orion-ops-vue/src/components/sftp/FileTransferList.vue b/orion-ops-vue/src/components/sftp/FileTransferList.vue new file mode 100644 index 0000000..a5df8bc --- /dev/null +++ b/orion-ops-vue/src/components/sftp/FileTransferList.vue @@ -0,0 +1,477 @@ + + + + + diff --git a/orion-ops-vue/src/components/sftp/MachineSftpDrawer.vue b/orion-ops-vue/src/components/sftp/MachineSftpDrawer.vue new file mode 100644 index 0000000..8a08f5d --- /dev/null +++ b/orion-ops-vue/src/components/sftp/MachineSftpDrawer.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/orion-ops-vue/src/components/sftp/MachineSftpMain.vue b/orion-ops-vue/src/components/sftp/MachineSftpMain.vue new file mode 100644 index 0000000..8f3baee --- /dev/null +++ b/orion-ops-vue/src/components/sftp/MachineSftpMain.vue @@ -0,0 +1,818 @@ + + + + + diff --git a/orion-ops-vue/src/components/sftp/SftpChmodModal.vue b/orion-ops-vue/src/components/sftp/SftpChmodModal.vue new file mode 100644 index 0000000..62f604a --- /dev/null +++ b/orion-ops-vue/src/components/sftp/SftpChmodModal.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/orion-ops-vue/src/components/sftp/SftpFolderTree.vue b/orion-ops-vue/src/components/sftp/SftpFolderTree.vue new file mode 100644 index 0000000..7ebbeb0 --- /dev/null +++ b/orion-ops-vue/src/components/sftp/SftpFolderTree.vue @@ -0,0 +1,214 @@ + + + + + diff --git a/orion-ops-vue/src/components/sftp/SftpMoveModal.vue b/orion-ops-vue/src/components/sftp/SftpMoveModal.vue new file mode 100644 index 0000000..51f1383 --- /dev/null +++ b/orion-ops-vue/src/components/sftp/SftpMoveModal.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/orion-ops-vue/src/components/sftp/SftpTouchModal.vue b/orion-ops-vue/src/components/sftp/SftpTouchModal.vue new file mode 100644 index 0000000..772c8f5 --- /dev/null +++ b/orion-ops-vue/src/components/sftp/SftpTouchModal.vue @@ -0,0 +1,123 @@ + + + + diff --git a/orion-ops-vue/src/components/sftp/SftpUpload.vue b/orion-ops-vue/src/components/sftp/SftpUpload.vue new file mode 100644 index 0000000..670bf75 --- /dev/null +++ b/orion-ops-vue/src/components/sftp/SftpUpload.vue @@ -0,0 +1,214 @@ + + + + + diff --git a/orion-ops-vue/src/components/system/AddSystemEnvModal.vue b/orion-ops-vue/src/components/system/AddSystemEnvModal.vue new file mode 100644 index 0000000..a05f5a8 --- /dev/null +++ b/orion-ops-vue/src/components/system/AddSystemEnvModal.vue @@ -0,0 +1,147 @@ + + + + + diff --git a/orion-ops-vue/src/components/system/IpConfig.vue b/orion-ops-vue/src/components/system/IpConfig.vue new file mode 100644 index 0000000..47ac052 --- /dev/null +++ b/orion-ops-vue/src/components/system/IpConfig.vue @@ -0,0 +1,174 @@ + + + + + diff --git a/orion-ops-vue/src/components/system/OtherConfig.vue b/orion-ops-vue/src/components/system/OtherConfig.vue new file mode 100644 index 0000000..b118d42 --- /dev/null +++ b/orion-ops-vue/src/components/system/OtherConfig.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/orion-ops-vue/src/components/system/SecurityConfig.vue b/orion-ops-vue/src/components/system/SecurityConfig.vue new file mode 100644 index 0000000..3551a90 --- /dev/null +++ b/orion-ops-vue/src/components/system/SecurityConfig.vue @@ -0,0 +1,197 @@ + + + + + diff --git a/orion-ops-vue/src/components/system/SystemAbout.vue b/orion-ops-vue/src/components/system/SystemAbout.vue new file mode 100644 index 0000000..37bdff4 --- /dev/null +++ b/orion-ops-vue/src/components/system/SystemAbout.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/orion-ops-vue/src/components/system/SystemAnalysis.vue b/orion-ops-vue/src/components/system/SystemAnalysis.vue new file mode 100644 index 0000000..2e7391d --- /dev/null +++ b/orion-ops-vue/src/components/system/SystemAnalysis.vue @@ -0,0 +1,362 @@ + + + + + diff --git a/orion-ops-vue/src/components/system/ThreadMetrics.vue b/orion-ops-vue/src/components/system/ThreadMetrics.vue new file mode 100644 index 0000000..d042368 --- /dev/null +++ b/orion-ops-vue/src/components/system/ThreadMetrics.vue @@ -0,0 +1,112 @@ + + + + + diff --git a/orion-ops-vue/src/components/terminal/TerminalBanner.vue b/orion-ops-vue/src/components/terminal/TerminalBanner.vue new file mode 100644 index 0000000..1161972 --- /dev/null +++ b/orion-ops-vue/src/components/terminal/TerminalBanner.vue @@ -0,0 +1,131 @@ + + + + + diff --git a/orion-ops-vue/src/components/terminal/TerminalBody.vue b/orion-ops-vue/src/components/terminal/TerminalBody.vue new file mode 100644 index 0000000..4497a39 --- /dev/null +++ b/orion-ops-vue/src/components/terminal/TerminalBody.vue @@ -0,0 +1,335 @@ + + + + + diff --git a/orion-ops-vue/src/components/terminal/TerminalHeader.vue b/orion-ops-vue/src/components/terminal/TerminalHeader.vue new file mode 100644 index 0000000..606513c --- /dev/null +++ b/orion-ops-vue/src/components/terminal/TerminalHeader.vue @@ -0,0 +1,188 @@ + + + + + diff --git a/orion-ops-vue/src/components/terminal/TerminalModal.vue b/orion-ops-vue/src/components/terminal/TerminalModal.vue new file mode 100644 index 0000000..f45bd27 --- /dev/null +++ b/orion-ops-vue/src/components/terminal/TerminalModal.vue @@ -0,0 +1,214 @@ + + + + + diff --git a/orion-ops-vue/src/components/terminal/TerminalScreenModal.vue b/orion-ops-vue/src/components/terminal/TerminalScreenModal.vue new file mode 100644 index 0000000..b50a8a6 --- /dev/null +++ b/orion-ops-vue/src/components/terminal/TerminalScreenModal.vue @@ -0,0 +1,185 @@ + + + + + diff --git a/orion-ops-vue/src/components/terminal/TerminalSearch.vue b/orion-ops-vue/src/components/terminal/TerminalSearch.vue new file mode 100644 index 0000000..9dc8d48 --- /dev/null +++ b/orion-ops-vue/src/components/terminal/TerminalSearch.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/orion-ops-vue/src/components/terminal/TerminalSettingModal.vue b/orion-ops-vue/src/components/terminal/TerminalSettingModal.vue new file mode 100644 index 0000000..8cb12db --- /dev/null +++ b/orion-ops-vue/src/components/terminal/TerminalSettingModal.vue @@ -0,0 +1,188 @@ + + + + + diff --git a/orion-ops-vue/src/components/terminal/TerminalView.vue b/orion-ops-vue/src/components/terminal/TerminalView.vue new file mode 100644 index 0000000..db42615 --- /dev/null +++ b/orion-ops-vue/src/components/terminal/TerminalView.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/orion-ops-vue/src/components/terminal/TerminalWatcherModal.vue b/orion-ops-vue/src/components/terminal/TerminalWatcherModal.vue new file mode 100644 index 0000000..65c33b5 --- /dev/null +++ b/orion-ops-vue/src/components/terminal/TerminalWatcherModal.vue @@ -0,0 +1,350 @@ + + + + + diff --git a/orion-ops-vue/src/components/user/AddAlarmGroup.vue b/orion-ops-vue/src/components/user/AddAlarmGroup.vue new file mode 100644 index 0000000..6971fc6 --- /dev/null +++ b/orion-ops-vue/src/components/user/AddAlarmGroup.vue @@ -0,0 +1,192 @@ + + + + + diff --git a/orion-ops-vue/src/components/user/AddUserModal.vue b/orion-ops-vue/src/components/user/AddUserModal.vue new file mode 100644 index 0000000..83d2877 --- /dev/null +++ b/orion-ops-vue/src/components/user/AddUserModal.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/orion-ops-vue/src/components/user/EventLogList.vue b/orion-ops-vue/src/components/user/EventLogList.vue new file mode 100644 index 0000000..c0b383d --- /dev/null +++ b/orion-ops-vue/src/components/user/EventLogList.vue @@ -0,0 +1,287 @@ + + + + + diff --git a/orion-ops-vue/src/components/user/LoginHistory.vue b/orion-ops-vue/src/components/user/LoginHistory.vue new file mode 100644 index 0000000..62a8f7a --- /dev/null +++ b/orion-ops-vue/src/components/user/LoginHistory.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/orion-ops-vue/src/components/user/ResetPassword.vue b/orion-ops-vue/src/components/user/ResetPassword.vue new file mode 100644 index 0000000..65237a0 --- /dev/null +++ b/orion-ops-vue/src/components/user/ResetPassword.vue @@ -0,0 +1,152 @@ + + diff --git a/orion-ops-vue/src/components/user/UserAutoComplete.vue b/orion-ops-vue/src/components/user/UserAutoComplete.vue new file mode 100644 index 0000000..400ef92 --- /dev/null +++ b/orion-ops-vue/src/components/user/UserAutoComplete.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/orion-ops-vue/src/components/user/UserBasicForm.vue b/orion-ops-vue/src/components/user/UserBasicForm.vue new file mode 100644 index 0000000..a87bf08 --- /dev/null +++ b/orion-ops-vue/src/components/user/UserBasicForm.vue @@ -0,0 +1,206 @@ + + + + + diff --git a/orion-ops-vue/src/components/user/UserSelector.vue b/orion-ops-vue/src/components/user/UserSelector.vue new file mode 100644 index 0000000..c207a43 --- /dev/null +++ b/orion-ops-vue/src/components/user/UserSelector.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/orion-ops-vue/src/css/common.less b/orion-ops-vue/src/css/common.less new file mode 100644 index 0000000..599f734 --- /dev/null +++ b/orion-ops-vue/src/css/common.less @@ -0,0 +1,218 @@ +#app { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +::-webkit-scrollbar-track { + //background: rgba(0, 0, 0, .1); + border-radius: 8px; +} + +::-webkit-scrollbar { + -webkit-appearance: none; + width: 5px; + height: 5px; +} + +::-webkit-scrollbar-thumb { + cursor: pointer; + border-radius: 8px; + background: #C8D5E3; + transition: color .2s ease; +} + +.copy-icon-left { + margin-right: 8px; +} + +.copy-icon-right { + margin-left: 8px; +} + +.span-blue { + color: #1890FF !important; +} + +.span-red { + color: #F5222D !important; +} + +.span-light-red { + color: #FF7676 !important; +} + +.pointer { + cursor: pointer !important; +} + +.radius-0 { + border-radius: 0 !important; +} + +.radius-4 { + border-radius: 4px !important; +} + +.radius-8 { + border-radius: 8px !important; +} + +.none { + display: none !important; +} + +.auto-ellipsis { + display: flex; + align-items: center; + align-content: center; +} + +.auto-ellipsis-item { + display: block; + overflow-x: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.normal-label { + display: inline-block; + text-align: right; + color: rgba(0, 0, 0, .85); + font-weight: 400; + font-size: 14px; + line-height: 1.5; +} + +.normal-label::after { + display: inline-block; + content: ':'; + margin-left: 4px; + font-size: 14px; +} + +.required-label::before { + display: inline-block; + content: '*'; + color: #F5222D; + margin-right: 4px; + font-size: 14px; +} + +.normal-description { + color: rgba(0, 0, 0, .45); + font-size: 14px; + line-height: 1.5; +} + +.normal-title { + color: rgba(0, 0, 0, .85); + font-weight: 500; + font-size: 20px; + line-height: 28px; +} + +.flex { + display: flex; +} + +.flex-1 { + flex: 1; +} + +.flex-center { + align-items: center; +} + +.help-text { + font-size: 13px; + display: inline-block; + white-space: nowrap; +} + +.right-menu { + position: absolute; + z-index: 10; + color: #D0EBFF; + + .right-menu-item { + padding: 0 14px; + } +} + +.usn { + user-select: none; +} + +.statistics-description { + margin-top: 4px; + color: rgba(0, 0, 0, .75); + font-size: 18px; + font-weight: 600; + text-align: center; +} + +.send-lf-trigger { + display: block; + margin-right: -8px; + width: 24px; + height: 24px; + padding-top: 5px; + cursor: pointer; + border-radius: 4px; + transition: .2s; +} + +.send-lf-trigger:hover { + background: #E9ECEF; +} + +.send-lf-trigger-enable { + background: #DEE2E6 !important; +} + +.send-lf-trigger-enable:hover { + background: #CED4DA !important; +} + +.command-write-input { + margin-right: 8px; + width: 310px !important; +} + +.text-center { + text-align: center; +} + +.between-input-center { + width: 30px !important; + border-left: 0 !important; + pointer-events: none !important; + background: #FFF !important; + user-select: none !important; +} + +.gray-box-shadow { + box-shadow: 0 1px 4px 0 #D9D9D9; +} + +.line-input { + width: 100%; + height: 100%; + color: #495057; + transition: border-color .15s ease-in-out; + border: none; + border-bottom: 2px solid #CED4DA; + border-radius: 0; + background: transparent; + box-shadow: none; +} + +.line-input:focus { + border-bottom-color: #1890FF; + color: #000; + outline: 0; +} + +.line-input::-webkit-input-placeholder { + color: #999; +} diff --git a/orion-ops-vue/src/css/component.less b/orion-ops-vue/src/css/component.less new file mode 100644 index 0000000..a2312a4 --- /dev/null +++ b/orion-ops-vue/src/css/component.less @@ -0,0 +1,49 @@ +.sftp-transfer-list-popover .ant-popover-inner-content { + padding: 8px 2px 8px 8px; +} + +.sftp-upload-list-popover .ant-popover-inner-content { + padding: 8px 2px 8px 8px; +} + +.profile-content-list-popover .ant-popover-inner-content { + padding: 0; +} + +.machine-content-list-popover .ant-popover-inner-content { + padding: 0; +} + +.ant-modal-draggable { + cursor: grab !important; +} + +.ant-modal-draggable-icon { + font-size: 18px; + margin-right: 8px; + color: #748FFC; +} + +.machine-info-wrapper { + display: flex; + flex-direction: column; + + .machine-info-label { + user-select: none; + display: inline-block; + color: rgba(0, 0, 0.9); + margin-right: 4px; + } + + .machine-info-name-wrapper { + margin-bottom: 2px; + } + + .machine-info-tag-wrapper { + margin-top: 2px; + } + + .machine-info-name-value, .machine-info-tag-value { + color: rgba(0, 0, 0, .7); + } +} diff --git a/orion-ops-vue/src/css/layout.less b/orion-ops-vue/src/css/layout.less new file mode 100644 index 0000000..54ec6c3 --- /dev/null +++ b/orion-ops-vue/src/css/layout.less @@ -0,0 +1,145 @@ +.m0 { + margin: 0 !important; +} + +.mx0 { + margin-left: 0 !important; + margin-right: 0 !important; +} + +.my0 { + margin-top: 0 !important; + margin-bottom: 0 !important; +} + +.ml4 { + margin-left: 4px !important; +} + +.mr4 { + margin-right: 4px !important; +} + +.mx4 { + margin: 0 4px !important; +} + +.mx2 { + margin: 0 2px !important; +} + +.my4 { + margin-top: 4px !important; + margin-bottom: 4px !important; +} + +.ml8 { + margin-left: 8px !important; +} + +.mr8 { + margin-right: 8px !important; +} + +.mt8 { + margin-top: 8px !important; +} + +.mt8 { + margin-top: 8px !important; +} + +.mb8 { + margin-bottom: 8px !important; +} + +.mx8 { + margin: 0 8px !important; +} + +.my8 { + margin-top: 8px !important; + margin-bottom: 8px !important; +} + +.ml16 { + margin-left: 16px !important; +} + +.mr16 { + margin-right: 16px !important; +} + +.mx16 { + margin: 0 16px !important; +} + +.my16 { + margin: 16px 0 !important; +} + +.mt16 { + margin-top: 16px !important; +} + +.mb16 { + margin-bottom: 16px !important; +} + +.p0 { + padding: 0 !important; +} + +.px0 { + padding-left: 0 !important; + padding-right: 0 !important; +} + +.py0 { + padding-top: 0 !important; + padding-bottom: 0 !important; +} + +.pl4 { + padding-left: 4px !important; +} + +.pr4 { + padding-right: 4px !important; +} + +.px4 { + padding: 0 4px !important; +} + +.pl8 { + padding-left: 8px !important; +} + +.pr8 { + padding-right: 8px !important; +} + +.pt8 { + padding-top: 8px !important; +} + +.px8 { + padding: 0 8px !important; +} + +.pl16 { + padding-left: 16px !important; +} + +.pr16 { + padding-right: 16px !important; +} + +.px16 { + padding: 0 16px !important; +} + +.nowrap { + white-space: nowrap; +} diff --git a/orion-ops-vue/src/css/table.less b/orion-ops-vue/src/css/table.less new file mode 100644 index 0000000..f597f5f --- /dev/null +++ b/orion-ops-vue/src/css/table.less @@ -0,0 +1,92 @@ +.table-search-columns { + padding: 18px; + margin-bottom: 18px; + background-color: #FFF; + box-shadow: 0 1px 4px 0 #D9D9D9; + border-radius: 2px; + + .ant-form-item { + margin: 0; + display: flex; + } + + .ant-form-item-control-wrapper { + flex: 0.8; + } + + .ant-select-selection-selected-value { + word-break: break-all; + white-space: pre-line; + height: 30px; + } + +} + +.table-wrapper { + box-shadow: 0 1px 4px 0 #D9D9D9; + border-radius: 2px; +} + +.table-tools-bar { + background-color: #FFF; + padding: 8px 16px; + height: 56px; + border-radius: 2px 2px 0 0; + display: flex; + align-items: center; + justify-content: space-between; + + .tools-fixed-left { + display: flex; + align-items: center; + } + + .tools-fixed-right { + display: flex; + align-items: center; + } + + .table-title { + font-size: 16px; + opacity: 0.8; + padding-right: 8px; + flex: 1; + white-space: nowrap; + color: rgba(0, 0, 0, 0.85); + } + + .tools-icon { + font-size: 18px; + cursor: pointer; + margin: 0 8px; + transition: .2s; + } + + .tools-icon:hover { + color: #1890FF; + } +} + +.table-main-container { + background-color: #FFF; + padding: 0 16px; + border-radius: 0 0 2px 2px; +} + +.table-main-container.table-scroll-x-auto { + .ant-table-body-inner, .ant-table-body, .ant-table-header { + overflow-x: auto !important; + } +} + +.table-main-container.table-scroll-y-auto { + .ant-table-body-inner, .ant-table-body, .ant-table-header { + overflow-y: auto !important; + } +} + +.table-main-container.table-scroll-auto { + .ant-table-body-inner, .ant-table-body, .ant-table-header { + overflow: auto !important; + } +} diff --git a/orion-ops-vue/src/lib/api.js b/orion-ops-vue/src/lib/api.js new file mode 100644 index 0000000..e37b8f8 --- /dev/null +++ b/orion-ops-vue/src/lib/api.js @@ -0,0 +1,2493 @@ +import $http from './http' + +/** + * web socket + */ +const $ws = { + + /** + * 机器终端 + */ + terminal: param => { + return `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}/orion/keep-alive/machine/terminal/${param.token}` + }, + + /** + * 机器终端监视 + */ + terminalWatcher: param => { + return `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}/orion/keep-alive/watcher/terminal/${param.token}` + }, + + /** + * sftp传输列表 + */ + sftpNotify: param => { + return `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}/orion/keep-alive/sftp/notify/${param.token}` + }, + + /** + * 文件tail + */ + fileTail: param => { + return `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}/orion/keep-alive/tail/${param.token}` + } + +} + +/** + * url + */ +const $url = { + + /** + * 执行下载文件请求 + */ + fileDownloadExec: param => { + return `/orion/api/file-download/${param.token}/exec` + } + +} + +const $api = { + + /** + * webSocketUrl + */ + ...$ws, + + /** + * url + */ + ...$url, + + /** + * 登录 + */ + login: param => { + return $http.$post('/auth/login', param, { auth: false }) + }, + + /** + * 登出 + */ + logout: () => { + return $http.$get('/auth/logout', null, { auth: false }) + }, + + /** + * 修改密码 + */ + resetPassword: param => { + return $http.$post('/auth/reset', param) + }, + + /** + * 检查token是否有效 + */ + validToken: () => { + return $http.$get('/auth/valid') + }, + + /** + * 获取菜单 + */ + getMenu: () => { + return $http.$get('/common/menu') + }, + + /** + * 获取用户列表 + */ + getUserList: param => { + return $http.$post('/user/list', param) + }, + + /** + * 获取用户详情 + */ + getUserDetail: param => { + return $http.$post('/user/detail', param) + }, + + /** + * 新增用户 + */ + addUser: param => { + return $http.$post('/user/add', param, { + loading: '正在添加...' + }) + }, + + /** + * 更新用户 + */ + updateUser: param => { + return $http.$post('/user/update', param, { + loading: '正在修改...' + }) + }, + + /** + * 更新用户 + */ + updateAvatar: param => { + return $http.$post('/user/update-avatar', param, { + timeout: 600000 + }) + }, + + /** + * 删除用户 + */ + deleteUser: param => { + return $http.$post('/user/delete', param, { + loading: '正在删除...' + }) + }, + + /** + * 更新用户 + */ + updateUserStatus: param => { + return $http.$post('/user/update-status', param) + }, + + /** + * 解锁用户 + */ + unlockUser: param => { + return $http.$post('/user/unlock', param, { + loading: '正在解锁...' + }) + }, + + /** + * 添加机器 + */ + addMachine: param => { + return $http.$post('/machine/add', param, { + loading: '正在添加...' + }) + }, + + /** + * 修改机器 + */ + updateMachine: param => { + return $http.$post('/machine/update', param, { + loading: '正在修改...' + }) + }, + + /** + * 删除机器 + */ + deleteMachine: param => { + return $http.$post('/machine/delete', param, { + loading: '正在删除...' + }) + }, + + /** + * 修改机器状态 + */ + updateMachineStatus: param => { + return $http.$post('/machine/update-status', param) + }, + + /** + * 机器列表 + */ + getMachineList: param => { + return $http.$post('/machine/list', param) + }, + + /** + * 机器详情 + */ + getMachineDetail: param => { + return $http.$post('/machine/detail', param) + }, + + /** + * 复制机器 + */ + copyMachine: param => { + return $http.$post('/machine/copy', param, { + timeout: 600000, + loading: '正在复制...' + }) + }, + + /** + * 测试机器ping + */ + machineTestPing: param => { + return $http.$post('/machine/test-ping', param) + }, + + /** + * 测试机器连接 + */ + machineTestConnect: param => { + return $http.$post('/machine/test-connect', param) + }, + /** + * 直接测试机器ping + */ + machineDirectTestPing: param => { + return $http.$post('/machine/direct-test-ping', param) + }, + + /** + * 直接测试机器连接 + */ + machineDirectConnect: param => { + return $http.$post('/machine/direct-test-connect', param) + }, + + /** + * 添加机器分组 + */ + addMachineGroup: param => { + return $http.$post('/machine-group/add', param) + }, + + /** + * 删除机器分组 + */ + deleteMachineGroup: param => { + return $http.$post('/machine-group/delete', param) + }, + + /** + * 移动机器分组 + */ + moveMachineGroup: param => { + return $http.$post('/machine-group/move', param) + }, + + /** + * 修改机器分组名称 + */ + renameMachineGroup: param => { + return $http.$post('/machine-group/rename', param) + }, + + /** + * 获取机器分组树 + */ + getMachineGroupTree: () => { + return $http.$get('/machine-group/tree') + }, + + /** + * 添加机器分组机器 + */ + addMachineGroupMachine: param => { + return $http.$post('/machine-group/add-machine', param) + }, + + /** + * 删除机器分组机器 + */ + deleteMachineGroupMachine: param => { + return $http.$post('/machine-group/delete-machine', param) + }, + + /** + * 获取终端访问信息 + */ + accessTerminal: param => { + return $http.$post('/terminal/access', param, { + skipErrorMessage: true + }) + }, + + /** + * 获取支持的终端类型 + */ + getTerminalSupportPyt: () => { + return $http.$get('/terminal/support/pty') + }, + + /** + * 获取机器终端配置 + */ + getTerminalSetting: param => { + return $http.$get(`/terminal/get/${param.machineId}`) + }, + + /** + * 修改机器终端配置 + */ + updateTerminalSetting: param => { + return $http.$post('/terminal/update', param, { + skipErrorMessage: true, + loading: '正在修改...' + }) + }, + + /** + * 终端日志列表 + */ + terminalLogList: param => { + return $http.$post('/terminal/log/list', param) + }, + + /** + * 删除终端日志 + */ + deleteTerminalLog: param => { + return $http.$post('/terminal/log/delete', param, { + loading: '正在删除...' + }) + }, + + /** + * 获取终端录屏 base64 + */ + getTerminalScreen: param => { + return $http.$post('/terminal/log/screen', param, { + loading: '数据加载中...' + }) + }, + + /** + * 终端会话列表 + */ + terminalSessionList: param => { + return $http.$post('/terminal/manager/session', param) + }, + + /** + * 强制下线终端 + */ + terminalOffline: param => { + return $http.$post('/terminal/manager/offline', param, { + loading: '正在操作...' + }) + }, + + /** + * 获取终端监视token + */ + getTerminalWatcherToken: param => { + return $http.$post('/terminal/manager/watcher', param, { + loading: '正在建立连接...' + }) + }, + + /** + * sftp 打开连接 + */ + sftpOpen: param => { + return $http.$post('/sftp/open', param, { skipErrorMessage: true }) + }, + + /** + * sftp 文件列表 + */ + sftpList: param => { + return $http.$post('/sftp/list', param) + }, + + /** + * sftp 文件夹列表 + */ + sftpListDir: param => { + return $http.$post('/sftp/list-dir', param) + }, + + /** + * sftp 创建目录 + */ + sftpMkdir: param => { + return $http.$post('/sftp/mkdir', param, { + skipErrorMessage: true, + loading: '正在创建...' + }) + }, + + /** + * sftp 创建文件 + */ + sftpTouch: param => { + return $http.$post('/sftp/touch', param, { + skipErrorMessage: true, + loading: '正在创建...' + }) + }, + + /** + * sftp 移动文件 + */ + sftpMove: param => { + return $http.$post('/sftp/move', param, { + skipErrorMessage: true, + loading: '正在移动...' + }) + }, + + /** + * sftp 删除文件 + */ + sftpRemove: param => { + return $http.$post('/sftp/remove', param, { + loading: '正在删除...' + }) + }, + + /** + * sftp 截断文件 + */ + sftpTruncate: param => { + return $http.$post('/sftp/truncate', param) + }, + + /** + * sftp 修改权限 + */ + sftpChmod: param => { + return $http.$post('/sftp/chmod', param, { + skipErrorMessage: true, + loading: '正在修改...' + }) + }, + + /** + * sftp 修改所有者 + */ + sftpChown: param => { + return $http.$post('/sftp/chown', param) + }, + + /** + * sftp 修改组 + */ + sftpChgrp: param => { + return $http.$post('/sftp/chgrp', param) + }, + + /** + * sftp 检查文件是否存在 + */ + sftpCheckFilePresent: param => { + return $http.$post('/sftp/check-present', param, { + timeout: 18000000 + }) + }, + + /** + * sftp 获取上传文件 accessToken + */ + getSftpUploadToken: param => { + return $http.$post('/sftp/upload/token', param) + }, + + /** + * sftp 上传文件 + */ + sftpUploadExec: param => { + return $http.$post('/sftp/upload/exec', param, { + timeout: 18000000, + loading: '正在上传文件...', + headers: { + 'Content-Type': 'multipart/form-data' + } + }) + }, + + /** + * sftp 下载文件 + */ + sftpDownloadExec: param => { + return $http.$post('/sftp/download/exec', param, { + timeout: 180000, + loading: '正在请求下载文件...' + }) + }, + + /** + * sftp 压缩下载文件 + */ + sftpPackageDownloadExec: param => { + return $http.$post('/sftp/package-download/exec', param, { + timeout: 180000, + loading: '正在执行压缩命令, 执行完成后自动下载' + }) + }, + + /** + * sftp 传输列表 + */ + sftpTransferList: param => { + return $http.$get(`/sftp/transfer/${param.sessionToken}/list`) + }, + + /** + * sftp 传输暂停 + */ + sftpTransferPause: param => { + return $http.$get(`/sftp/transfer/${param.fileToken}/pause`) + }, + + /** + * sftp 传输恢复 + */ + sftpTransferResume: param => { + return $http.$get(`/sftp/transfer/${param.fileToken}/resume`) + }, + + /** + * sftp 传输失败重试 + */ + sftpTransferRetry: param => { + return $http.$get(`/sftp/transfer/${param.fileToken}/retry`) + }, + + /** + * sftp 重新上传 + */ + sftpTransferReUpload: param => { + return $http.$get(`/sftp/transfer/${param.fileToken}/re-upload`) + }, + + /** + * sftp 重新下载 + */ + sftpTransferReDownload: param => { + return $http.$get(`/sftp/transfer/${param.fileToken}/re-download`) + }, + + /** + * sftp 传输暂停全部 + */ + sftpTransferPauseAll: param => { + return $http.$get(`/sftp/transfer/${param.sessionToken}/pause-all`, null, { + timeout: 180000 + }) + }, + + /** + * sftp 传输恢复全部 + */ + sftpTransferResumeAll: param => { + return $http.$get(`/sftp/transfer/${param.sessionToken}/resume-all`, null, { + timeout: 180000 + }) + }, + + /** + * sftp 传输失败重试全部 + */ + sftpTransferRetryAll: param => { + return $http.$get(`/sftp/transfer/${param.sessionToken}/retry-all`, null, { + timeout: 180000 + }) + }, + + /** + * sftp 传输删除(单个) + */ + sftpTransferRemove: param => { + return $http.$get(`/sftp/transfer/${param.fileToken}/remove`) + }, + + /** + * sftp 传输清空(全部) + */ + sftpTransferClear: param => { + return $http.$get(`/sftp/transfer/${param.sessionToken}/clear`) + }, + + /** + * sftp 传输打包 + */ + sftpTransferPackage: param => { + return $http.$get(`/sftp/transfer/${param.sessionToken}/${param.packageType}/package`) + }, + + /** + * 获取文件下载token + */ + getFileDownloadToken: param => { + return $http.$post('/file-download/token', param) + }, + + /** + * 添加机器代理 + */ + addMachineProxy: param => { + return $http.$post('/machine-proxy/add', param, { + loading: '正在添加...' + }) + }, + + /** + * 更新机器代理 + */ + updateMachineProxy: param => { + return $http.$post('/machine-proxy/update', param, { + loading: '正在修改...' + }) + }, + + /** + * 机器代理列表 + */ + getMachineProxyList: param => { + return $http.$post('/machine-proxy/list', param) + }, + + /** + * 机器代理详情 + */ + getMachineProxyDetail: param => { + return $http.$post('/machine-proxy/detail', param) + }, + + /** + * 删除机器代理 + */ + deleteMachineProxy: param => { + return $http.$post('/machine-proxy/delete', param, { + loading: '正在删除...' + }) + }, + + /** + * 添加密钥 + */ + addMachineKey: param => { + return $http.$post('/machine-key/add', param, { + loading: '正在添加...' + }) + }, + + /** + * 更新密钥 + */ + updateMachineKey: param => { + return $http.$post('/machine-key/update', param, { + loading: '正在修改...' + }) + }, + + /** + * 删除密钥 + */ + removeMachineKey: param => { + return $http.$post('/machine-key/remove', param, { + loading: '正在删除...' + }) + }, + + /** + * 查询密钥列表 + */ + getMachineKeyList: param => { + return $http.$post('/machine-key/list', param) + }, + + /** + * 查询密钥详情 + */ + getMachineKeyDetail: param => { + return $http.$post('/machine-key/detail', param) + }, + + /** + * 绑定机器密钥 + */ + bindMachineKey: param => { + return $http.$post('/machine-key/bind', param, { + loading: '正在绑定...' + }) + }, + + /** + * 添加机器环境变量 + */ + addMachineEnv: param => { + return $http.$post('/machine-env/add', param, { + loading: '正在添加...' + }) + }, + + /** + * 更新机器环境变量 + */ + updateMachineEnv: param => { + return $http.$post('/machine-env/update', param, { + timeout: 600000, + loading: '正在修改...' + }) + }, + + /** + * 删除机器环境变量 + */ + deleteMachineEnv: param => { + return $http.$post('/machine-env/delete', param, { + loading: '正在删除...' + }) + }, + + /** + * 获取机器环境变量列表 + */ + getMachineEnvList: param => { + return $http.$post('/machine-env/list', param) + }, + + /** + * 获取机器环境变量详情 + */ + getMachineEnvDetail: param => { + return $http.$post('/machine-env/detail', param) + }, + + /** + * 获取机器环境变量视图 + */ + getMachineEnvView: param => { + return $http.$post('/machine-env/view', param) + }, + + /** + * 保存机器环境变量视图 + */ + saveMachineEnvView: param => { + return $http.$post('/machine-env/view-save', param, { + skipErrorMessage: true, + timeout: 600000, + loading: '正在保存...' + }) + }, + + /** + * 同步机器环境变量 + */ + syncMachineEnv: param => { + return $http.$post('/machine-env/sync', param, { + timeout: 600000, + loading: '正在同步...' + }) + }, + + /** + * 获取历史值列表 + */ + getHistoryValueList: param => { + return $http.$post('/history-value/list', param) + }, + + /** + * 回滚历史值 + */ + rollbackHistoryValue: param => { + return $http.$post('/history-value/rollback', param, { + loading: '正在回滚...' + }) + }, + + /** + * 执行提交 + */ + submitExecTask: param => { + return $http.$post('/batch-exec/submit', param, { + timeout: 600000, + loading: '正在提交...' + }) + }, + + /** + * 执行列表 + */ + getExecList: param => { + return $http.$post('/batch-exec/list', param) + }, + + /** + * 执行详情 + */ + getExecDetail: param => { + return $http.$post('/batch-exec/detail', param) + }, + + /** + * 执行输入 + */ + writeExecTask: param => { + return $http.$post('/batch-exec/write', param) + }, + + /** + * 执行停止 + */ + terminateExecTask: param => { + return $http.$post('/batch-exec/terminate', param, { + loading: '正在停止...' + }) + }, + + /** + * 删除执行任务 + */ + deleteExecTask: param => { + return $http.$post('/batch-exec/delete', param, { + loading: '正在删除...' + }) + }, + + /** + * 获取执行状态 + */ + getExecTaskStatus: param => { + return $http.$post('/batch-exec/list-status', param, { + skipErrorMessage: true + }) + }, + + /** + * 批量上传检查机器以及文件 + */ + checkBatchUploadFiles: param => { + return $http.$post('/batch-upload/check', param, { + timeout: 18000000 + }) + }, + + /** + * 获取批量上传 token + */ + getBatchUploadToken: param => { + return $http.$post('/batch-upload/token', param, { + timeout: 18000000 + }) + }, + + /** + * 执行批量上传 + */ + execBatchUpload: param => { + return $http.$post('/batch-upload/exec', param, { + timeout: 18000000, + headers: { + 'Content-Type': 'multipart/form-data' + } + }) + }, + + /** + * 新增模板 + */ + addTemplate: param => { + return $http.$post('/template/add', param, { + loading: '正在添加...' + }) + }, + + /** + * 修改模板 + */ + updateTemplate: param => { + return $http.$post('/template/update', param, { + loading: '正在修改...' + }) + }, + + /** + * 模板列表 + */ + getTemplateList: param => { + return $http.$post('/template/list', param) + }, + + /** + * 模板详情 + */ + getTemplateDetail: param => { + return $http.$post('/template/detail', param) + }, + + /** + * 删除模板 + */ + deleteTemplate: param => { + return $http.$post('/template/delete', param, { + loading: '正在删除...' + }) + }, + + /** + * 获取 tail token + */ + getTailToken: param => { + return $http.$post('/file-tail/token', param) + }, + + /** + * 添加 tail 文件 + */ + addTailFile: param => { + return $http.$post('/file-tail/add', param, { + loading: '正在添加...' + }) + }, + + /** + * 修改 tail 文件 + */ + updateTailFile: param => { + return $http.$post('/file-tail/update', param, { + loading: '正在修改...' + }) + }, + + /** + * 上传 tail 文件 + */ + uploadTailFile: param => { + return $http.$post('/file-tail/upload', param, { + loading: '正在上传...', + timeout: 18000000, + headers: { + 'Content-Type': 'multipart/form-data' + } + }) + }, + + /** + * 清理文件 ANSI 码 + */ + cleanFileAnsiCode: param => { + return $http.$post('/file-tail/clean-ansi', param, { + skipRespInterceptor: true, + responseType: 'blob', + timeout: 18000000, + headers: { + 'Content-Type': 'multipart/form-data' + } + }) + }, + + /** + * 删除 tail 文件 + */ + deleteTailFile: param => { + return $http.$post('/file-tail/delete', param, { + loading: '正在删除...' + }) + }, + + /** + * 获取 tail 文件列表 + */ + getTailList: param => { + return $http.$post('/file-tail/list', param) + }, + + /** + * 获取 tail 详情 + */ + getTailDetail: param => { + return $http.$post('/file-tail/detail', param) + }, + + /** + * 获取 tail 机器默认配置 + */ + getTailConfig: param => { + return $http.$post('/file-tail/config', param, { skipErrorMessage: true }) + }, + + /** + * tail 输入命令 + */ + writeTailCommand: param => { + return $http.$post('/file-tail/write', param) + }, + + /** + * 应用环境列表 + */ + getProfileList: param => { + return $http.$post('/app-profile/list', param) + }, + + /** + * 应用环境列表 (快速) + */ + fastGetProfileList: param => { + return $http.$get('/app-profile/fast-list', param) + }, + + /** + * 应用环境详情 + */ + getProfileDetail: param => { + return $http.$post('/app-profile/detail', param) + }, + + /** + * 添加应用环境 + */ + addProfile: param => { + return $http.$post('/app-profile/add', param, { + loading: '正在添加...' + }) + }, + + /** + * 修改应用环境 + */ + updateProfile: param => { + return $http.$post('/app-profile/update', param, { + loading: '正在修改...' + }) + }, + + /** + * 删除应用环境 + */ + deleteProfile: param => { + return $http.$post('/app-profile/delete', param, { + loading: '正在删除...' + }) + }, + + /** + * 添加应用变量 + */ + addAppEnv: param => { + return $http.$post('/app-env/add', param, { + loading: '正在添加...' + }) + }, + + /** + * 删除应用变量 + */ + deleteAppEnv: param => { + return $http.$post('/app-env/delete', param, { + loading: '正在删除...' + }) + }, + + /** + * 修改应用变量 + */ + updateAppEnv: param => { + return $http.$post('/app-env/update', param, { + loading: '正在修改...' + }) + }, + + /** + * 应用环境变量列表 + */ + getAppEnvList: param => { + return $http.$post('/app-env/list', param) + }, + + /** + * 应用环境变量详情 + */ + getAppEnvDetail: param => { + return $http.$post('/app-env/detail', param) + }, + + /** + * 同步应用环境变量 + */ + syncAppEnv: param => { + return $http.$post('/app-env/sync', param, { + timeout: 600000, + loading: '正在同步...' + }) + }, + + /** + * 应用环境变量视图 + */ + getAppEnvView: param => { + return $http.$post('/app-env/view', param) + }, + + /** + * 应用环境变量视图保存 + */ + saveAppEnvView: param => { + return $http.$post('/app-env/view-save', param, { + skipErrorMessage: true, + timeout: 600000, + loading: '正在保存...' + }) + }, + + /** + * 添加应用版本仓库 + */ + addRepository: param => { + return $http.$post('/app-repo/add', param, { + loading: '正在添加...' + }) + }, + + /** + * 修改应用版本仓库 + */ + updateRepository: param => { + return $http.$post('/app-repo/update', param, { + timeout: 600000, + loading: '正在修改...' + }) + }, + + /** + * 删除版本仓库 + */ + deleteRepository: param => { + return $http.$post('/app-repo/delete', param, { + timeout: 600000, + loading: '正在删除...' + }) + }, + + /** + * 获取版本仓库列表 + */ + getRepositoryList: param => { + return $http.$post('/app-repo/list', param) + }, + + /** + * 获取版本仓库详情 + */ + getRepositoryDetail: param => { + return $http.$post('/app-repo/detail', param) + }, + + /** + * 初始化版本仓库 + */ + initRepository: param => { + return $http.$post('/app-repo/init', param, { + timeout: 600000, + loading: '正在初始化...' + }) + }, + + /** + * 重新初始化版本仓库 + */ + reInitRepository: param => { + return $http.$post('/app-repo/re-init', param, { + timeout: 600000, + loading: '正在初始化...' + }) + }, + + /** + * 清空仓库 + */ + cleanRepository: param => { + return $http.$post('/app-repo/clean', param, { + timeout: 600000, + loading: '正在清空...' + }) + }, + + /** + * 获取版本仓库分支和提交记录信息 + */ + getRepositoryInfo: param => { + return $http.$post('/app-repo/info', param) + }, + + /** + * 获取版本仓库分支列表 + */ + getRepositoryBranchList: param => { + return $http.$post('/app-repo/branch', param) + }, + + /** + * 获取版本仓库提交列表 + */ + getRepositoryCommitList: param => { + return $http.$post('/app-repo/commit', param) + }, + + /** + * 添加应用 + */ + addApp: param => { + return $http.$post('/app-info/add', param, { + loading: '正在添加...' + }) + }, + + /** + * 更新应用 + */ + updateApp: param => { + return $http.$post('/app-info/update', param, { + loading: '正在修改...' + }) + }, + + /** + * 更新应用排序 + */ + adjustAppSort: param => { + return $http.$post('/app-info/sort', param) + }, + + /** + * 删除应用 + */ + deleteApp: param => { + return $http.$post('/app-info/delete', param, { + loading: '正在删除...' + }) + }, + + /** + * 应用列表 + */ + getAppList: param => { + return $http.$post('/app-info/list', param) + }, + + /** + * 应用机器列表 + */ + getAppMachineList: param => { + return $http.$post('/app-info/list-machine', param) + }, + + /** + * 获取应用 + */ + getAppDetail: param => { + return $http.$post('/app-info/detail', param) + }, + + /** + * 配置应用 + */ + configApp: param => { + return $http.$post('/app-info/config', param, { + timeout: 600000, + loading: '正在保存...' + }) + }, + + /** + * 同步应用 + */ + syncApp: param => { + return $http.$post('/app-info/sync', param, { + timeout: 600000, + loading: '正在同步...' + }) + }, + + /** + * 复制应用 + */ + copyApp: param => { + return $http.$post('/app-info/copy', param, { + timeout: 600000, + loading: '正在复制...' + }) + }, + + /** + * 删除应用机器 + */ + deleteAppMachine: param => { + return $http.$post('/app-info/delete-machine', param, { + loading: '正在删除...' + }) + }, + + /** + * 获取应用机器id + */ + getAppMachineId: param => { + return $http.$post('/app-info/get-machine-id', param) + }, + + /** + * 提交应用构建 + */ + submitAppBuild: param => { + return $http.$post('/app-build/submit', param, { + timeout: 600000, + loading: '正在提交...' + }) + }, + + /** + * 应用操作详情 + */ + getAppActionDetail: param => { + return $http.$post('/app-action-log/detail', param) + }, + + /** + * 应用操作状态 + */ + getAppActionStatus: param => { + return $http.$post('/app-action-log/status', param, { + skipErrorMessage: true + }) + }, + + /** + * 应用构建列表 + */ + getAppBuildList: param => { + return $http.$post('/app-build/list', param) + }, + + /** + * 应用构建详情 + */ + getAppBuildDetail: param => { + return $http.$post('/app-build/detail', param) + }, + + /** + * 获取应用构建状态 + */ + getAppBuildStatus: param => { + return $http.$post('/app-build/status', param, { + skipErrorMessage: true + }) + }, + + /** + * 停止应用构建 + */ + terminateAppBuild: param => { + return $http.$post('/app-build/terminate', param, { + loading: '正在停止...' + }) + }, + + /** + * 应用构建输入命令 + */ + writeAppBuild: param => { + return $http.$post('/app-build/write', param) + }, + + /** + * 删除应用构建 + */ + deleteAppBuild: param => { + return $http.$post('/app-build/delete', param, { + loading: '正在删除...' + }) + }, + + /** + * 重新构建应用 + */ + rebuildApp: param => { + return $http.$post('/app-build/rebuild', param, { + timeout: 600000, + loading: '正在重新构建...' + }) + }, + + /** + * 获取发布构建列表 + */ + getBuildReleaseList: param => { + return $http.$post('/app-build/release-list', param) + }, + + /** + * 应用构建状态列表 + */ + getAppBuildStatusList: param => { + return $http.$post('/app-build/list-status', param, { + skipErrorMessage: true + }) + }, + + /** + * 应用发布列表 + */ + getAppReleaseList: param => { + return $http.$post('/app-release/list', param) + }, + + /** + * 应用发布机器列表 + */ + getAppReleaseMachineList: param => { + return $http.$post('/app-release/list-machine', param) + }, + + /** + * 应用发布详情 + */ + getAppReleaseDetail: param => { + return $http.$post('/app-release/detail', param) + }, + + /** + * 应用发布机器详情 + */ + getAppReleaseMachineDetail: param => { + return $http.$post('/app-release/machine-detail', param) + }, + + /** + * 提交应用发布 + */ + submitAppRelease: param => { + return $http.$post('/app-release/submit', param, { + timeout: 600000, + loading: '正在提交...' + }) + }, + + /** + * 复制应用发布 + */ + copyAppRelease: param => { + return $http.$post('/app-release/copy', param, { + timeout: 600000, + loading: '正在复制...' + }) + }, + + /** + * 审核应用发布 + */ + auditAppRelease: param => { + return $http.$post('/app-release/audit', param, { + loading: '正在操作...' + }) + }, + + /** + * 执行应用发布 + */ + runnableAppRelease: param => { + return $http.$post('/app-release/runnable', param, { + loading: '正在执行...' + }) + }, + + /** + * 应用取消定时发布 + */ + cancelAppTimedRelease: param => { + return $http.$post('/app-release/cancel-timed', param, { + loading: '正在取消...' + }) + }, + + /** + * 设置定时发布时间 + */ + setAppTimedRelease: param => { + return $http.$post('/app-release/set-timed', param, { + loading: '正在设置...' + }) + }, + + /** + * 应用回滚发布 + */ + rollbackAppRelease: param => { + return $http.$post('/app-release/rollback', param, { + timeout: 600000, + loading: '正在提交...' + }) + }, + + /** + * 删除应用发布 + */ + deleteAppRelease: param => { + return $http.$post('/app-release/delete', param, { + loading: '正在删除...' + }) + }, + + /** + * 应用发布停止 + */ + terminateAppRelease: param => { + return $http.$post('/app-release/terminate', param, { + loading: '正在停止...' + }) + }, + + /** + * 应用发布机器停止 + */ + terminateAppReleaseMachine: param => { + return $http.$post('/app-release/terminate-machine', param, { + loading: '正在停止...' + }) + }, + + /** + * 应用发布机器跳过 + */ + skipAppReleaseMachine: param => { + return $http.$post('/app-release/skip-machine', param, { + loading: '正在跳过...' + }) + }, + + /** + * 应用发布机器输入命令 + */ + writeAppReleaseMachine: param => { + return $http.$post('/app-release/write-machine', param) + }, + + /** + * 应用发布列表状态 + */ + getAppReleaseListStatus: param => { + return $http.$post('/app-release/list-status', param, { + skipErrorMessage: true + }) + }, + + /** + * 应用发布状态 + */ + getAppReleaseStatus: param => { + return $http.$post('/app-release/status', param, { + skipErrorMessage: true + }) + }, + + /** + * 应用发布机器列表状态 + */ + getAppReleaseMachineListStatus: param => { + return $http.$post('/app-release/list-machine-status', param, { + skipErrorMessage: true + }) + }, + + /** + * 应用发布机器状态 + */ + getAppReleaseMachineStatus: param => { + return $http.$post('/app-release/machine-status', param, { + skipErrorMessage: true + }) + }, + + /** + * 首页统计信息 + */ + getHomeStatistics: param => { + return $http.$post('/statistics/home', param, { + skipErrorMessage: true + }) + }, + + /** + * 调度任务统计信息 + */ + getSchedulerTaskStatistics: param => { + return $http.$post('/statistics/scheduler-task', param) + }, + + /** + * 应用构建统计信息 指标 + */ + getAppBuildStatisticsMetrics: param => { + return $http.$post('/statistics/app-build/metrics', param) + }, + + /** + * 应用构建统计信息 视图 + */ + getAppBuildStatisticsView: param => { + return $http.$post('/statistics/app-build/view', param) + }, + + /** + * 应用构建统计信息 折线图 + */ + getAppBuildStatisticsChart: param => { + return $http.$post('/statistics/app-build/chart', param) + }, + + /** + * 应用发布统计信息 指标 + */ + getAppReleaseStatisticsMetrics: param => { + return $http.$post('/statistics/app-release/metrics', param) + }, + + /** + * 应用发布统计信息 视图 + */ + getAppReleaseStatisticsView: param => { + return $http.$post('/statistics/app-release/view', param) + }, + + /** + * 应用发布统计信息 折线图 + */ + getAppReleaseStatisticsChart: param => { + return $http.$post('/statistics/app-release/chart', param) + }, + + /** + * 应用流水线统计信息 指标 + */ + getAppPipelineTaskStatisticsMetrics: param => { + return $http.$post('/statistics/app-pipeline/metrics', param) + }, + + /** + * 应用流水线统计信息 视图 + */ + getAppPipelineTaskStatisticsView: param => { + return $http.$post('/statistics/app-pipeline/view', param) + }, + + /** + * 应用流水线统计信息 折线图 + */ + getAppPipelineTaskStatisticsChart: param => { + return $http.$post('/statistics/app-pipeline/chart', param) + }, + + /** + * 获取操作日志列表 + */ + getEventLogList: param => { + return $http.$post('/event-log/list', param) + }, + + /** + * 添加系统环境变量 + */ + addSystemEnv: param => { + return $http.$post('/system-env/add', param, { + loading: '正在添加...' + }) + }, + + /** + * 更新系统环境变量 + */ + updateSystemEnv: param => { + return $http.$post('/system-env/update', param, { + timeout: 600000, + loading: '正在修改...' + }) + }, + + /** + * 删除系统环境变量 + */ + deleteSystemEnv: param => { + return $http.$post('/system-env/delete', param, { + loading: '正在删除...' + }) + }, + + /** + * 获取系统环境变量列表 + */ + getSystemEnvList: param => { + return $http.$post('/system-env/list', param) + }, + + /** + * 获取系统环境变量详情 + */ + getSystemEnvDetail: param => { + return $http.$post('/system-env/detail', param) + }, + + /** + * 获取系统环境变量视图 + */ + getSystemEnvView: param => { + return $http.$post('/system-env/view', param) + }, + + /** + * 保存系统环境变量视图 + */ + saveSystemEnvView: param => { + return $http.$post('/system-env/view-save', param, { + skipErrorMessage: true, + timeout: 600000, + loading: '正在保存...' + }) + }, + + /** + * 获取 ip 配置 + */ + getIpInfo: () => { + return $http.$get('/system/ip-info') + }, + + /** + * 配置 ip 列表 + */ + configIpList: param => { + return $http.$post('/system/config-ip', param, { + loading: '正在保存...' + }) + }, + + /** + * 获取系统分析信息 + */ + getSystemAnalysis: () => { + return $http.$get('/system/get-system-analysis') + }, + + /** + * 重新进行系统统计分析 + */ + reAnalysisSystem: () => { + return $http.$get('/system/re-analysis', null, { + timeout: 600000 + }) + }, + + /** + * 清理系统文件 + */ + cleanSystemFile: param => { + return $http.$post('/system/clean-system-file', param) + }, + + /** + * 修改系统配置项 + */ + updateSystemOption: param => { + return $http.$post('/system/update-system-option', param) + }, + + /** + * 获取系统配置项 + */ + getSystemOptions: () => { + return $http.$get('/system/get-system-options') + }, + + /** + * 获取系统线程池指标 + */ + getSystemThreadMetrics: () => { + return $http.$get('/system/get-thread-metrics') + }, + + /** + * 获取系统信息 + */ + getSystemAbout: () => { + return $http.$get('/system/about') + }, + + /** + * 获取 cron 下几次执行时间 + */ + getCronNextTime: param => { + return $http.$post('/scheduler/cron-next', param) + }, + + /** + * 添加调度任务 + */ + addSchedulerTask: param => { + return $http.$post('/scheduler/add', param, { + loading: '正在保存...' + }) + }, + + /** + * 修改调度任务 + */ + updateSchedulerTask: param => { + return $http.$post('/scheduler/update', param, { + loading: '正在修改...' + }) + }, + + /** + * 获取调度任务详情 + */ + getSchedulerTask: param => { + return $http.$post('/scheduler/get', param) + }, + + /** + * 获取调度任务列表 + */ + getSchedulerTaskList: param => { + return $http.$post('/scheduler/list', param) + }, + + /** + * 更新调度任务状态 + */ + updateSchedulerTaskStatus: param => { + return $http.$post('/scheduler/update-status', param, { + loading: '正在更新...' + }) + }, + + /** + * 删除调度任务 + */ + deleteSchedulerTask: param => { + return $http.$post('/scheduler/delete', param, { + loading: '正在删除...' + }) + }, + + /** + * 手动触发调度任务 + */ + manualTriggerSchedulerTask: param => { + return $http.$post('/scheduler/manual-trigger', param, { + loading: '正在触发...' + }) + }, + + /** + * 查询调度任务执行列表 + */ + getSchedulerTaskRecordList: param => { + return $http.$post('/scheduler-record/list', param) + }, + + /** + * 查询调度任务执行详情 + */ + getSchedulerTaskRecordDetail: param => { + return $http.$post('/scheduler-record/detail', param) + }, + + /** + * 查询调度任务机器列表 + */ + getSchedulerTaskMachinesRecordList: param => { + return $http.$post('/scheduler-record/machines', param) + }, + + /** + * 查询调度任务状态 + */ + getSchedulerTaskRecordStatus: param => { + return $http.$post('/scheduler-record/list-status', param, { + skipErrorMessage: true + }) + }, + + /** + * 查询调度任务机器明细状态 + */ + getSchedulerTaskMachinesRecordStatus: param => { + return $http.$post('/scheduler-record/machines-status', param, { + skipErrorMessage: true + }) + }, + + /** + * 删除调度任务明细 + */ + deleteSchedulerTaskRecord: param => { + return $http.$post('/scheduler-record/delete', param, { + loading: '正在删除...' + }) + }, + + /** + * 停止所有调度任务机器 + */ + terminateAllSchedulerTaskRecord: param => { + return $http.$post('/scheduler-record/terminate-all', param, { + loading: '正在停止...' + }) + }, + + /** + * 停止单个调度任务机器 + */ + terminateMachineSchedulerTaskRecord: param => { + return $http.$post('/scheduler-record/terminate-machine', param, { + loading: '正在停止...' + }) + }, + + /** + * 跳过单个调度任务机器 + */ + skipMachineSchedulerTaskRecord: param => { + return $http.$post('/scheduler-record/skip-machine', param, { + loading: '正在跳过...' + }) + }, + + /** + * 发送调度任务机器命令 + */ + writeMachineSchedulerTaskRecord: param => { + return $http.$post('/scheduler-record/write-machine', param) + }, + + /** + * 获取站内信未读数量 + */ + getWebSideMessageUnreadCount: () => { + return $http.$get('/message/unread-count', null, { + skipErrorMessage: true + }) + }, + + /** + * 设置站内信全部已读 + */ + setWebSideMessageAllRead: () => { + return $http.$get('/message/set-all-read') + }, + + /** + * 设置已读站内信 + */ + setMessageRead: params => { + return $http.$post('/message/read', params, { + skipErrorMessage: true + }) + }, + + /** + * 删除全部已读站内信 + */ + deleteAllReadMessage: () => { + return $http.$get('/message/delete-all-read') + }, + + /** + * 删除站内信 + */ + deleteWebSideMessage: param => { + return $http.$post('/message/delete', param) + }, + + /** + * 获取站内信详情 + */ + getWebSideMessageDetail: param => { + return $http.$post('/message/detail', param) + }, + + /** + * 获取站内信列表 + */ + getWebSideMessageList: param => { + return $http.$post('/message/list', param) + }, + + /** + * 获取最新站内信 + */ + getNewMessage: param => { + return $http.$post('/message/get-new-message', param) + }, + + /** + * 获取更多站内信 + */ + getMoreMessage: param => { + return $http.$post('/message/get-more-message', param) + }, + + /** + * 轮询获取站内信 + */ + pollWebSideMessage: param => { + return $http.$post('/message/poll-new-message', param, { + skipErrorMessage: true + }) + }, + + /** + * 新增应用流水线 + */ + addAppPipeline: param => { + return $http.$post('/app-pipeline/add', param, { + loading: '正在保存...' + }) + }, + + /** + * 修改应用流水线 + */ + updateAppPipeline: param => { + return $http.$post('/app-pipeline/update', param, { + loading: '正在保存...' + }) + }, + + /** + * 获取应用流水线列表 + */ + getAppPipelineList: param => { + return $http.$post('/app-pipeline/list', param) + }, + + /** + * 获取应用流水线详情 + */ + getAppPipelineDetail: param => { + return $http.$post('/app-pipeline/get', param) + }, + + /** + * 删除应用流水线 + */ + deleteAppPipeline: param => { + return $http.$post('/app-pipeline/delete', param, { + loading: '正在删除...' + }) + }, + + /** + * 获取应用流水线任务列表 + */ + getAppPipelineTaskList: param => { + return $http.$post('/app-pipeline-task/list', param) + }, + + /** + * 获取应用流水线任务详情 + */ + getAppPipelineTaskDetail: param => { + return $http.$post('/app-pipeline-task/detail', param) + }, + + /** + * 获取应用流水线任务详情 + */ + getAppPipelineTaskDetails: param => { + return $http.$post('/app-pipeline-task/task-details', param) + }, + + /** + * 提交执行应用流水线任务 + */ + submitAppPipelineTask: param => { + return $http.$post('/app-pipeline-task/submit', param, { + timeout: 600000, + loading: '正在提交...' + }) + }, + + /** + * 审核应用流水线任务 + */ + auditAppPipelineTask: param => { + return $http.$post('/app-pipeline-task/audit', param, { + loading: '正在操作...' + }) + }, + + /** + * 复制应用流水线任务 + */ + copyAppPipelineTask: param => { + return $http.$post('/app-pipeline-task/copy', param, { + timeout: 600000, + loading: '正在复制...' + }) + }, + + /** + * 执行应用流水线任务 + */ + execAppPipelineTask: param => { + return $http.$post('/app-pipeline-task/exec', param, { + loading: '正在执行...' + }) + }, + + /** + * 删除应用流水线任务 + */ + deleteAppPipelineTask: param => { + return $http.$post('/app-pipeline-task/delete', param, { + loading: '正在删除...' + }) + }, + + /** + * 设置应用流水线定时任务 + */ + setAppPipelineTaskTimedExec: param => { + return $http.$post('/app-pipeline-task/set-timed', param, { + loading: '正在设置...' + }) + }, + + /** + * 取消应用流水线定时任务 + */ + cancelAppPipelineTaskTimedExec: param => { + return $http.$post('/app-pipeline-task/cancel-timed', param, { + loading: '正在取消...' + }) + }, + + /** + * 停止执行应用流水线任务 + */ + terminateAppPipelineTask: param => { + return $http.$post('/app-pipeline-task/terminate', param, { + loading: '正在停止...' + }) + }, + + /** + * 停止执行应用流水线任务部分操作 + */ + terminateAppPipelineTaskDetail: param => { + return $http.$post('/app-pipeline-task/terminate-detail', param, { + loading: '正在停止...' + }) + }, + + /** + * 停止执行应用流水线任务部分操作 + */ + skipAppPipelineTaskDetail: param => { + return $http.$post('/app-pipeline-task/skip-detail', param, { + loading: '正在跳过...' + }) + }, + + /** + * 应用流水线任务状态 + */ + geAppPipelineTaskStatus: param => { + return $http.$post('/app-pipeline-task/status', param, { + skipErrorMessage: true + }) + }, + + /** + * 应用流水线任务状态列表 + */ + getAppPipelineTaskListStatus: param => { + return $http.$post('/app-pipeline-task/list-status', param, { + skipErrorMessage: true + }) + }, + + /** + * 应用流水线任务日志 + */ + getAppPipelineTaskLog: param => { + return $http.$post('/app-pipeline-task/log', param) + }, + + /** + * 清理数据 + */ + clearData: param => { + return $http.$post('/data-clear/clear', param, { + loading: '正在清理...' + }) + }, + + /** + * 导出数据 + */ + exportData: param => { + return $http.$export('/data-export/export', param) + }, + + /** + * 获取导入模板 + */ + getImportTemplate: param => { + return $http.$get('/data-import/get-template', param, { + skipRespInterceptor: true, + timeout: 600000, + responseType: 'blob' + }) + }, + + /** + * 检查导入数据 + */ + checkImportData: param => { + return $http.$post('/data-import/check-data', param, { + timeout: 18000000, + headers: { + 'Content-Type': 'multipart/form-data' + } + }) + }, + + /** + * 导入数据 + */ + importData: param => { + return $http.$post('/data-import/import', param) + }, + + /** + * 取消导入数据 + */ + cancelImportData: param => { + return $http.$post('/data-import/cancel-import', param) + }, + + /** + * 获取机器监控列表 + */ + getMachineMonitorList: param => { + return $http.$post('/monitor/list', param) + }, + + /** + * 获取机器监控配置 + */ + getMachineMonitorConfig: param => { + return $http.$get('/monitor/get-config', param) + }, + + /** + * 设置机器监控配置 + */ + setMachineMonitorConfig: param => { + return $http.$post('/monitor/set-config', param) + }, + + /** + * 测试连接监控 + */ + testPingMachineMonitor: param => { + return $http.$post('/monitor/test', param, { + skipErrorMessage: true + }) + }, + + /** + * 安装机器监控插件 + */ + installMachineMonitorAgent: param => { + return $http.$post('/monitor/install', param) + }, + + /** + * 检测机器监控插件状态 + */ + checkMachineMonitorAgentStatus: param => { + return $http.$post('/monitor/check', param) + }, + + /** + * 查询机器监控指标 + */ + getMachineMonitorMetrics: param => { + return $http.$get('/monitor-endpoint/metrics', param) + }, + + /** + * 查询机器 负载 + */ + getMachineMonitorLoad: param => { + return $http.$get('/monitor-endpoint/load', param) + }, + + /** + * 查询机器 top 进程 + */ + getMachineMonitorTop: param => { + return $http.$get('/monitor-endpoint/top', param) + }, + + /** + * 查询机器硬盘名称 + */ + getMachineDiskName: param => { + return $http.$get('/monitor-endpoint/disk-name', param) + }, + + /** + * 查询机器cpu图表 + */ + getMachineMonitorCpuChart: param => { + return $http.$post('/monitor-endpoint/chart-cpu', param) + }, + + /** + * 查询机器内存图表 + */ + getMachineMonitorMemoryChart: param => { + return $http.$post('/monitor-endpoint/chart-memory', param) + }, + + /** + * 查询机器网络图表 + */ + getMachineMonitorNetChart: param => { + return $http.$post('/monitor-endpoint/chart-net', param) + }, + + /** + * 查询机器硬盘图表 + */ + getMachineMonitorDiskChart: param => { + return $http.$post('/monitor-endpoint/chart-disk', param) + }, + + /** + * 添加 webhook 配置 + */ + addWebhookConfig: param => { + return $http.$post('/webhook-config/add', param, { + loading: '正在保存...' + }) + }, + + /** + * 修改 webhook 配置 + */ + updateWebhookConfig: param => { + return $http.$post('/webhook-config/update', param, { + loading: '正在修改...' + }) + }, + + /** + * 删除 webhook 配置 + */ + deleteWebhookConfig: param => { + return $http.$post('/webhook-config/delete', param, { + loading: '正在删除...' + }) + }, + + /** + * 获取 webhook 详情 + */ + getWebhookConfigDetail: param => { + return $http.$post('/webhook-config/get', param) + }, + + /** + * 获取 webhook 列表 + */ + getWebhookConfigList: param => { + return $http.$post('/webhook-config/list', param) + }, + + /** + * 添加报警组配置 + */ + addAlarmGroup: param => { + return $http.$post('/alarm-group/add', param, { + loading: '正在保存...' + }) + }, + + /** + * 修改报警组配置 + */ + updateAlarmGroup: param => { + return $http.$post('/alarm-group/update', param, { + loading: '正在修改...' + }) + }, + + /** + * 删除报警组配置 + */ + deleteAlarmGroup: param => { + return $http.$post('/alarm-group/delete', param, { + loading: '正在删除...' + }) + }, + + /** + * 获取报警组详情 + */ + getAlarmGroupDetail: param => { + return $http.$post('/alarm-group/get', param) + }, + + /** + * 获取报警组列表 + */ + getAlarmGroupList: param => { + return $http.$post('/alarm-group/list', param) + }, + + /** + * 获取机器报警配置 + */ + getMachineAlarmConfig: param => { + return $http.$get('/machine-alarm/get-config', param) + }, + + /** + * 设置机器报警配置 + */ + setMachineAlarmConfig: param => { + return $http.$post('/machine-alarm/set-alarm-config', param, { + loading: '正在修改...' + }) + }, + + /** + * 设置机器报警联系组 + */ + setMachineAlarmGroup: param => { + return $http.$post('/machine-alarm/set-group-config', param, { + loading: '正在修改...' + }) + }, + + /** + * 获取机器报警记录 + */ + getMachineAlarmHistory: param => { + return $http.$post('/machine-alarm/history', param) + }, + + /** + * 触发机器报警通知 + */ + triggerMachineAlarmNotify: param => { + return $http.$post('/machine-alarm/trigger-alarm-notify', param, { + loading: '正在通知...' + }) + } + +} + +export default $api diff --git a/orion-ops-vue/src/lib/chart.js b/orion-ops-vue/src/lib/chart.js new file mode 100644 index 0000000..2bc4c9e --- /dev/null +++ b/orion-ops-vue/src/lib/chart.js @@ -0,0 +1,56 @@ +import { Chart } from '@antv/g2' +import { formatSecond } from '@/lib/utils' + +/** + * 渲染时间戳对象折现图 + */ +export function timestampRender(id, chatObject, chartField, timeFormatter, valueFormatter, tooltipFormatter, renderData) { + let chart = chatObject[chartField] + const needInit = !chart + if (needInit) { + chart = chatObject[chartField] = new Chart({ + container: id, + autoFit: true + }) + chart.animate(false) + chart.scale({ + time: { + tickCount: 6 + }, + value: { + nice: true + } + }) + if (timeFormatter) { + chart.axis('time', { + label: { + formatter: timeFormatter + } + }) + } else { + chart.axis('time', false) + } + if (valueFormatter) { + chart.axis('value', { + label: { + formatter: valueFormatter + } + }) + } else { + chart.axis('value', false) + } + chart.tooltip({ + title: (title, datum) => formatSecond(datum.time, 'yyyy-MM-dd HH:mm:ss'), + customItems: (items) => { + tooltipFormatter(items[0]) + return items + } + }) + chart.line().position('time*value') + chart.data(renderData) + chart.render() + } else { + chart.changeData(renderData) + chart.render() + } +} diff --git a/orion-ops-vue/src/lib/directive.js b/orion-ops-vue/src/lib/directive.js new file mode 100644 index 0000000..93a3aa3 --- /dev/null +++ b/orion-ops-vue/src/lib/directive.js @@ -0,0 +1,122 @@ +import Vue from 'vue' + +/** + * 设置title data-title v-title + */ +Vue.directive('title', { + inserted: function(el, binding) { + document.title = el.dataset.title + } +}) + +/** + * 限制input只能输入正则匹配的 + */ +Vue.directive('limit-pattern', limitPattern()) + +/** + * 限制input只能输入正整数 + */ +Vue.directive('limit-integer', limitPattern(/^(0+)|[^\d]+/g)) + +/** + * 限制input输入n位小数 + */ +Vue.directive('limit-decimal', { + inserted: function(el, binding, vnode) { + // input外层的div + const input = el.tagName === 'INPUT' ? el : el.querySelector('input') + input.addEventListener('keyup', function() { + const reg = new RegExp('\\d+(\\.\\d{0,' + binding.value + '})?') + input.value = input.value.match(reg) ? input.value.match(reg)[0] : '' + // 手动触发数据的双向绑定 + if (vnode.componentInstance) { + vnode.componentInstance.stateValue = input.value + } else { + vnode.elm.dispatchEvent(new CustomEvent('input', input.value)) + } + }) + + input.addEventListener('afterpaste', function() { + const reg = new RegExp('\\d+(\\.\\d{0,' + binding.value + '})?') + input.value = input.value.match(reg) ? input.value.match(reg)[0] : '' + // 手动触发数据的双向绑定 + if (vnode.componentInstance) { + vnode.componentInstance.stateValue = input.value + } else { + vnode.elm.dispatchEvent(new CustomEvent('input', input.value)) + } + }) + } +}) + +/** + * 可拖拽模态框 + */ +Vue.directive('drag-modal', (el, bindings, vnode) => { + Vue.nextTick(() => { + const { + visible, + destroyOnClose + } = vnode.componentInstance + // 防止未定义 destroyOnClose 关闭弹窗时dom未被销毁, 指令被重复调用 + if (!visible) return + const modal = el.getElementsByClassName('ant-modal')[0] + const draggable = el.getElementsByClassName('ant-modal-draggable')[0] + + let left = 0 + let top = 0 + // 未定义 destroyOnClose 时, dom未被销毁, 关闭弹窗再次打开, 弹窗会停留在上一次拖动的位置 + if (!destroyOnClose) { + left = modal.left || 0 + top = modal.top || 0 + } + // top 初始值为 offsetTop + top = top || modal.offsetTop + // 点击title部分拖动 + draggable.onmousedown = e => { + const startX = e.clientX + const startY = e.clientY + // draggable.left = draggable.offsetLeft + // draggable.top = draggable.offsetTop + el.onmousemove = event => { + const endX = event.clientX + const endY = event.clientY + // modal.left = draggable.left + (endX - startX) + left + // modal.top = draggable.top + (endY - startY) + top + modal.left = (endX - startX) + left + modal.top = (endY - startY) + top + modal.style.left = modal.left + 'px' + modal.style.top = modal.top + 'px' + } + el.onmouseup = event => { + left = modal.left + top = modal.top + el.onmousemove = null + el.onmouseup = null + draggable.releaseCapture && draggable.releaseCapture() + } + draggable.setCapture && draggable.setCapture() + } + }) +}) + +function limitPattern(pattern) { + return { + inserted: function(el, binding, vnode) { + // input外层的div + const input = el.tagName === 'INPUT' ? el : el.querySelector('input') + input.addEventListener('keyup', function() { + input.value = input.value.replace(pattern || binding.value, '') + // 手动触发数据的双向绑定 + vnode.context.$nextTick(() => { + if (vnode.componentInstance) { + vnode.componentInstance.stateValue = input.value + } else { + vnode.elm.dispatchEvent(new CustomEvent('input', input.value)) + } + }) + }) + } + } +} diff --git a/orion-ops-vue/src/lib/enum.js b/orion-ops-vue/src/lib/enum.js new file mode 100644 index 0000000..d64c6bf --- /dev/null +++ b/orion-ops-vue/src/lib/enum.js @@ -0,0 +1,2562 @@ +/** + * 用 value 获取枚举值 + */ +export function enumValueOf(e, value) { + for (const key in e) { + const val = e[key] + if (val && val.value === value) { + return val + } + } + return {} +} + +/** + * 终端操作 client 端 + */ +export const TERMINAL_CLIENT_OPERATOR = { + KEY: { + value: '0' + }, + CONNECT: { + value: '1' + }, + PING: { + value: '2' + }, + PONG: { + value: '3' + }, + RESIZE: { + value: '4' + }, + COMMAND: { + value: '5' + }, + CLEAR: { + value: '6' + } +} + +/** + * 终端状态 + */ +export const TERMINAL_STATUS = { + NOT_CONNECT: { + value: 0, + label: '未连接', + color: '#FFD43B' + }, + CONNECTED: { + value: 20, + label: '已连接', + color: '#4DABF7' + }, + DISCONNECTED: { + value: 30, + label: '已断开', + color: '#ADB5BD' + }, + ERROR: { + value: 40, + label: '错误', + color: '#E03131' + } +} + +/** + * WS protocol + */ +export const WS_PROTOCOL = { + OK: { + value: '0' + }, + CONNECTED: { + value: '1' + }, + PING: { + value: '2' + }, + PONG: { + value: '3' + }, + ERROR: { + value: '4' + } +} + +/** + * 文件类型 + */ +export const FILE_TYPE = { + NORMAL_FILE: { + value: '-', + label: '普通文件', + icon: 'file' + }, + DIRECTORY: { + value: 'd', + label: '目录', + icon: 'folder' + }, + LINK_FILE: { + value: 'l', + label: '链接文件', + icon: 'link' + }, + MANAGE_FILE: { + value: 'p', + label: '管理文件', + icon: 'dhh' + }, + BLOCK_DEVICE_FILE: { + value: 'b', + label: '块设备文件', + icon: 'dhh' + }, + CHARACTER_DEVICE_FILE: { + value: 'c', + label: '字符设备文件', + icon: 'dhh' + }, + SOCKET_FILE: { + value: 's', + label: '套接字文件', + icon: 'dhh' + } +} + +/** + * 启用状态 + */ +export const ENABLE_STATUS = { + ENABLE: { + value: 1, + label: '启用', + status: 'processing' + }, + DISABLE: { + value: 2, + label: '停用', + status: 'default' + } +} + +/** + * 启用状态 + */ +export const AUDIT_STATUS = { + RESOLVE: { + value: 10, + label: '通过' + }, + REJECT: { + value: 20, + label: '驳回' + } +} + +/** + * 应用环境审核状态 + */ +export const PROFILE_AUDIT_STATUS = { + ENABLE: { + value: 1, + label: '需要审核' + }, + DISABLE: { + value: 2, + label: '无需审核' + } +} + +/** + * 机器认证类型 + */ +export const MACHINE_AUTH_TYPE = { + PASSWORD: { + value: 1, + label: '密码认证' + }, + SECRET_KEY: { + value: 2, + label: '独立密钥' + } +} + +/** + * 机器代理方式 + */ +export const MACHINE_PROXY_TYPE = { + HTTP: { + value: 1, + label: 'http', + color: '#228BE6' + }, + SOCKS4: { + value: 2, + label: 'socks4', + color: '#1098AD' + }, + SOCKS5: { + value: 3, + label: 'socks5', + color: '#0CA678' + } +} + +/** + * 历史值类型 + */ +export const HISTORY_VALUE_TYPE = { + MACHINE_ENV: { + value: 10, + label: '机器环境变量' + }, + APP_ENV: { + value: 20, + label: '应用环境变量' + }, + SYSTEM_ENV: { + value: 30, + label: '系统环境变量' + } +} + +/** + * 历史值操作类型 + */ +export const HISTORY_VALUE_OPTION_TYPE = { + INSERT: { + value: 1, + label: '新增', + color: '#94D82D' + }, + UPDATE: { + value: 2, + label: '修改', + color: '#4C6EF5' + }, + DELETE: { + value: 3, + label: '删除', + color: '#FF922B' + } +} + +/** + * sftp 传输类型 + */ +export const SFTP_TRANSFER_TYPE = { + UPLOAD: { + value: 10, + label: '上传' + }, + DOWNLOAD: { + value: 20, + label: '下载' + }, + TRANSFER: { + value: 30, + label: '传输' + }, + PACKAGE: { + value: 40, + label: '打包' + } +} + +/** + * sftp 传输状态 + */ +export const SFTP_TRANSFER_STATUS = { + WAIT: { + value: 10, + label: '等待中', + color: '#FF922B' + }, + RUNNABLE: { + value: 20, + label: '进行中', + color: '#5C7CFA' + }, + PAUSE: { + value: 30, + label: '已暂停', + color: '#CED4DA' + }, + FINISH: { + value: 40, + label: '已完成', + color: '#51CF66' + }, + CANCEL: { + value: 50, + label: '已取消', + color: '#868E96' + }, + ERROR: { + value: 60, + label: '失败', + color: '#F03E3E' + } +} + +/** + * 批量执行 + */ +export const BATCH_EXEC_STATUS = { + WAITING: { + value: 10, + label: '未开始', + color: '' + }, + RUNNABLE: { + value: 20, + label: '执行中', + color: 'green' + }, + COMPLETE: { + value: 30, + label: '已完成', + color: 'blue' + }, + EXCEPTION: { + value: 40, + label: '异常', + color: 'red' + }, + TERMINATED: { + value: 50, + label: '已停止', + color: 'orange' + } +} + +/** + * 文件 tail 模式 + */ +export const FILE_TAIL_MODE = { + TRACKER: { + value: 'tracker', + label: 'tracker', + tips: '本地文件推荐选择 (IO)' + }, + TAIL: { + value: 'tail', + label: 'tail', + tips: '远程文件必须选择 (命令)' + } +} + +/** + * 文件 tail 类型 + */ +export const FILE_TAIL_TYPE = { + /** + * 命令执行日志 + */ + EXEC_LOG: { + value: 10 + }, + /** + * tail列表 + */ + TAIL_LIST: { + value: 20 + }, + /** + * 应用构建日志 + */ + APP_BUILD_LOG: { + value: 30 + }, + /** + * 应用发布日志 + */ + APP_RELEASE_LOG: { + value: 40 + }, + /** + * 调度任务机器日志 + */ + SCHEDULER_TASK_MACHINE_LOG: { + value: 50 + }, + /** + * 应用操作日志 + */ + APP_ACTION_LOG: { + value: 60 + } +} + +/** + * 日志 tail 状态 + */ +export const LOG_TAIL_STATUS = { + WAITING: { + value: 10, + label: '等待', + status: 'warning' + }, + RUNNABLE: { + value: 20, + label: '正常', + status: 'processing' + }, + ERROR: { + value: 30, + label: '错误', + status: 'error' + }, + CLOSE: { + value: 40, + label: '关闭', + status: 'default' + } +} + +/** + * 文件下载类型 + */ +export const FILE_DOWNLOAD_TYPE = { + /** + * 密钥 + */ + SECRET_KEY: { + value: 10 + }, + /** + * 终端录屏 + */ + TERMINAL_SCREEN: { + value: 20 + }, + /** + * 命令执行日志 + */ + EXEC_LOG: { + value: 30 + }, + /** + * sftp下载文件 + */ + SFTP_DOWNLOAD: { + value: 40 + }, + /** + * tail列表文件 + */ + TAIL_LIST_FILE: { + value: 50 + }, + /** + * 应用构建日志 + */ + APP_BUILD_LOG: { + value: 60 + }, + /** + * 应用操作日志 + */ + APP_ACTION_LOG: { + value: 70 + }, + /** + * 应用构建产物文件 + */ + APP_BUILD_BUNDLE: { + value: 80 + }, + /** + * 应用发布机器日志 + */ + APP_RELEASE_MACHINE_LOG: { + value: 90 + }, + /** + * 调度任务机器日志 + */ + SCHEDULER_TASK_MACHINE_LOG: { + value: 110 + } +} + +/** + * 角色类型 + */ +export const ROLE_TYPE = { + ADMINISTRATOR: { + value: 10, + label: '管理员' + }, + DEVELOPER: { + value: 20, + label: '开发' + }, + OPERATION: { + value: 30, + label: '运维' + } +} + +/** + * 视图类型 + */ +export const VIEW_TYPE = { + TABLE: { + value: 0, + name: '表格', + lang: null + }, + JSON: { + value: 10, + name: 'json', + lang: 'json' + }, + XML: { + value: 20, + name: 'xml', + lang: 'xml' + }, + YML: { + value: 30, + name: 'yml', + lang: 'yaml' + }, + PROPERTIES: { + value: 40, + name: 'properties', + lang: 'properties' + } +} + +/** + * 仓库状态 + */ +export const REPOSITORY_STATUS = { + UNINITIALIZED: { + value: 10, + label: '未初始化', + color: '' + }, + INITIALIZING: { + value: 20, + label: '未初始中', + color: 'green' + }, + OK: { + value: 30, + label: '正常', + color: 'blue' + }, + ERROR: { + value: 40, + label: '失败', + color: 'red' + } +} + +/** + * 仓库认证方式 + */ +export const REPOSITORY_AUTH_TYPE = { + PASSWORD: { + value: 10, + label: '密码认证' + }, + TOKEN: { + value: 20, + label: '私人令牌' + } +} + +/** + * 仓库令牌类型 + */ +export const REPOSITORY_TOKEN_TYPE = { + GITHUB: { + value: 10, + label: 'github', + description: 'Settings -> Developer settings -> Personal access tokens' + }, + GITEE: { + value: 20, + label: 'gitee', + description: '设置 -> 私人令牌' + }, + GITLAB: { + value: 30, + label: 'gitlab', + description: 'User Settings -> Access Tokens' + } +} + +/** + * 配置类型 + */ +export const CONFIG_STATUS = { + CONFIGURED: { + value: 1, + label: '已配置', + color: 'blue' + }, + NOT_CONFIGURED: { + value: 2, + label: '未配置', + color: 'green' + } +} + +/** + * 构建操作类型 + */ +export const BUILD_ACTION_TYPE = { + CHECKOUT: { + value: 110, + label: '检出' + }, + COMMAND: { + value: 120, + label: '主机命令' + } +} + +/** + * 发布操作类型 + */ +export const RELEASE_ACTION_TYPE = { + TRANSFER: { + value: 210, + label: '传输' + }, + COMMAND: { + value: 220, + label: '目标主机命令' + } +} + +/** + * 序列类型 + */ +export const SERIAL_TYPE = { + SERIAL: { + value: 10, + label: '串行' + }, + PARALLEL: { + value: 20, + label: '并行' + } +} + +/** + * 发布产物传输方式 + */ +export const RELEASE_TRANSFER_MODE = { + SCP: { + value: 'scp', + label: 'SCP' + }, + SFTP: { + value: 'sftp', + label: 'SFTP' + } +} + +/** + * 发布产物传输类型 + */ +export const RELEASE_TRANSFER_FILE_TYPE = { + NORMAL: { + value: 'normal', + label: '文件 / 文件夹' + }, + ZIP: { + value: 'zip', + label: '文件夹zip' + } +} + +/** + * 构建状态 + */ +export const BUILD_STATUS = { + WAIT: { + value: 10, + color: '', + label: '未开始', + stepStatus: 'wait' + }, + RUNNABLE: { + value: 20, + color: 'green', + label: '进行中', + stepStatus: 'process' + }, + FINISH: { + value: 30, + color: 'blue', + label: '已完成', + stepStatus: 'finish' + }, + FAILURE: { + value: 40, + color: 'red', + label: '已失败', + stepStatus: 'error' + }, + TERMINATED: { + value: 50, + color: 'orange', + label: '已停止', + stepStatus: 'finish' + } +} + +/** + * 操作状态 + */ +export const ACTION_STATUS = { + WAIT: { + value: 10, + color: '', + label: '未开始', + stepStatus: 'wait', + log: false, + actionStyle: { + background: '#CED4DA' + }, + actionValue() { + return '未开始' + } + }, + RUNNABLE: { + value: 20, + color: 'green', + label: '进行中', + stepStatus: 'process', + log: true, + actionStyle: { + background: '#94D82D' + }, + actionValue() { + return '进行中' + } + }, + FINISH: { + value: 30, + color: 'blue', + label: '已完成', + stepStatus: 'finish', + log: true, + actionStyle: { + background: '#74C0FC' + }, + actionValue(log) { + return log.usedInterval || '已完成' + } + }, + FAILURE: { + value: 40, + color: 'red', + label: '已失败', + stepStatus: 'error', + log: true, + actionStyle: { + background: '#F03E3E' + }, + actionValue(log) { + return log.usedInterval || '已失败' + } + }, + SKIPPED: { + value: 50, + color: 'orange', + label: '已跳过', + stepStatus: 'wait', + log: false, + actionStyle: { + background: '#FFD43B' + }, + actionValue() { + return '已跳过' + } + }, + TERMINATED: { + value: 60, + color: 'orange', + label: '已停止', + stepStatus: 'wait', + log: true, + actionStyle: { + background: '#FFA94D' + }, + actionValue() { + return '已停止' + } + } +} + +/** + * 发布状态 + */ +export const RELEASE_STATUS = { + WAIT_AUDIT: { + value: 10, + color: '', + label: '待审核' + }, + AUDIT_REJECT: { + value: 20, + color: 'orange', + label: '已驳回' + }, + WAIT_RUNNABLE: { + value: 30, + color: 'cyan', + label: '待发布' + }, + WAIT_SCHEDULE: { + value: 35, + color: 'cyan', + label: '待调度' + }, + RUNNABLE: { + value: 40, + color: 'green', + label: '发布中' + }, + FINISH: { + value: 50, + color: 'blue', + label: '已完成' + }, + TERMINATED: { + value: 60, + color: 'orange', + label: '已停止' + }, + FAILURE: { + value: 70, + color: 'red', + label: '已失败' + } +} + +/** + * 发布类型 + */ +export const RELEASE_TYPE = { + NORMAL: { + value: 10, + label: '正常发布' + }, + ROLLBACK: { + value: 20, + label: '回滚发布' + } +} + +/** + * 发布状态 + */ +export const TIMED_TYPE = { + NORMAL: { + value: 10, + releaseLabel: '普通发布', + execLabel: '普通执行' + }, + TIMED: { + value: 20, + releaseLabel: '定时发布', + execLabel: '定时执行' + } +} + +/** + * 操作阶段类型 + */ +export const STAGE_TYPE = { + BUILD: { + value: 10, + symbol: 'build', + label: '构建' + }, + RELEASE: { + value: 20, + symbol: 'release', + label: '发布' + } +} + +/** + * 操作日志分类 + */ +export const EVENT_CLASSIFY = { + AUTHENTICATION: { + value: 100, + label: '认证操作' + }, + USER: { + value: 110, + label: '用户操作' + }, + ALARM_GROUP: { + value: 120, + label: '报警组操作' + }, + MACHINE: { + value: 200, + label: '机器操作' + }, + MACHINE_ENV: { + value: 210, + label: '机器环境变量操作' + }, + MACHINE_KEY: { + value: 220, + label: '密钥操作' + }, + MACHINE_PROXY: { + value: 230, + label: '代理操作' + }, + MACHINE_MONITOR: { + value: 240, + label: '机器监控' + }, + MACHINE_ALARM: { + value: 250, + label: '机器报警' + }, + TERMINAL: { + value: 260, + label: '终端操作' + }, + SFTP: { + value: 270, + label: 'sftp 操作' + }, + EXEC: { + value: 300, + label: '批量执行操作' + }, + TAIL: { + value: 310, + label: '日志追踪操作' + }, + SCHEDULER: { + value: 320, + label: '调度操作' + }, + APP: { + value: 400, + label: '应用操作' + }, + PROFILE: { + value: 410, + label: '环境操作' + }, + APP_ENV: { + value: 420, + label: '应用环境变量操作' + }, + REPOSITORY: { + value: 430, + label: '应用仓库操作' + }, + BUILD: { + value: 440, + label: '应用构建操作' + }, + RELEASE: { + value: 450, + label: '应用发布操作' + }, + PIPELINE: { + value: 460, + label: '应用流水线' + }, + TEMPLATE: { + value: 500, + label: '模板操作' + }, + WEBHOOK: { + value: 510, + label: 'webhook操作' + }, + SYSTEM: { + value: 600, + label: '系统操作' + }, + SYSTEM_ENV: { + value: 610, + label: '系统环境变量操作' + }, + DATA_CLEAR: { + value: 620, + label: '数据清理' + }, + DATA_IMPORT: { + value: 630, + label: '数据导入' + }, + DATA_EXPORT: { + value: 640, + label: '数据导出' + } +} + +/** + * 操作日志类型 + */ +export const EVENT_TYPE = { + LOGIN: { + value: 100010, + label: '登录系统', + classify: 100 + }, + LOGOUT: { + value: 100020, + label: '退出系统', + classify: 100 + }, + RESET_PASSWORD: { + value: 100030, + label: '重置密码', + classify: 100 + }, + ADD_USER: { + value: 110010, + label: '添加用户', + classify: 110 + }, + UPDATE_USER: { + value: 110020, + label: '修改用户信息', + classify: 110 + }, + DELETE_USER: { + value: 110030, + label: '删除用户', + classify: 110 + }, + CHANGE_USER_STATUS: { + value: 110040, + label: '修改用户状态', + classify: 110 + }, + UNLOCK_USER: { + value: 110050, + label: '解锁用户', + classify: 110 + }, + ADD_ALARM_GROUP: { + value: 120010, + label: '添加报警组', + classify: 120 + }, + UPDATE_ALARM_GROUP: { + value: 120020, + label: '修改报警组', + classify: 120 + }, + DELETE_ALARM_GROUP: { + value: 120030, + label: '删除报警组', + classify: 120 + }, + ADD_MACHINE: { + value: 200010, + label: '添加机器', + classify: 200 + }, + UPDATE_MACHINE: { + value: 200020, + label: '修改机器', + classify: 200 + }, + DELETE_MACHINE: { + value: 200030, + label: '删除机器', + classify: 200 + }, + CHANGE_MACHINE_STATUS: { + value: 200040, + label: '修改机器状态', + classify: 200 + }, + COPY_MACHINE: { + value: 200050, + label: '复制机器', + classify: 200 + }, + DELETE_MACHINE_ENV: { + value: 210010, + label: '删除机器环境变量', + classify: 210 + }, + SYNC_MACHINE_ENV: { + value: 210020, + label: '同步机器环境变量', + classify: 210 + }, + ADD_MACHINE_KEY: { + value: 220010, + label: '新增密钥', + classify: 220 + }, + UPDATE_MACHINE_KEY: { + value: 220020, + label: '修改密钥', + classify: 220 + }, + DELETE_MACHINE_KEY: { + value: 220030, + label: '删除密钥', + classify: 220 + }, + BIND_MACHINE_KEY: { + value: 220040, + label: '绑定密钥', + classify: 220 + }, + ADD_MACHINE_PROXY: { + value: 230010, + label: '新建代理', + classify: 230 + }, + UPDATE_MACHINE_PROXY: { + value: 230020, + label: '修改代理', + classify: 230 + }, + DELETE_MACHINE_PROXY: { + value: 230030, + label: '删除代理', + classify: 230 + }, + UPDATE_MACHINE_MONITOR_CONFIG: { + value: 240010, + label: '修改配置', + classify: 240 + }, + INSTALL_UPGRADE_MACHINE_MONITOR: { + value: 240020, + label: '安装/升级插件', + classify: 240 + }, + SET_MACHINE_ALARM_CONFIG: { + value: 250010, + label: '修改报警配置', + classify: 250 + }, + SET_MACHINE_ALARM_GROUP: { + value: 250020, + label: '修改报警联系组', + classify: 250 + }, + RENOTIFY_MACHINE_ALARM_GROUP: { + value: 250030, + label: '重新发送报警通知', + classify: 250 + }, + OPEN_TERMINAL: { + value: 260010, + label: '打开机器终端', + classify: 260 + }, + FORCE_OFFLINE_TERMINAL: { + value: 260020, + label: '强制下线终端', + classify: 260 + }, + UPDATE_TERMINAL_CONFIG: { + value: 260030, + label: '修改终端配置', + classify: 260 + }, + DELETE_TERMINAL_LOG: { + value: 260040, + label: '删除终端日志', + classify: 260 + }, + OPEN_SFTP: { + value: 270010, + label: '打开机器 SFTP', + classify: 270 + }, + SFTP_MKDIR: { + value: 270020, + label: '创建文件夹', + classify: 270 + }, + SFTP_TOUCH: { + value: 270030, + label: '创建文件', + classify: 270 + }, + SFTP_TRUNCATE: { + value: 270040, + label: '截断文件', + classify: 270 + }, + SFTP_MOVE: { + value: 270050, + label: '移动文件', + classify: 270 + }, + SFTP_REMOVE: { + value: 270060, + label: '删除文件', + classify: 270 + }, + SFTP_CHMOD: { + value: 270070, + label: '修改文件权限', + classify: 270 + }, + SFTP_CHOWN: { + value: 270080, + label: '修改文件所有者', + classify: 270 + }, + SFTP_CHGRP: { + value: 270090, + label: '修改文件所有组', + classify: 270 + }, + SFTP_UPLOAD: { + value: 270100, + label: '上传文件', + classify: 270 + }, + SFTP_DOWNLOAD: { + value: 270110, + label: '下载文件', + classify: 270 + }, + SFTP_PACKAGE: { + value: 270120, + label: '打包文件', + classify: 270 + }, + EXEC_SUBMIT: { + value: 300010, + label: '批量执行', + classify: 300 + }, + EXEC_DELETE: { + value: 300020, + label: '删除执行', + classify: 300 + }, + EXEC_TERMINATE: { + value: 300030, + label: '终止执行', + classify: 300 + }, + ADD_TAIL_FILE: { + value: 310010, + label: '添加日志文件', + classify: 310 + }, + UPDATE_TAIL_FILE: { + value: 310020, + label: '修改日志文件', + classify: 310 + }, + DELETE_TAIL_FILE: { + value: 310030, + label: '删除日志文件', + classify: 310 + }, + UPLOAD_TAIL_FILE: { + value: 310040, + label: '上传日志文件', + classify: 310 + }, + ADD_SCHEDULER_TASK: { + value: 320010, + label: '添加调度任务', + classify: 320 + }, + UPDATE_SCHEDULER_TASK: { + value: 320020, + label: '修改调度任务', + classify: 320 + }, + UPDATE_SCHEDULER_TASK_STATUS: { + value: 320030, + label: '更新任务状态', + classify: 320 + }, + DELETE_SCHEDULER_TASK: { + value: 320040, + label: '删除调度任务', + classify: 320 + }, + MANUAL_TRIGGER_SCHEDULER_TASK: { + value: 320050, + label: '手动触发任务', + classify: 320 + }, + TERMINATE_ALL_SCHEDULER_TASK: { + value: 320060, + label: '停止任务', + classify: 320 + }, + TERMINATE_SCHEDULER_TASK_MACHINE: { + value: 320070, + label: '停止机器操作', + classify: 320 + }, + SKIP_SCHEDULER_TASK_MACHINE: { + value: 320080, + label: '跳过机器操作', + classify: 320 + }, + DELETE_TASK_RECORD: { + value: 320090, + label: '删除调度明细', + classify: 320 + }, + ADD_APP: { + value: 400010, + label: '添加应用', + classify: 400 + }, + UPDATE_APP: { + value: 400020, + label: '修改应用', + classify: 400 + }, + DELETE_APP: { + value: 400030, + label: '删除应用', + classify: 400 + }, + CONFIG_APP: { + value: 400040, + label: '配置应用', + classify: 400 + }, + SYNC_APP: { + value: 400050, + label: '同步应用', + classify: 400 + }, + COPY_APP: { + value: 400060, + label: '复制应用', + classify: 400 + }, + ADD_PROFILE: { + value: 410010, + label: '添加应用环境', + classify: 410 + }, + UPDATE_PROFILE: { + value: 410020, + label: '修改应用环境', + classify: 410 + }, + DELETE_PROFILE: { + value: 410030, + label: '删除应用环境', + classify: 410 + }, + DELETE_APP_ENV: { + value: 420010, + label: '删除应用环境变量', + classify: 420 + }, + SYNC_APP_ENV: { + value: 420020, + label: '同步应用环境变量', + classify: 420 + }, + ADD_REPOSITORY: { + value: 430010, + label: '添加版本仓库', + classify: 430 + }, + INIT_REPOSITORY: { + value: 430020, + label: '初始化版本仓库', + classify: 430 + }, + RE_INIT_REPOSITORY: { + value: 430030, + label: '重新初始化版本仓库', + classify: 430 + }, + UPDATE_REPOSITORY: { + value: 430040, + label: '更新版本仓库', + classify: 430 + }, + DELETE_REPOSITORY: { + value: 430050, + label: '删除版本仓库', + classify: 430 + }, + CLEAN_REPOSITORY: { + value: 430060, + label: '清空版本仓库', + classify: 430 + }, + SUBMIT_BUILD: { + value: 440010, + label: '提交应用构建', + classify: 440 + }, + BUILD_TERMINATE: { + value: 440020, + label: '停止应用构建', + classify: 440 + }, + DELETE_BUILD: { + value: 440030, + label: '删除应用构建', + classify: 440 + }, + SUBMIT_REBUILD: { + value: 440040, + label: '重新构建应用', + classify: 440 + }, + SUBMIT_RELEASE: { + value: 450010, + label: '提交应用发布', + classify: 450 + }, + AUDIT_RELEASE: { + value: 450020, + label: '应用发布审核', + classify: 450 + }, + RUNNABLE_RELEASE: { + value: 450030, + label: '执行应用发布', + classify: 450 + }, + ROLLBACK_RELEASE: { + value: 450040, + label: '应用回滚发布', + classify: 450 + }, + TERMINATE_RELEASE: { + value: 450050, + label: '停止应用发布', + classify: 450 + }, + DELETE_RELEASE: { + value: 450060, + label: '删除应用发布', + classify: 450 + }, + COPY_RELEASE: { + value: 450070, + label: '复制应用发布', + classify: 450 + }, + CANCEL_TIMED_RELEASE: { + value: 450080, + label: '取消定时发布', + classify: 450 + }, + SET_TIMED_RELEASE: { + value: 450090, + label: '设置定时发布', + classify: 450 + }, + TERMINATE_MACHINE_RELEASE: { + value: 450100, + label: '停止机器操作', + classify: 450 + }, + SKIP_MACHINE_RELEASE: { + value: 450110, + label: '跳过机器操作', + classify: 450 + }, + ADD_PIPELINE: { + value: 460010, + label: '添加流水线', + classify: 460 + }, + UPDATE_PIPELINE: { + value: 460020, + label: '修改流水线', + classify: 460 + }, + DELETE_PIPELINE: { + value: 460030, + label: '删除流水线', + classify: 460 + }, + SUBMIT_PIPELINE_TASK: { + value: 460040, + label: '提交执行任务', + classify: 460 + }, + AUDIT_PIPELINE_TASK: { + value: 460050, + label: '审核任务', + classify: 460 + }, + COPY_PIPELINE_TASK: { + value: 460060, + label: '复制任务', + classify: 460 + }, + EXEC_PIPELINE_TASK: { + value: 460070, + label: '执行任务', + classify: 460 + }, + DELETE_PIPELINE_TASK: { + value: 460080, + label: '删除任务', + classify: 460 + }, + SET_PIPELINE_TIMED_TASK: { + value: 460090, + label: '设置定时执行', + classify: 460 + }, + CANCEL_PIPELINE_TIMED_TASK: { + value: 460100, + label: '取消定时执行', + classify: 460 + }, + TERMINATE_PIPELINE_TASK: { + value: 460110, + label: '停止执行任务', + classify: 460 + }, + TERMINATE_PIPELINE_TASK_DETAIL: { + value: 460120, + label: '停止执行操作', + classify: 460 + }, + SKIP_PIPELINE_TASK_DETAIL: { + value: 460130, + label: '跳过执行操作', + classify: 460 + }, + ADD_TEMPLATE: { + value: 500010, + label: '添加模板', + classify: 500 + }, + UPDATE_TEMPLATE: { + value: 500020, + label: '修改模板', + classify: 500 + }, + DELETE_TEMPLATE: { + value: 500030, + label: '删除模板', + classify: 500 + }, + ADD_WEBHOOK: { + value: 510010, + label: '添加配置', + classify: 510 + }, + UPDATE_WEBHOOK: { + value: 510020, + label: '修改配置', + classify: 510 + }, + DELETE_WEBHOOK: { + value: 510030, + label: '删除配置', + classify: 510 + }, + CONFIG_IP_LIST: { + value: 600010, + label: '配置IP过滤器', + classify: 600 + }, + RE_ANALYSIS_SYSTEM: { + value: 600020, + label: '系统统计分析', + classify: 600 + }, + CLEAN_SYSTEM_FILE: { + value: 600030, + label: '清理系统文件', + classify: 600 + }, + UPDATE_SYSTEM_OPTION: { + value: 600040, + label: '修改系统配置', + classify: 600 + }, + ADD_SYSTEM_ENV: { + value: 610010, + label: '添加系统环境变量', + classify: 610 + }, + UPDATE_SYSTEM_ENV: { + value: 610020, + label: '修改系统环境变量', + classify: 610 + }, + DELETE_SYSTEM_ENV: { + value: 610030, + label: '删除系统环境变量', + classify: 610 + }, + SAVE_SYSTEM_ENV: { + value: 610040, + label: '保存系统环境变量', + classify: 610 + }, + DATA_CLEAR: { + value: 620010, + label: '清理数据', + classify: 620 + }, + DATA_IMPORT: { + value: 630010, + label: '导入数据', + classify: 630 + }, + DATA_EXPORT: { + value: 640010, + label: '导出数据', + classify: 640 + } +} + +/** + * 系统清理类型 + */ +export const SYSTEM_CLEAR_TYPE = { + TEMP_FILE: { + key: 'tempFile', + value: 10, + label: '临时文件' + }, + LOG_FILE: { + key: 'logFile', + value: 20, + label: '日志文件' + }, + SWAP_FILE: { + key: 'swapFile', + value: 30, + label: '交换区文件' + }, + DIST_FILE: { + key: 'distFile', + value: 40, + label: '旧版本构建产物' + }, + REPO_FILE: { + key: 'repoFile', + value: 50, + label: '旧版本应用仓库' + }, + SCREEN_FILE: { + key: 'screenFile', + value: 60, + label: '录屏文件' + } +} + +/** + * 系统配置项 + */ +export const SYSTEM_OPTION_KEY = { + FILE_CLEAN_THRESHOLD: { + key: 'fileCleanThreshold', + value: 10 + }, + ENABLE_AUTO_CLEAN_FILE: { + key: 'autoCleanFile', + value: 20 + }, + ALLOW_MULTIPLE_LOGIN: { + key: 'allowMultipleLogin', + value: 30 + }, + LOGIN_FAILURE_LOCK: { + key: 'loginFailureLock', + value: 40 + }, + LOGIN_IP_BIND: { + key: 'loginIpBind', + value: 50 + }, + LOGIN_TOKEN_AUTO_RENEW: { + key: 'loginTokenAutoRenew', + value: 60 + }, + LOGIN_TOKEN_EXPIRE: { + key: 'loginTokenExpire', + value: 70 + }, + LOGIN_FAILURE_LOCK_THRESHOLD: { + key: 'loginFailureLockThreshold', + value: 80 + }, + LOGIN_TOKEN_AUTO_RENEW_THRESHOLD: { + key: 'loginTokenAutoRenewThreshold', + value: 90 + }, + RESUME_ENABLE_SCHEDULER_TASK: { + key: 'resumeEnableSchedulerTask', + value: 100 + }, + SFTP_UPLOAD_THRESHOLD: { + key: 'sftpUploadThreshold', + value: 110 + }, + STATISTICS_CACHE_EXPIRE: { + key: 'statisticsCacheExpire', + value: 120 + }, + TERMINAL_ACTIVE_PUSH_HEARTBEAT: { + key: 'terminalActivePushHeartbeat', + value: 130 + } +} + +/** + * 调度任务执行状态 + */ +export const SCHEDULER_TASK_STATUS = { + WAIT: { + value: 10, + label: '待调度', + color: '' + }, + RUNNABLE: { + value: 20, + label: '执行中', + color: 'green' + }, + SUCCESS: { + value: 30, + label: '成功', + color: 'blue' + }, + FAILURE: { + value: 40, + label: '失败', + color: 'red' + }, + TERMINATED: { + value: 50, + label: '已停止', + color: 'orange' + } +} + +/** + * 调度任务执行机器状态 + */ +export const SCHEDULER_TASK_MACHINE_STATUS = { + WAIT: { + value: 10, + label: '待调度', + color: '' + }, + RUNNABLE: { + value: 20, + label: '执行中', + color: 'green' + }, + SUCCESS: { + value: 30, + label: '成功', + color: 'blue' + }, + FAILURE: { + value: 40, + label: '失败', + color: 'red' + }, + SKIPPED: { + value: 50, + label: '已跳过', + color: 'orange' + }, + TERMINATED: { + value: 60, + label: '已停止', + color: 'orange' + } +} + +/** + * 异常处理类型 + */ +export const EXCEPTION_HANDLER_TYPE = { + SKIP_ALL: { + value: 10, + label: '跳过所有', + title: '跳过所有项, 中断执行' + }, + SKIP_ERROR: { + value: 20, + label: '跳过错误', + title: '跳过错误项, 继续执行' + } +} + +/** + * 线程池指标类型 + */ +export const THREAD_POOL_METRICS_TYPE = { + TERMINAL: { + value: 10, + label: '远程终端线程池' + }, + TERMINAL_WATCHER: { + value: 15, + label: '终端监视线程池' + }, + EXEC: { + value: 20, + label: '批量执行线程池' + }, + TAIL: { + value: 30, + label: '文件追踪线程池' + }, + SFTP_TRANSFER_RATE: { + value: 40, + label: '传输进度线程池' + }, + SFTP_UPLOAD: { + value: 50, + label: '文件上传线程池' + }, + SFTP_DOWNLOAD: { + value: 60, + label: '文件下载线程池' + }, + SFTP_PACKAGE: { + value: 70, + label: '文件打包线程池' + }, + APP_BUILD: { + value: 80, + label: '应用构建线程池' + }, + RELEASE_MAIN: { + value: 90, + label: '应用发布主线程池' + }, + RELEASE_MACHINE: { + value: 100, + label: '应用发布机器线程池' + }, + SCHEDULER_TASK_MAIN: { + value: 110, + label: '调度任务主线程池' + }, + SCHEDULER_TASK_MACHINE: { + value: 120, + label: '调度任务机器线程池' + }, + PIPELINE: { + value: 130, + label: '应用流水线线程池' + } +} + +/** + * 阅读状态 + */ +export const READ_STATUS = { + UNREAD: { + value: 1, + label: '未读' + }, + READ: { + value: 2, + label: '已读' + } +} + +/** + * 消息分类 + */ +export const MESSAGE_CLASSIFY = { + SYSTEM: { + value: 10, + label: '系统消息', + icon: 'setting' + }, + IMPORT: { + value: 20, + label: '数据导入', + icon: 'import' + }, + ALARM: { + value: 30, + label: '系统报警', + icon: 'thunderbolt' + } +} + +/** + * 消息类型 + */ +export const MESSAGE_TYPE = { + EXEC_SUCCESS: { + classify: 10, + value: 1010, + label: '命令执行完成', + notify: 'success', + redirect: row => `/batch/exec/list?id=${row.relId || ''}` + }, + EXEC_FAILURE: { + classify: 10, + value: 1020, + label: '命令执行失败', + notify: 'error', + duration: null, + redirect: row => `/batch/exec/list?id=${row.relId || ''}` + }, + REPOSITORY_INIT_SUCCESS: { + classify: 10, + value: 1030, + label: '版本仓库初始化成功', + notify: 'success', + redirect: row => `/app/repo?id=${row.relId || ''}` + }, + REPOSITORY_INIT_FAILURE: { + classify: 10, + value: 1040, + label: '版本仓库初始化失败', + notify: 'error', + duration: null, + redirect: row => `/app/repo?id=${row.relId || ''}` + }, + BUILD_SUCCESS: { + classify: 10, + value: 1050, + label: '构建执行成功', + notify: 'success', + redirect: row => `/app/build/list?id=${row.relId || ''}` + }, + BUILD_FAILURE: { + classify: 10, + value: 1060, + label: '构建执行失败', + notify: 'error', + duration: null, + redirect: row => `/app/build/list?id=${row.relId || ''}` + }, + RELEASE_AUDIT_RESOLVE: { + classify: 10, + value: 1070, + label: '发布审批通过', + notify: 'success', + redirect: row => `/app/release/list?id=${row.relId || ''}` + }, + RELEASE_AUDIT_REJECT: { + classify: 10, + value: 1080, + label: '发布审批驳回', + notify: 'warning', + redirect: row => `/app/release/list?id=${row.relId || ''}` + }, + RELEASE_SUCCESS: { + classify: 10, + value: 1090, + label: '发布执行成功', + notify: 'success', + redirect: row => `/app/release/list?id=${row.relId || ''}` + }, + RELEASE_FAILURE: { + classify: 10, + value: 1100, + label: '发布执行失败', + notify: 'error', + duration: null, + redirect: row => `/app/release/list?id=${row.relId || ''}` + }, + PIPELINE_AUDIT_RESOLVE: { + classify: 10, + value: 1110, + label: '应用流水线审批通过', + notify: 'success', + redirect: row => `/app/pipeline/task?id=${row.relId || ''}` + }, + PIPELINE_AUDIT_REJECT: { + classify: 10, + value: 1120, + label: '应用流水线审批驳回', + notify: 'warning', + redirect: row => `/app/pipeline/task?id=${row.relId || ''}` + }, + PIPELINE_EXEC_SUCCESS: { + classify: 10, + value: 1130, + label: '流水线执行成功', + notify: 'success', + redirect: row => `/app/pipeline/task?id=${row.relId || ''}` + }, + PIPELINE_EXEC_FAILURE: { + classify: 10, + value: 1140, + label: '流水线执行失败', + notify: 'error', + duration: null, + redirect: row => `/app/pipeline/task?id=${row.relId || ''}` + }, + MACHINE_AGENT_INSTALL_SUCCESS: { + classify: 10, + value: 1150, + label: '机器监控插件安装成功', + notify: 'success', + redirect: row => `/machine/monitor/list?machineId=${row.relId || ''}` + }, + MACHINE_AGENT_INSTALL_FAILURE: { + classify: 10, + value: 1160, + label: '机器监控插件安装失败', + notify: 'error', + duration: null, + redirect: row => `/machine/monitor/list?machineId=${row.relId || ''}` + }, + DATA_IMPORT_SUCCESS: { + classify: 20, + value: 2010, + label: '数据导入成功', + notify: 'success', + redirect: row => { + if (!row.relId) { + return null + } + return enumValueOf(IMPORT_TYPE, row.relId).redirect + } + }, + DATA_IMPORT_FAILURE: { + classify: 20, + value: 2020, + label: '数据导入失败', + notify: 'error', + redirect: row => { + if (!row.relId) { + return null + } + return enumValueOf(IMPORT_TYPE, row.relId).redirect + } + }, + MACHINE_ALARM: { + classify: 30, + value: 3010, + label: '机器发生报警', + notify: 'error', + duration: null, + redirect: row => `/machine/monitor/metrics/${row.relId || ''}` + } +} + +/** + * 流水线状态 + */ +export const PIPELINE_STATUS = { + WAIT_AUDIT: { + value: 10, + color: '', + label: '待审核' + }, + AUDIT_REJECT: { + value: 20, + color: 'orange', + label: '已驳回' + }, + WAIT_RUNNABLE: { + value: 30, + color: 'cyan', + label: '待执行' + }, + WAIT_SCHEDULE: { + value: 35, + color: 'cyan', + label: '待调度' + }, + RUNNABLE: { + value: 40, + color: 'green', + label: '执行中' + }, + FINISH: { + value: 50, + color: 'blue', + label: '已完成' + }, + TERMINATED: { + value: 60, + color: 'orange', + label: '已停止' + }, + FAILURE: { + value: 70, + color: 'red', + label: '已失败' + } +} + +/** + * 流水线操作状态 + */ +export const PIPELINE_DETAIL_STATUS = { + WAIT: { + value: 10, + color: '', + label: '未开始', + log: false, + actionStyle: { + background: '#CED4DA' + }, + actionValue() { + return '未开始' + } + }, + RUNNABLE: { + value: 20, + color: 'green', + label: '进行中', + log: true, + actionStyle: { + background: '#94D82D' + }, + actionValue() { + return '进行中' + } + }, + FINISH: { + value: 30, + color: 'blue', + label: '已完成', + log: true, + actionStyle: { + background: '#74C0FC' + }, + actionValue(log) { + return log.usedInterval || '已完成' + } + }, + FAILURE: { + value: 40, + color: 'red', + label: '已失败', + log: true, + actionStyle: { + background: '#F03E3E' + }, + actionValue(log) { + return log.usedInterval || '已失败' + } + }, + SKIPPED: { + value: 50, + color: 'orange', + label: '已跳过', + log: false, + actionStyle: { + background: '#FFD43B' + }, + actionValue() { + return '已跳过' + } + }, + TERMINATED: { + value: 60, + color: 'orange', + label: '已停止', + log: true, + actionStyle: { + background: '#FFA94D' + }, + actionValue() { + return '已停止' + } + } +} + +/** + * 流水线日志状态 + */ +export const PIPELINE_LOG_STATUS = { + CREATE: { + value: 10, + label: '创建' + }, + EXEC: { + value: 20, + label: '执行' + }, + SUCCESS: { + value: 30, + label: '成功' + }, + FAILURE: { + value: 40, + label: '失败' + }, + TERMINATED: { + value: 50, + label: '停止' + }, + SKIPPED: { + value: 60, + label: '跳过' + } +} + +/** + * 批量上传状态 + */ +export const BATCH_UPLOAD_STATUS = { + WAIT: { + value: 10, + loading: false, + mask: false, + checked: false, + visibleTransfer: false, + message: '点击上传按钮开始上传', + type: 'info' + }, + CHECKING: { + value: 20, + loading: true, + mask: true, + checked: false, + visibleTransfer: false, + message: '正在检查文件...', + type: 'info' + }, + REQUESTING: { + value: 30, + loading: true, + mask: true, + checked: false, + visibleTransfer: false, + message: '正在请求上传...', + type: 'info' + }, + WAIT_UPLOAD: { + value: 40, + loading: true, + mask: false, + checked: true, + visibleTransfer: false, + message: '等待上传中...', + type: 'info' + }, + UPLOADING: { + value: 50, + loading: true, + mask: false, + checked: false, + visibleTransfer: true, + message: '文件上传中... 清空或页面关闭则在后台继续上传', + type: 'info' + }, + ERROR: { + value: 60, + loading: false, + mask: false, + checked: false, + visibleTransfer: false, + message: '文件上传失败, 请重试', + type: 'error' + }, + NO_AVAILABLE: { + value: 70, + loading: false, + mask: false, + checked: false, + visibleTransfer: false, + message: '无可用上传机器, 请重新选择后重试', + type: 'error' + } +} + +/** + * 数据清理区间 + */ +export const DATA_CLEAR_RANGE = { + DAY: { value: 10 }, + TOTAL: { value: 20 }, + REL_ID: { value: 30 } +} + +/** + * 数据导出类型 + */ +export const EXPORT_TYPE = { + MACHINE_INFO: { value: 100 }, + MACHINE_PROXY: { value: 110 }, + TERMINAL_LOG: { value: 120 }, + MACHINE_ALARM_HISTORY: { value: 130 }, + APP_PROFILE: { value: 200 }, + APPLICATION: { value: 210 }, + APP_REPOSITORY: { value: 220 }, + COMMAND_TEMPLATE: { value: 300 }, + USER_EVENT_LOG: { value: 310 }, + TAIL_FILE: { value: 320 }, + WEBHOOK: { value: 330 } +} + +/** + * 数据导入类型 + */ +export const IMPORT_TYPE = { + MACHINE_INFO: { + value: 100, + tips: '使用唯一标识来区分数据, 存在更新不存在新增, 优先使用导入密码', + title: '机器信息 导入', + redirect: '/machine/list' + }, + MACHINE_PROXY: { + value: 110, + tips: '导入时优先使用导入密码', + title: '机器代理 导入', + redirect: '/machine/proxy' + }, + TAIL_FILE: { + value: 120, + tips: '通过机器标识来区分机器, 机器名称无需填写', + title: '日志文件 导入', + redirect: '/log/list' + }, + APP_PROFILE: { + value: 200, + tips: '使用唯一标识来区分数据, 存在更新不存在新增', + title: '应用环境 导入', + redirect: '/app/profile' + }, + APPLICATION: { + value: 210, + tips: '使用唯一标识来区分数据, 存在更新不存在新增', + title: '应用信息 导入', + redirect: '/app/list' + }, + APP_REPOSITORY: { + value: 220, + tips: '使用名称来区分数据, 存在更新不存在新增, 优先使用导入密码', + title: '版本仓库 导入', + redirect: '/app/repo' + }, + COMMAND_TEMPLATE: { + value: 300, + tips: '使用模板名称来区分数据, 存在更新不存在新增', + title: '命令模板 导入', + redirect: '/template/list' + }, + WEBHOOK: { + value: 310, + tips: '使用名称来区分数据, 存在更新不存在新增', + title: 'webhook 导入', + redirect: '/webhook/list' + } +} + +/** + * 数据清理类型 + */ +export const DATA_CLEAR_TYPE = { + BATCH_EXEC: { value: 10 }, + TERMINAL_LOG: { value: 20 }, + SCHEDULER_RECORD: { value: 30 }, + APP_BUILD: { value: 40 }, + APP_RELEASE: { value: 50 }, + APP_PIPELINE_EXEC: { value: 60 }, + USER_EVENT_LOG: { value: 70 }, + MACHINE_ALARM_HISTORY: { value: 80 } +} + +/** + * 监控状态 + */ +export const MONITOR_STATUS = { + NOT_START: { + value: 1, + label: '未启动', + status: 'default' + }, + STARTING: { + value: 2, + label: '启动中', + status: 'success' + }, + RUNNING: { + value: 3, + label: '运行中', + status: 'processing' + } +} + +/** + * 监控数据区间 + */ +export const MONITOR_DATA_RANGE = { + HOUR: { + value: 1, + label: '实时', + rangeGetter: () => { + const end = ~~(Date.now() / 1000) + const start = end - (60 * 60) + return [start, end] + } + }, + DAY: { + value: 2, + label: '近24时', + rangeGetter: () => { + const end = ~~(Date.now() / 1000) + const start = end - (60 * 60 * 24) + return [start, end] + } + }, + WEEK: { + value: 3, + label: '近7天', + rangeGetter: () => { + const end = ~~(Date.now() / 1000) + const start = end - (60 * 60 * 24 * 7) + return [start, end] + } + } +} + +/** + * 监控数据粒度 + */ +export const MONITOR_DATA_GRANULARITY = { + SECOND_30: { + value: 12, + label: '30秒', + check: (start, end) => { + return end - start <= 60 * 60 + } + }, + MINUTE_1: { + value: 20, + label: '1分', + min: 0, + max: 60 * 60 * 24, + default: true, + check: (start, end) => { + return end - start <= 60 * 60 + } + }, + MINUTE_5: { + value: 22, + label: '5分', + default: true, + check: (start, end) => { + return end - start <= 60 * 60 * 24 + } + }, + MINUTE_10: { + value: 24, + label: '10分', + check: (start, end) => { + const e = end - start + return 60 * 60 < e && e <= 60 * 60 * 24 + } + }, + MINUTE_30: { + value: 26, + label: '30分', + check: (start, end) => { + const e = end - start + return 60 * 60 < e && e <= 60 * 60 * 24 + } + }, + HOUR_1: { + value: 30, + label: '1时', + default: true, + check: (start, end) => { + const e = end - start + return 60 * 60 * 24 <= e && e <= 60 * 60 * 24 * 7 + } + }, + HOUR_6: { + value: 32, + label: '6时', + check: (start, end) => { + const e = end - start + return 60 * 60 * 24 < e && e <= 60 * 60 * 24 * 7 * 2 + } + }, + HOUR_12: { + value: 34, + label: '12时', + check: (start, end) => { + const e = end - start + return 60 * 60 * 24 < e && e <= 60 * 60 * 24 * 7 * 2 + } + }, + DAY: { + value: 40, + label: '1天', + default: true, + check: (start, end) => { + const e = end - start + return 60 * 60 * 24 * 7 <= e + } + } +} + +/** + * webhook 类型 + */ +export const WEBHOOK_TYPE = { + DING_ROBOT: { + value: 10, + label: '钉钉机器人' + } +} + +/** + * 机器报警类型 + */ +export const MACHINE_ALARM_TYPE = { + CPU_USAGE: { + value: 10, + label: 'CPU使用率', + alarmProp: 'cpuAlarm' + }, + MEMORY_USAGE: { + value: 20, + label: '内存使用率', + alarmProp: 'memoryAlarm' + } +} + +/** + * 树状结构移动类型 + */ +export const TREE_MOVE_TYPE = { + IN_TOP: { + value: 1 + }, + IN_BOTTOM: { + value: 2 + }, + PREV: { + value: 3 + }, + NEXT: { + value: 4 + } +} diff --git a/orion-ops-vue/src/lib/filters.js b/orion-ops-vue/src/lib/filters.js new file mode 100644 index 0000000..7880303 --- /dev/null +++ b/orion-ops-vue/src/lib/filters.js @@ -0,0 +1,8 @@ +import { dateFormat } from './utils' + +/** + * 格式化时间 + */ +export function formatDate(origin, pattern = 'yyyy-MM-dd HH:mm:ss') { + return dateFormat(new Date(origin), pattern) +} diff --git a/orion-ops-vue/src/lib/http.js b/orion-ops-vue/src/lib/http.js new file mode 100644 index 0000000..905d590 --- /dev/null +++ b/orion-ops-vue/src/lib/http.js @@ -0,0 +1,239 @@ +import axios from 'axios' +import $message from 'ant-design-vue/lib/message' +import $storage from './storage' +import router from '../router/index' + +const $http = axios.create({ + responseType: 'json', + baseURL: process.env.VUE_APP_BASE_API, + timeout: 10000 +}) + +// 默认配置项 +const defaultConfig = { + // 是否需要登录 + auth: true, + // 超时时间 + timeout: 10000, + // 请求头 + contentType: 'application/json', + // 跳过响应拦截器异常处理提示 + skipErrorMessage: false, + // 跳过响应拦截器 + skipRespInterceptor: false +} + +/** + * get请求 + */ +const $get = (url, params = {}, config = {}) => { + config.params = params + return new Promise((resolve, reject) => { + $http.get(url, fillDefaultConfig(config)) + .then(res => resolve(res)) + .catch(err => reject(err)) + }) +} + +/** + * post请求 + */ +const $post = (url, data = {}, config = {}) => { + return new Promise((resolve, reject) => { + $http.post(url, data, fillDefaultConfig(config)) + .then(res => resolve(res)) + .catch(err => reject(err)) + }) +} + +/** + * http请求 + */ +const $fetch = (url, method = 'get', config) => { + return new Promise((resolve, reject) => { + $http.request({ + url: url, + method: method, + ...fillDefaultConfig(config) + }) + .then(res => resolve(res)) + .catch(err => reject(err)) + }) +} + +/** + * 导出请求 + */ +const $export = (url, data, config) => { + return new Promise((resolve, reject) => { + $http.post(url, data, fillDefaultConfig({ + skipRespInterceptor: true, + responseType: 'blob', + timeout: 600000, + loading: '正在导出请耐心等待...', + ...config + })).then(res => resolve(res)) + .catch(err => reject(err)) + }) +} + +/** + * 填充默认配置 + */ +function fillDefaultConfig(config) { + for (const defaultConfigKey in defaultConfig) { + if (!(defaultConfigKey in config)) { + config[defaultConfigKey] = defaultConfig[defaultConfigKey] + } + } + return config +} + +/** + * 请求拦截器 + */ +$http.interceptors.request.use( + config => { + const loginToken = $storage.get($storage.keys.LOGIN_TOKEN) + // 设置 Content-Type + config.headers['Content-Type'] = config.contentType + // 登录判断 + if (config.auth && !loginToken) { + throw new RequestError(700, '会话过期') + } + config.headers[$storage.keys.LOGIN_TOKEN] = loginToken + // 设置加载 + if (config.loading) { + config.loadingKey = $message.loading(config.loading) + } + return config + }, err => { + return Promise.reject(err) + } +) + +/** + * 响应拦截器 + */ +$http.interceptors.response.use( + resp => { + // 加载关闭 + resp.config.loadingKey && resp.config.loadingKey() + // 跳过响应拦截器 + if (resp.config.skipRespInterceptor) { + return resp + } + const skipErrorMessage = resp.config.skipErrorMessage + // 判断data + var respData = resp.data + if (!respData || !respData.code) { + if (!skipErrorMessage) { + $message.warning('请求无效') + } + return Promise.reject(resp) + } + // 判断code + switch (respData.code) { + case 200: + // 正常返回 + return respData + case 700: + case 730: + case 740: + // 未登录/IP封禁/用户禁用 + $message.warning(respData.msg) + $storage.clear() + $storage.clearSession() + router.push({ path: '/login' }) + return Promise.reject(respData) + case 500: + if (!skipErrorMessage) { + $message.error(respData.msg) + } + return Promise.reject(respData) + default: + if (!skipErrorMessage) { + $message.warning(respData.msg) + } + return Promise.reject(respData) + } + }, err => { + // 加载关闭 + err.config.loadingKey && err.config.loadingKey() + // 跳过响应拦截器 + if (err.config.skipRespInterceptor) { + return Promise.reject(err) + } + let rejectWrapper + if (err instanceof RequestError) { + // 自定义error + rejectWrapper = err.toWrapper() + if (err.code === 700) { + rejectWrapper.notifyLevel('warning') + router.push({ path: '/login' }) + } + } else { + // http错误 + if (!err.response || !err.response.status) { + rejectWrapper = new RejectWrapper(0, '网络异常') + } else { + rejectWrapper = new RejectWrapper(err.response.status, '请求失败') + } + } + if (!err.config.skipErrorMessage) { + rejectWrapper.tips() + } + return Promise.reject(rejectWrapper) + } +) + +/** + * 请求异常 + */ +class RequestError extends Error { + code + msg + + constructor(code, msg) { + super() + this.code = code + this.msg = msg + Error.captureStackTrace(this, this.constructor) + } + + toWrapper() { + return new RejectWrapper(this.code, this.msg) + } +} + +/** + * reject包装 + */ +class RejectWrapper { + code + msg + level + + constructor(code, msg, level = 'error') { + this.code = code + this.msg = msg + this.level = level + } + + notifyLevel(_level = 'error') { + this.level = _level + return this + } + + tips() { + $message[this.level].call(this, this.msg) + delete this.level + } +} + +export default { + $get, + $post, + $fetch, + $export +} diff --git a/orion-ops-vue/src/lib/storage.js b/orion-ops-vue/src/lib/storage.js new file mode 100644 index 0000000..26fb50f --- /dev/null +++ b/orion-ops-vue/src/lib/storage.js @@ -0,0 +1,72 @@ +import { isEmptyStr } from './utils' + +const $storage = { + + keys: { + LOGIN_TOKEN: 'O-Login-Token', + CURRENT_USER: 'currentUser', + ACTIVE_PROFILE: 'activeProfile', + MACHINE_VIEW: 'machineView' + }, + + /** + * 获取值 + */ + get(key, def) { + const item = localStorage.getItem(key) + return isEmptyStr(item) ? def : item + }, + + /** + * 设置值 + */ + set(key, val) { + localStorage.setItem(key, val) + }, + + /** + * 删除key + */ + remove(key) { + localStorage.removeItem(key) + }, + + /** + * 清空本地存储 + */ + clear() { + localStorage.clear() + }, + + /** + * 获取值 + */ + getSession(key, def) { + const item = sessionStorage.getItem(key) + return isEmptyStr(item) ? def : item + }, + + /** + * 设置值 + */ + setSession(key, val) { + sessionStorage.setItem(key, val) + }, + + /** + * 删除key + */ + removeSession(key) { + sessionStorage.removeItem(key) + }, + + /** + * 清空本地存储 + */ + clearSession() { + sessionStorage.clear() + } + +} + +export default $storage diff --git a/orion-ops-vue/src/lib/tree.js b/orion-ops-vue/src/lib/tree.js new file mode 100644 index 0000000..9ae6ae0 --- /dev/null +++ b/orion-ops-vue/src/lib/tree.js @@ -0,0 +1,111 @@ +/** + * 查询 tree node + */ +export function findNode(nodes, findKey) { + // 查询当前层 + for (const node of nodes) { + if (node.key === findKey) { + return node + } + } + // 查询子节点 + for (const node of nodes) { + if (node.children && node.children.length) { + const findValue = findNode(node.children, findKey) + if (findValue) { + return findValue + } + } + } +} + +/** + * 查询 tree parent node + */ +export function findParentNode(nodes, findKey) { + // 查询当前层 + for (const node of nodes) { + if (node.children && node.children.length) { + const filterNodes = node.children.filter(childrenNode => childrenNode.key === findKey) + if (filterNodes.length) { + return node + } + } + } + // 查询子节点 + for (const node of nodes) { + if (node.children && node.children.length) { + const findValue = findParentNode(node.children, findKey) + if (findValue) { + return findValue + } + } + } +} + +/** + * 获取子节点的 key + */ +export function getChildNodeKeys(node, keys = []) { + if (node.children && node.children.length) { + // 查询当前层 + for (const child of node.children) { + keys.push(child.key) + } + // 查询子节点 + for (const child of node.children) { + getChildNodeKeys(child, keys) + } + } + return keys +} + +/** + * 填充 tree属性 + */ +export function setTreeDataProps(tree, filler) { + if (tree != null) { + tree.forEach(node => { + filler(node) + setTreeDataProps(node.children, filler) + }) + } + return tree || [] +} + +/** + * 获取深度 key + */ +export function getDepthKeys(tree, depth, keys = [], curr = 1) { + if (curr > depth) { + return keys + } + for (const node of tree) { + keys.push(node.key) + if (node.children && node.children.length) { + getDepthKeys(node.children, depth, keys, curr + 1) + } + } + return keys +} + +/** + * 设置 tree 全路径 + */ +export function setTreePath(tree) { + if (!tree || !tree.length) { + return [] + } + const loop = (node, parents = []) => { + const current = [...parents, node.title] + if (node.children && node.children.length) { + for (const child of node.children) { + loop(child, current) + } + } + node.path = current.join('/') + return current + } + tree.forEach(s => loop(s)) + return tree || [] +} diff --git a/orion-ops-vue/src/lib/utils.js b/orion-ops-vue/src/lib/utils.js new file mode 100644 index 0000000..e1e4f1c --- /dev/null +++ b/orion-ops-vue/src/lib/utils.js @@ -0,0 +1,428 @@ +import * as $md5 from 'js-md5' + +/** + * 判断值是否非空 + */ +export function isEmptyStr(val) { + return typeof (val) === 'undefined' || val == null || val === '' +} + +/** + * string -> boolean + */ +export function strToBoo(str) { + if (!str) { + return false + } + return str === 'true' +} + +/** + * 复制到剪切板 + */ +export function copyToClipboard(content) { + const clipboardData = window.clipboardData + if (clipboardData) { + clipboardData.clearData() + clipboardData.setData('Text', content) + return true + } else if (document.execCommand) { + const el = document.createElement('textarea') + el.value = content + el.setAttribute('readonly', '') + el.style.position = 'absolute' + el.style.left = '-9999px' + document.body.appendChild(el) + el.select() + document.execCommand('copy') + document.body.removeChild(el) + return true + } + return false +} + +/** + * 获取剪切板内容 返回promise + */ +export function getClipboardText() { + return navigator.clipboard.readText() +} + +/** + * md5 + */ +export function md5(val) { + return $md5.hex(val) +} + +/** + * ssh命令 + */ +export function getSshCommand(username, host, port) { + return `ssh -p ${port} ${username}@${host}` +} + +/** + * 全屏 + */ +export function fullScreen() { + const el = document.documentElement + const rfs = el.requestFullScreen || el.webkitRequestFullScreen || el.mozRequestFullScreen || el.msRequestFullScreen + if (typeof rfs !== 'undefined' && rfs) { + rfs.call(el) + } +} + +/** + * 取消全屏 + */ +export function exitFullScreen() { + const el = document + const cfs = el.cancelFullScreen || el.webkitCancelFullScreen || el.mozCancelFullScreen || el.exitFullScreen + if (typeof cfs !== 'undefined' && cfs) { + cfs.call(el) + } +} + +/** + * 格式化时间 + */ +export function dateFormat(date, pattern = 'yyyy-MM-dd HH:mm:ss') { + const o = { + 'M+': date.getMonth() + 1, + 'd+': date.getDate(), + 'H+': date.getHours(), + 'm+': date.getMinutes(), + 's+': date.getSeconds(), + 'q+': Math.floor((date.getMonth() + 3) / 3), + 'S+': date.getMilliseconds() + } + if (/(y+)/.test(pattern)) { + pattern = pattern.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)) + } + for (var k in o) { + if (new RegExp('(' + k + ')').test(pattern)) { + pattern = pattern.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length))) + } + } + return pattern +} + +/** + * 格式化秒 + */ +export function formatSecond(s, p = 'HH:mm') { + return dateFormat(new Date(~~s * 1000), p) +} + +/** + * 10进制权限 转 字符串权限 + */ +export function permission10toString(permission) { + const ps = (permission + '') + let res = '' + for (let i = 0; i < ps.length; i++) { + const per = ps.charAt(i) + if ((per & 4) === 0) { + res += '-' + } else { + res += 'r' + } + if ((per & 2) === 0) { + res += '-' + } else { + res += 'w' + } + if ((per & 1) === 0) { + res += '-' + } else { + res += 'x' + } + } + return res +} + +/** + * 返回base64实际数据 + */ +export function getBase64Data(e) { + return e.substring(e.indexOf(',') + 1) +} + +/** + *读取文件base64 返回promise + */ +export function readFileBase64(e) { + return new Promise((resolve, reject) => { + const reader = new FileReader() + reader.readAsDataURL(e) + reader.onload = res => { + resolve(res.target.result) + } + reader.onerror = err => { + reject(err) + } + }) +} + +/** + * 获取页面路由 + */ +export function getRoute(url = location.href) { + return url.substring(url.lastIndexOf('#') + 1).split('?')[0] +} + +/** + * 格式化数字为 ,分割 + */ +export function formatNumber(value = 0) { + value += '' + const list = value.split('.') + const prefix = list[0].charAt(0) === '-' ? '-' : '' + let num = prefix ? list[0].slice(1) : list[0] + let result = '' + while (num.length > 3) { + result = `,${num.slice(-3)}${result}` + num = num.slice(0, num.length - 3) + } + if (num) { + result = num + result + } + return `${prefix}${result}${list[1] ? `.${list[1]}` : ''}` +} + +/** + * 判断是否为数字 + */ +export function isNumber(value, decimal = true, negative = true) { + let reg + if (decimal && negative) { + reg = /^-?[0-9]*(\.[0-9]*)?$/ + } else if (!decimal && negative) { + reg = /^-?[0-9]*$/ + } else if (decimal && !negative) { + reg = /^[0-9]*(\.[0-9]*)?$/ + } else if (!decimal && !negative) { + reg = /^[0-9]*$/ + } else { + return false + } + return (!isNaN(value) && reg.test(value)) || value === '' +} + +/** + * 替换数字 + */ +export function replaceNumber(value) { + const s = value.charAt(value.length - 1) + if (s === '.' || s === '-') { + return s.substring(0, value.length - 1) + } + return value +} + +/** + * 给array的元素定义key + */ +export function defineArrayKey(array, key, value = null) { + if (!array || !array.length) { + return + } + for (const item of array) { + item[key] = value + } +} + +/** + * 给array的元素定义key + */ +export function defineArrayKeys(array) { + if (!array || !array.length) { + return + } + for (const item of array) { + for (let i = 1; i < arguments.length; i++) { + item[arguments[i]] = null + } + } +} + +/** + * 获取解析路径 + */ +export function getPathAnalysis(analysisPath, paths = []) { + const lastSymbol = analysisPath.lastIndexOf('/') + if (lastSymbol === -1) { + paths.unshift({ + name: '/', + path: '/' + }) + return paths + } + const name = analysisPath.substring(lastSymbol, analysisPath.length) + if (!isEmptyStr(name) && name !== '/') { + paths.unshift({ + name: name.substring(1, name.length), + path: analysisPath + }) + } + return getPathAnalysis(analysisPath.substring(0, lastSymbol), paths) +} + +/** + * 替换路径 + */ +export function getPath(path) { + return path.replace(new RegExp('\\\\+', 'g'), '/') + .replace(new RegExp('/+', 'g'), '/') +} + +/** + * 获取父级路径 + */ +export function getParentPath(path) { + const paths = getPath(path).split('/') + const len = paths.length + if (len <= 2) { + return '/' + } + let parent = '' + for (let i = 0; i < len - 1; i++) { + parent += paths[i] + if (i !== len - 2) { + parent += '/' + } + } + return parent +} + +/** + * 获取当前页面的缩放值 + */ +export function detectZoom() { + let ratio = 0 + const screen = window.screen + const ua = navigator.userAgent.toLowerCase() + if (window.devicePixelRatio !== undefined) { + ratio = window.devicePixelRatio + } else if (~ua.indexOf('msie')) { + if (screen.deviceXDPI && screen.logicalXDPI) { + ratio = screen.deviceXDPI / screen.logicalXDPI + } + } else if (window.outerWidth !== undefined && window.innerWidth !== undefined) { + ratio = window.outerWidth / window.innerWidth + } + if (ratio) { + ratio = Math.round(ratio * 100) + } + return ratio +} + +/** + * 清除xss + */ +export function cleanXss(s) { + s = s.replaceAll('&', '&') + s = s.replaceAll('<', '<') + s = s.replaceAll('>', '>') + s = s.replaceAll('\'', ''') + s = s.replaceAll('"', '"') + s = s.replaceAll('\n', '
') + s = s.replaceAll('\t', '    ') + return s +} + +/** + * 替换高亮关键字 + */ +export function replaceStainKeywords(message) { + return cleanXss(message) + .replaceAll('<sb 0>', '') + .replaceAll('<sb 2>', '') + .replaceAll('<sb>', '') + .replaceAll('</sb>', '') + .replaceAll('<sr 0>', '') + .replaceAll('<sr 2>', '') + .replaceAll('<sr>', '') + .replaceAll('</sr>', '') + .replaceAll('<b>', '') + .replaceAll('</b>', '') +} + +/** + * 清除高亮关键字 + */ +export function clearStainKeywords(message) { + return cleanXss(message) + .replaceAll('<sb 0>', '') + .replaceAll('<sb 2>', '') + .replaceAll('<sb>', '') + .replaceAll('</sb>', '') + .replaceAll('<sr 0>', '') + .replaceAll('<sr 2>', '') + .replaceAll('<sr>', '') + .replaceAll('</sr>', '') + .replaceAll('<b>', '') + .replaceAll('</b>', '') + .replaceAll('
', '\n') +} + +/** + * 获取唯一的 UUID + */ +export function getUUID() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + const r = Math.random() * 16 | 0 + const v = c === 'x' ? r : (r & 0x3 | 0x8) + return v.toString(16) + }) +} + +/** + * 下载文件 + */ +export function downloadFile(res, fileName) { + const blob = new Blob([res.data]) + const tempLink = document.createElement('a') + const blobURL = window.URL.createObjectURL(blob) + tempLink.style.display = 'none' + tempLink.href = blobURL + if (!fileName) { + fileName = res.headers['content-disposition'] + ? res.headers['content-disposition'].split(';')[1].split('=')[1] + : new Date().getTime() + } + tempLink.download = decodeURIComponent(fileName) + if (typeof tempLink.download === 'undefined') { + tempLink.target = '_blank' + } + document.body.appendChild(tempLink) + tempLink.click() + document.body.removeChild(tempLink) + window.URL.revokeObjectURL(blobURL) +} + +/** + * 将消息转为分段消息 + */ +export function messageToPartialMessages(msg) { + const max = 4096 + const len = msg.length + if (len <= max) { + return [msg] + } + let c = ~~(len / max) + const mod = len % max + if (mod !== 0) { + c++ + } + const arr = [] + for (let i = 0; i < c; i++) { + if (i === c - 1) { + arr.push(msg.substr(i * max, mod === 0 ? max : mod)) + } else { + arr.push(msg.substr(i * max, max)) + } + } + return arr +} diff --git a/orion-ops-vue/src/lib/validate.js b/orion-ops-vue/src/lib/validate.js new file mode 100644 index 0000000..d837428 --- /dev/null +++ b/orion-ops-vue/src/lib/validate.js @@ -0,0 +1,9 @@ +export function validatePort(rule, value, callback) { + if (!value) { + callback(new Error('请输入端口')) + } else if (parseInt(value) < 2 || parseInt(value) > 65534) { + callback(new Error('端口必须在2~65534之间')) + } else { + callback() + } +} diff --git a/orion-ops-vue/src/lib/watermark.js b/orion-ops-vue/src/lib/watermark.js new file mode 100644 index 0000000..3ae1877 --- /dev/null +++ b/orion-ops-vue/src/lib/watermark.js @@ -0,0 +1,80 @@ +const watermark = { + currentId: undefined +} + +/** + * 设置水印 + */ +watermark.set = (str) => { + if (process.env.VUE_APP_WATERMARK !== 'true') { + return undefined + } + let reset = true + if (watermark.currentId) { + // 存在并且文本变化 + const before = document.getElementById(watermark.currentId) + if (before) { + if (before.mv !== str) { + document.body.removeChild(before) + } else { + reset = false + } + } + } + // 重置 + if (reset) { + return watermark.reset(str) + } else { + return watermark.currentId + } +} + +/** + * 重设水印 + */ +watermark.reset = (str) => { + const id = 'water.mask.' + new Date().getTime() + const can = document.createElement('canvas') + can.width = 450 + can.height = 350 + + const cans = can.getContext('2d') + cans.rotate((-15 * Math.PI) / 180) + cans.font = '13px Vedana' + cans.fillStyle = 'rgba(200, 200, 200, 0.60)' + cans.textAlign = 'center' + cans.textBaseline = 'hanging' + cans.fillText(str, can.width / 3, can.height / 2) + + const div = document.createElement('div') + div.id = id + div.mv = str + div.style.pointerEvents = 'none' + div.style.top = '30px' + div.style.left = '0px' + div.style.position = 'fixed' + div.style.zIndex = '100000' + div.style.display = 'block' + div.style.width = '100%' + div.style.height = '100%' + div.style.background = 'url(' + can.toDataURL('image/png') + ') left top repeat' + document.body.appendChild(div) + watermark.currentId = id + return id +} + +/** + * 移除水印 + */ +watermark.remove = () => { + if (!watermark.currentId) { + return + } + const ele = document.getElementById(watermark.currentId) + if (ele !== null) { + document.body.removeChild(ele) + } + watermark.currentId = undefined +} + +export default watermark diff --git a/orion-ops-vue/src/main.js b/orion-ops-vue/src/main.js new file mode 100644 index 0000000..82fe961 --- /dev/null +++ b/orion-ops-vue/src/main.js @@ -0,0 +1,89 @@ +import Vue from 'vue' +import App from './App' +import router from './router' +import $api from './lib/api' +import $storage from './lib/storage' +import { copyToClipboard } from '@/lib/utils' +import './lib/directive' + +import ant from 'ant-design-vue' +import $message from 'ant-design-vue/lib/message' +import 'ant-design-vue/dist/antd.min.css' +import watermark from '@/lib/watermark' + +Vue.use(ant) + +Vue.prototype.$api = $api +Vue.prototype.$storage = $storage + +Vue.config.productionTip = false + +$message.config({ + maxCount: 1 +}) + +router.beforeEach((to, from, next) => { + // 校验登录 + if (to.meta.requireAuth !== false && !$storage.get($storage.keys.LOGIN_TOKEN)) { + router.push({ path: '/login' }) + return + } + // 校验管理员 + if (to.meta.requireAdmin && !Vue.prototype.$isAdmin()) { + router.push({ path: '/404' }) + return + } + // 设置标题 + if (to.meta.title) { + document.title = to.meta.title + } + // 添加水印 + const userInfo = $storage.get($storage.keys.CURRENT_USER) + if (to.meta.mask === false) { + watermark.remove() + } else if (userInfo) { + const user = JSON.parse(userInfo) + watermark.set(`${user.username} (${user.nickname})`) + } + next() +}) + +/** + * 复制 + */ +Vue.prototype.$copy = function(value, tips = '已复制') { + if (copyToClipboard(value) && tips) { + if (tips === true) { + this.$message.success(value + ' 已复制') + } else { + this.$message.success(tips) + } + } +} + +/** + * 是否是管理员 + */ +Vue.prototype.$isAdmin = function() { + try { + return JSON.parse($storage.get($storage.keys.CURRENT_USER)).roleType === 10 + } catch (e) { + return false + } +} + +/** + * 获取当前userId + */ +Vue.prototype.$getUserId = function() { + try { + return JSON.parse($storage.get($storage.keys.CURRENT_USER)).userId + } catch (e) { + return false + } +} + +new Vue({ + router, + render: h => h(App) +}).$mount('#app') diff --git a/orion-ops-vue/src/router/index.js b/orion-ops-vue/src/router/index.js new file mode 100644 index 0000000..ccad998 --- /dev/null +++ b/orion-ops-vue/src/router/index.js @@ -0,0 +1,537 @@ +import Vue from 'vue' +import VueRouter from 'vue-router' +import Login from '../views/Login' +import Layout from '../components/layout/Layout' + +Vue.use(VueRouter) + +// 重复点击路由不抛异常 +const originalPush = VueRouter.prototype.push +VueRouter.prototype.push = function push(location) { + return originalPush.call(this, location).catch(err => err) +} + +const routes = [ + { + path: '/', + redirect: '/console' + }, + { + path: '/login', + name: 'login', + meta: { + requireAuth: false, + title: '登录', + mask: false + }, + component: Login + }, + { + path: '/machine/terminal/:id?', + name: 'terminal', + meta: { + requireAuth: true, + title: '终端' + }, + component: () => import('../views/machine/MachineTerminal') + }, + { + path: '/machine/sftp/:id?', + name: 'sftp', + meta: { + requireAuth: true, + title: '文件管理器' + }, + component: () => import('../views/machine/MachineSftp') + }, + { + path: '/batch/exec/log/view/:id', + name: 'ExecLoggerView', + meta: { + requireAuth: true, + title: '执行日志' + }, + component: () => import('../views/exec/ExecLoggerView') + }, + { + path: '/log/view/:id?', + name: 'loggerView', + meta: { + requireAuth: true, + title: '日志面板' + }, + component: () => import('../views/log/LoggerView') + }, + { + path: '/app/action/log/view/:id', + name: 'AppActionLogView', + meta: { + requireAuth: true, + title: '操作日志' + }, + component: () => import('../views/app/AppActionLogView') + }, + { + path: '/app/build/log/view/:id', + name: 'AppBuildLogView', + meta: { + requireAuth: true, + title: '构建日志' + }, + component: () => import('../views/app/AppBuildLogView') + }, + { + path: '/app/release/log/view/:id', + name: 'AppReleaseLogView', + meta: { + requireAuth: true, + title: '发布日志' + }, + component: () => import('../views/app/AppReleaseLogView') + }, + { + path: '/app/release/machine/log/view/:id', + name: 'AppReleaseMachineLogView', + meta: { + requireAuth: true, + title: '发布日志' + }, + component: () => import('../views/app/AppReleaseMachineLogView') + }, + { + path: '/task/machine/log/view/:id', + name: 'SchedulerMachineLogView', + meta: { + requireAuth: true, + title: '调度日志' + }, + component: () => import('../views/scheduler/SchedulerMachineLogView') + }, + { + path: '/task/log/view/:id', + name: 'SchedulerTaskLogView', + meta: { + requireAuth: true, + title: '调度日志' + }, + component: () => import('../views/scheduler/SchedulerTaskLogView') + }, + { + path: '', + name: 'layout', + component: Layout, + children: [ + { + path: '/console', + name: 'console', + meta: { + requireAuth: true, + title: '控制台', + visibleProfile: true + }, + component: () => import('../views/Console') + }, + { + path: '/machine/list', + name: 'machineList', + meta: { + requireAuth: true, + title: '机器列表' + // GROUP_FLAG + // leftProps: [{ + // icon: 'swap', + // title: '切换视图', + // event: 'changeView' + // }] + }, + component: () => import('../views/machine/MachineList') + }, + { + path: '/machine/add', + name: 'machineAdd', + meta: { + requireAuth: true, + title: '添加机器', + leftProps: [{ + icon: 'arrow-left', + title: '返回', + call: 'back' + }] + }, + component: () => import('../views/machine/AddMachine') + }, + { + path: '/machine/update/:id', + name: 'machineUpdate', + meta: { + requireAuth: true, + title: '修改机器', + leftProps: [{ + icon: 'arrow-left', + title: '返回', + call: 'back' + }] + }, + component: () => import('../views/machine/AddMachine') + }, + { + path: '/machine/monitor/list', + name: 'machineMonitorList', + meta: { + requireAuth: true, + title: '机器监控' + }, + component: () => import('../views/machine/MachineMonitorList') + }, + { + path: '/machine/monitor/metrics/:machineId', + name: 'machineMonitor', + meta: { + requireAuth: true, + title: '机器监控' + }, + component: () => import('../views/machine/MachineMonitorMetrics') + }, + { + path: '/machine/env/:id?', + name: 'machineEnv', + meta: { + requireAuth: true, + title: '环境变量', + leftProps: [{ + icon: 'arrow-left', + title: '返回', + call: 'back' + }] + }, + component: () => import('../views/machine/MachineEnv') + }, + { + path: '/machine/key', + name: 'MachineKey', + meta: { + requireAuth: true, + title: '机器密钥' + }, + component: () => import('../views/machine/MachineKey') + }, + { + path: '/terminal/session', + name: 'terminalSession', + meta: { + requireAuth: true, + requireAdmin: true, + title: '终端会话' + }, + component: () => import('../views/machine/MachineTerminalSession') + }, + { + path: '/terminal/logs', + name: 'terminalLogs', + meta: { + requireAuth: true, + title: '终端日志' + }, + component: () => import('../views/machine/MachineTerminalLogs') + }, + { + path: '/machine/proxy', + name: 'MachineProxy', + meta: { + requireAuth: true, + title: '机器代理' + }, + component: () => import('../views/machine/MachineProxy') + }, + { + path: '/batch/exec/list', + name: 'BatchExecList', + meta: { + requireAuth: true, + title: '执行记录' + }, + component: () => import('../views/exec/BatchExecList') + }, + { + path: '/batch/exec/add', + name: 'BatchExecTask', + meta: { + requireAuth: true, + title: '批量执行' + }, + component: () => import('../views/exec/BatchExecTask') + }, + { + path: '/batch/upload', + name: 'BatchUploadFile', + meta: { + requireAuth: true, + title: '批量上传' + }, + component: () => import('../views/exec/BatchUploadFile') + }, + { + path: '/log/list', + name: 'loggerList', + meta: { + requireAuth: true, + title: '日志列表' + }, + component: () => import('../views/log/LoggerList') + }, + { + path: '/app/list', + name: 'appList', + meta: { + requireAuth: true, + title: '应用列表', + visibleProfile: true + }, + component: () => import('../views/app/AppList') + }, + { + path: '/app/conf/:appId', + name: 'appConfig', + meta: { + requireAuth: true, + title: '应用配置', + visibleProfile: true, + leftProps: [{ + icon: 'arrow-left', + title: '返回', + call: 'back' + }, { + icon: 'unordered-list', + title: '选择模板', + event: 'openTemplate' + }] + }, + component: () => import('../views/app/AppConfig') + }, + { + path: '/app/pipeline/list', + name: 'appPipelineList', + meta: { + requireAuth: true, + title: '流水线配置', + visibleProfile: true + }, + component: () => import('../views/app/AppPipeline') + }, + { + path: '/app/profile', + name: 'appProfile', + meta: { + requireAuth: true, + title: '环境管理' + }, + component: () => import('../views/app/AppProfile') + }, + { + path: '/app/env/:id?', + name: 'appEnv', + meta: { + requireAuth: true, + title: '环境变量', + visibleProfile: true, + leftProps: [{ + icon: 'arrow-left', + title: '返回', + call: 'back' + }] + }, + component: () => import('../views/app/AppEnv') + }, + { + path: '/app/repo', + name: 'appRepository', + meta: { + requireAuth: true, + title: '版本仓库' + }, + component: () => import('../views/app/AppRepository') + }, + { + path: '/app/build/list', + name: 'appBuildList', + meta: { + requireAuth: true, + title: '应用构建', + visibleProfile: true + }, + component: () => import('../views/app/AppBuild') + }, + { + path: '/app/release/list', + name: 'appReleaseList', + meta: { + requireAuth: true, + title: '应用发布', + visibleProfile: true + }, + component: () => import('../views/app/AppRelease') + }, + { + path: '/app/pipeline/task', + name: 'appPipelineTask', + meta: { + requireAuth: true, + title: '流水线任务', + visibleProfile: true + }, + component: () => import('../views/app/AppPipelineTask') + }, + { + path: '/app/build/statistics', + name: 'appBuildStatistics', + meta: { + requireAuth: true, + title: '构建统计', + visibleProfile: true + }, + component: () => import('../views/app/AppBuildStatistics') + }, + { + path: '/app/release/statistics', + name: 'appReleaseStatistics', + meta: { + requireAuth: true, + title: '发布统计', + visibleProfile: true + }, + component: () => import('../views/app/AppReleaseStatistics') + }, + { + path: '/app/pipeline/statistics', + name: 'appPipelineStatistics', + meta: { + requireAuth: true, + title: '流水线统计', + visibleProfile: true + }, + component: () => import('../views/app/AppPipelineStatistics') + }, + { + path: '/user/list', + name: 'userList', + meta: { + requireAuth: true, + title: '用户列表' + }, + component: () => import('../views/user/UserList') + }, + { + path: '/alarm/group/list', + name: 'alarmGroupList', + meta: { + requireAuth: true, + title: '报警组列表' + }, + component: () => import('../views/user/AlarmGroupList') + }, + { + path: '/user/detail', + name: 'userDetail', + meta: { + requireAuth: true, + title: '基本信息' + }, + component: () => import('../views/user/UserDetail') + }, + { + path: '/user/event/logs/:id?', + name: 'userEventLogList', + meta: { + requireAuth: true, + title: '操作日志' + }, + component: () => import('../views/user/UserEventLogList') + }, + { + path: '/template/list', + name: 'templateList', + meta: { + requireAuth: true, + title: '模板配置' + }, + component: () => import('../views/content/TemplateList') + }, + { + path: '/webhook/list', + name: 'webhookList', + meta: { + requireAuth: true, + title: 'webhook 配置' + }, + component: () => import('../views/content/WebhookList') + }, + { + path: '/system/env', + name: 'systemEnv', + meta: { + requireAuth: true, + title: '环境变量' + }, + component: () => import('../views/system/SystemEnv') + }, + { + path: '/system/setting', + name: 'systemSetting', + meta: { + requireAuth: true, + title: '系统设置' + }, + component: () => import('../views/system/SystemSetting') + }, + { + path: '/scheduler/list', + name: 'schedulerList', + meta: { + requireAuth: true, + title: '调度任务' + }, + component: () => import('../views/scheduler/SchedulerList') + }, + { + path: '/scheduler/record/:id?', + name: 'schedulerRecord', + meta: { + requireAuth: true, + title: '调度历史' + }, + component: () => import('../views/scheduler/SchedulerRecord') + }, + { + path: '/scheduler/statistics/:id?', + name: 'schedulerStatistics', + meta: { + requireAuth: true, + title: '调度统计' + }, + component: () => import('../views/scheduler/SchedulerStatistics') + }, + { + path: '*', + name: '404', + meta: { + requireAuth: true, + title: '404', + leftProps: [{ + icon: 'arrow-left', + title: '返回', + call: 'back' + }] + }, + component: () => import('../views/404') + } + ] + } + +] + +const router = new VueRouter({ + mode: 'hash', + routes +}) + +export default router diff --git a/orion-ops-vue/src/views/404.vue b/orion-ops-vue/src/views/404.vue new file mode 100644 index 0000000..447860f --- /dev/null +++ b/orion-ops-vue/src/views/404.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/orion-ops-vue/src/views/Console.vue b/orion-ops-vue/src/views/Console.vue new file mode 100644 index 0000000..7802f7e --- /dev/null +++ b/orion-ops-vue/src/views/Console.vue @@ -0,0 +1,276 @@ + + + + + diff --git a/orion-ops-vue/src/views/Login.vue b/orion-ops-vue/src/views/Login.vue new file mode 100644 index 0000000..a8a6a2d --- /dev/null +++ b/orion-ops-vue/src/views/Login.vue @@ -0,0 +1,225 @@ + + + + + diff --git a/orion-ops-vue/src/views/app/AppActionLogView.vue b/orion-ops-vue/src/views/app/AppActionLogView.vue new file mode 100644 index 0000000..167b43b --- /dev/null +++ b/orion-ops-vue/src/views/app/AppActionLogView.vue @@ -0,0 +1,20 @@ + + + + + diff --git a/orion-ops-vue/src/views/app/AppBuild.vue b/orion-ops-vue/src/views/app/AppBuild.vue new file mode 100644 index 0000000..0273c9d --- /dev/null +++ b/orion-ops-vue/src/views/app/AppBuild.vue @@ -0,0 +1,451 @@ + + + + + diff --git a/orion-ops-vue/src/views/app/AppBuildLogView.vue b/orion-ops-vue/src/views/app/AppBuildLogView.vue new file mode 100644 index 0000000..37ea9a8 --- /dev/null +++ b/orion-ops-vue/src/views/app/AppBuildLogView.vue @@ -0,0 +1,20 @@ + + + + + diff --git a/orion-ops-vue/src/views/app/AppBuildStatistics.vue b/orion-ops-vue/src/views/app/AppBuildStatistics.vue new file mode 100644 index 0000000..cf33de8 --- /dev/null +++ b/orion-ops-vue/src/views/app/AppBuildStatistics.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/orion-ops-vue/src/views/app/AppConfig.vue b/orion-ops-vue/src/views/app/AppConfig.vue new file mode 100644 index 0000000..21d4e42 --- /dev/null +++ b/orion-ops-vue/src/views/app/AppConfig.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/orion-ops-vue/src/views/app/AppEnv.vue b/orion-ops-vue/src/views/app/AppEnv.vue new file mode 100644 index 0000000..b19823a --- /dev/null +++ b/orion-ops-vue/src/views/app/AppEnv.vue @@ -0,0 +1,515 @@ + + + + + diff --git a/orion-ops-vue/src/views/app/AppList.vue b/orion-ops-vue/src/views/app/AppList.vue new file mode 100644 index 0000000..f5709f1 --- /dev/null +++ b/orion-ops-vue/src/views/app/AppList.vue @@ -0,0 +1,502 @@ + + + + + diff --git a/orion-ops-vue/src/views/app/AppPipeline.vue b/orion-ops-vue/src/views/app/AppPipeline.vue new file mode 100644 index 0000000..e9d3905 --- /dev/null +++ b/orion-ops-vue/src/views/app/AppPipeline.vue @@ -0,0 +1,278 @@ + + + + + diff --git a/orion-ops-vue/src/views/app/AppPipelineStatistics.vue b/orion-ops-vue/src/views/app/AppPipelineStatistics.vue new file mode 100644 index 0000000..ab43cd4 --- /dev/null +++ b/orion-ops-vue/src/views/app/AppPipelineStatistics.vue @@ -0,0 +1,176 @@ + + + + + diff --git a/orion-ops-vue/src/views/app/AppPipelineTask.vue b/orion-ops-vue/src/views/app/AppPipelineTask.vue new file mode 100644 index 0000000..71c2a11 --- /dev/null +++ b/orion-ops-vue/src/views/app/AppPipelineTask.vue @@ -0,0 +1,783 @@ + + + + + diff --git a/orion-ops-vue/src/views/app/AppProfile.vue b/orion-ops-vue/src/views/app/AppProfile.vue new file mode 100644 index 0000000..c261122 --- /dev/null +++ b/orion-ops-vue/src/views/app/AppProfile.vue @@ -0,0 +1,255 @@ + + + + + diff --git a/orion-ops-vue/src/views/app/AppRelease.vue b/orion-ops-vue/src/views/app/AppRelease.vue new file mode 100644 index 0000000..44e8458 --- /dev/null +++ b/orion-ops-vue/src/views/app/AppRelease.vue @@ -0,0 +1,794 @@ + + + + + diff --git a/orion-ops-vue/src/views/app/AppReleaseLogView.vue b/orion-ops-vue/src/views/app/AppReleaseLogView.vue new file mode 100644 index 0000000..a19d874 --- /dev/null +++ b/orion-ops-vue/src/views/app/AppReleaseLogView.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/orion-ops-vue/src/views/app/AppReleaseMachineLogView.vue b/orion-ops-vue/src/views/app/AppReleaseMachineLogView.vue new file mode 100644 index 0000000..822357c --- /dev/null +++ b/orion-ops-vue/src/views/app/AppReleaseMachineLogView.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/orion-ops-vue/src/views/app/AppReleaseStatistics.vue b/orion-ops-vue/src/views/app/AppReleaseStatistics.vue new file mode 100644 index 0000000..ddc6f88 --- /dev/null +++ b/orion-ops-vue/src/views/app/AppReleaseStatistics.vue @@ -0,0 +1,167 @@ + + + + + diff --git a/orion-ops-vue/src/views/app/AppRepository.vue b/orion-ops-vue/src/views/app/AppRepository.vue new file mode 100644 index 0000000..56825a6 --- /dev/null +++ b/orion-ops-vue/src/views/app/AppRepository.vue @@ -0,0 +1,333 @@ + + + + + diff --git a/orion-ops-vue/src/views/content/TemplateList.vue b/orion-ops-vue/src/views/content/TemplateList.vue new file mode 100644 index 0000000..46b7db6 --- /dev/null +++ b/orion-ops-vue/src/views/content/TemplateList.vue @@ -0,0 +1,261 @@ + + + + + diff --git a/orion-ops-vue/src/views/content/WebhookList.vue b/orion-ops-vue/src/views/content/WebhookList.vue new file mode 100644 index 0000000..cb0ebb5 --- /dev/null +++ b/orion-ops-vue/src/views/content/WebhookList.vue @@ -0,0 +1,230 @@ + + + + + diff --git a/orion-ops-vue/src/views/exec/BatchExecList.vue b/orion-ops-vue/src/views/exec/BatchExecList.vue new file mode 100644 index 0000000..2702747 --- /dev/null +++ b/orion-ops-vue/src/views/exec/BatchExecList.vue @@ -0,0 +1,559 @@ + + + + + diff --git a/orion-ops-vue/src/views/exec/BatchExecTask.vue b/orion-ops-vue/src/views/exec/BatchExecTask.vue new file mode 100644 index 0000000..551573e --- /dev/null +++ b/orion-ops-vue/src/views/exec/BatchExecTask.vue @@ -0,0 +1,446 @@ + + + + + diff --git a/orion-ops-vue/src/views/exec/BatchUploadFile.vue b/orion-ops-vue/src/views/exec/BatchUploadFile.vue new file mode 100644 index 0000000..5ebbb44 --- /dev/null +++ b/orion-ops-vue/src/views/exec/BatchUploadFile.vue @@ -0,0 +1,591 @@ + + + + + diff --git a/orion-ops-vue/src/views/exec/ExecLoggerView.vue b/orion-ops-vue/src/views/exec/ExecLoggerView.vue new file mode 100644 index 0000000..0203b6a --- /dev/null +++ b/orion-ops-vue/src/views/exec/ExecLoggerView.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/orion-ops-vue/src/views/log/LoggerList.vue b/orion-ops-vue/src/views/log/LoggerList.vue new file mode 100644 index 0000000..465e9d0 --- /dev/null +++ b/orion-ops-vue/src/views/log/LoggerList.vue @@ -0,0 +1,353 @@ + + + + + diff --git a/orion-ops-vue/src/views/log/LoggerView.vue b/orion-ops-vue/src/views/log/LoggerView.vue new file mode 100644 index 0000000..6b007eb --- /dev/null +++ b/orion-ops-vue/src/views/log/LoggerView.vue @@ -0,0 +1,358 @@ + + + + + diff --git a/orion-ops-vue/src/views/machine/AddMachine.vue b/orion-ops-vue/src/views/machine/AddMachine.vue new file mode 100644 index 0000000..a7f82b8 --- /dev/null +++ b/orion-ops-vue/src/views/machine/AddMachine.vue @@ -0,0 +1,452 @@ + + + + + diff --git a/orion-ops-vue/src/views/machine/MachineEnv.vue b/orion-ops-vue/src/views/machine/MachineEnv.vue new file mode 100644 index 0000000..652b957 --- /dev/null +++ b/orion-ops-vue/src/views/machine/MachineEnv.vue @@ -0,0 +1,502 @@ + + + + + diff --git a/orion-ops-vue/src/views/machine/MachineGroupView.vue b/orion-ops-vue/src/views/machine/MachineGroupView.vue new file mode 100644 index 0000000..1c4f3ac --- /dev/null +++ b/orion-ops-vue/src/views/machine/MachineGroupView.vue @@ -0,0 +1,365 @@ + + + + + diff --git a/orion-ops-vue/src/views/machine/MachineKey.vue b/orion-ops-vue/src/views/machine/MachineKey.vue new file mode 100644 index 0000000..fd8e8e8 --- /dev/null +++ b/orion-ops-vue/src/views/machine/MachineKey.vue @@ -0,0 +1,268 @@ + + + + + diff --git a/orion-ops-vue/src/views/machine/MachineList.vue b/orion-ops-vue/src/views/machine/MachineList.vue new file mode 100644 index 0000000..cc9e572 --- /dev/null +++ b/orion-ops-vue/src/views/machine/MachineList.vue @@ -0,0 +1,184 @@ + + + + + diff --git a/orion-ops-vue/src/views/machine/MachineListView.vue b/orion-ops-vue/src/views/machine/MachineListView.vue new file mode 100644 index 0000000..2bc6feb --- /dev/null +++ b/orion-ops-vue/src/views/machine/MachineListView.vue @@ -0,0 +1,500 @@ + + + + + diff --git a/orion-ops-vue/src/views/machine/MachineMonitorList.vue b/orion-ops-vue/src/views/machine/MachineMonitorList.vue new file mode 100644 index 0000000..fc2bcaf --- /dev/null +++ b/orion-ops-vue/src/views/machine/MachineMonitorList.vue @@ -0,0 +1,367 @@ + + + + + diff --git a/orion-ops-vue/src/views/machine/MachineMonitorMetrics.vue b/orion-ops-vue/src/views/machine/MachineMonitorMetrics.vue new file mode 100644 index 0000000..7711260 --- /dev/null +++ b/orion-ops-vue/src/views/machine/MachineMonitorMetrics.vue @@ -0,0 +1,169 @@ + + + + + diff --git a/orion-ops-vue/src/views/machine/MachineProxy.vue b/orion-ops-vue/src/views/machine/MachineProxy.vue new file mode 100644 index 0000000..96b9aab --- /dev/null +++ b/orion-ops-vue/src/views/machine/MachineProxy.vue @@ -0,0 +1,261 @@ + + + + + diff --git a/orion-ops-vue/src/views/machine/MachineSftp.vue b/orion-ops-vue/src/views/machine/MachineSftp.vue new file mode 100644 index 0000000..070daba --- /dev/null +++ b/orion-ops-vue/src/views/machine/MachineSftp.vue @@ -0,0 +1,128 @@ + + + + + diff --git a/orion-ops-vue/src/views/machine/MachineTerminal.vue b/orion-ops-vue/src/views/machine/MachineTerminal.vue new file mode 100644 index 0000000..f9e5f1d --- /dev/null +++ b/orion-ops-vue/src/views/machine/MachineTerminal.vue @@ -0,0 +1,173 @@ + + + + + diff --git a/orion-ops-vue/src/views/machine/MachineTerminalLogs.vue b/orion-ops-vue/src/views/machine/MachineTerminalLogs.vue new file mode 100644 index 0000000..05caf20 --- /dev/null +++ b/orion-ops-vue/src/views/machine/MachineTerminalLogs.vue @@ -0,0 +1,332 @@ + + + + + diff --git a/orion-ops-vue/src/views/machine/MachineTerminalSession.vue b/orion-ops-vue/src/views/machine/MachineTerminalSession.vue new file mode 100644 index 0000000..ba0edbc --- /dev/null +++ b/orion-ops-vue/src/views/machine/MachineTerminalSession.vue @@ -0,0 +1,239 @@ + + + + + diff --git a/orion-ops-vue/src/views/scheduler/SchedulerList.vue b/orion-ops-vue/src/views/scheduler/SchedulerList.vue new file mode 100644 index 0000000..ec6b3b9 --- /dev/null +++ b/orion-ops-vue/src/views/scheduler/SchedulerList.vue @@ -0,0 +1,327 @@ + + + + + diff --git a/orion-ops-vue/src/views/scheduler/SchedulerMachineLogView.vue b/orion-ops-vue/src/views/scheduler/SchedulerMachineLogView.vue new file mode 100644 index 0000000..9127f8c --- /dev/null +++ b/orion-ops-vue/src/views/scheduler/SchedulerMachineLogView.vue @@ -0,0 +1,20 @@ + + + + + diff --git a/orion-ops-vue/src/views/scheduler/SchedulerRecord.vue b/orion-ops-vue/src/views/scheduler/SchedulerRecord.vue new file mode 100644 index 0000000..6c7dbd2 --- /dev/null +++ b/orion-ops-vue/src/views/scheduler/SchedulerRecord.vue @@ -0,0 +1,632 @@ + + + + + diff --git a/orion-ops-vue/src/views/scheduler/SchedulerStatistics.vue b/orion-ops-vue/src/views/scheduler/SchedulerStatistics.vue new file mode 100644 index 0000000..d85cf78 --- /dev/null +++ b/orion-ops-vue/src/views/scheduler/SchedulerStatistics.vue @@ -0,0 +1,224 @@ + + + + + diff --git a/orion-ops-vue/src/views/scheduler/SchedulerTaskLogView.vue b/orion-ops-vue/src/views/scheduler/SchedulerTaskLogView.vue new file mode 100644 index 0000000..1c678b3 --- /dev/null +++ b/orion-ops-vue/src/views/scheduler/SchedulerTaskLogView.vue @@ -0,0 +1,20 @@ + + + + + diff --git a/orion-ops-vue/src/views/system/SystemEnv.vue b/orion-ops-vue/src/views/system/SystemEnv.vue new file mode 100644 index 0000000..4168028 --- /dev/null +++ b/orion-ops-vue/src/views/system/SystemEnv.vue @@ -0,0 +1,362 @@ + + + + + diff --git a/orion-ops-vue/src/views/system/SystemSetting.vue b/orion-ops-vue/src/views/system/SystemSetting.vue new file mode 100644 index 0000000..bf7663c --- /dev/null +++ b/orion-ops-vue/src/views/system/SystemSetting.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/orion-ops-vue/src/views/user/AlarmGroupList.vue b/orion-ops-vue/src/views/user/AlarmGroupList.vue new file mode 100644 index 0000000..460c4f3 --- /dev/null +++ b/orion-ops-vue/src/views/user/AlarmGroupList.vue @@ -0,0 +1,198 @@ + + + + + diff --git a/orion-ops-vue/src/views/user/UserDetail.vue b/orion-ops-vue/src/views/user/UserDetail.vue new file mode 100644 index 0000000..2cab482 --- /dev/null +++ b/orion-ops-vue/src/views/user/UserDetail.vue @@ -0,0 +1,69 @@ + + + + + diff --git a/orion-ops-vue/src/views/user/UserEventLogList.vue b/orion-ops-vue/src/views/user/UserEventLogList.vue new file mode 100644 index 0000000..a25c9a2 --- /dev/null +++ b/orion-ops-vue/src/views/user/UserEventLogList.vue @@ -0,0 +1,374 @@ + + + + + diff --git a/orion-ops-vue/src/views/user/UserList.vue b/orion-ops-vue/src/views/user/UserList.vue new file mode 100644 index 0000000..717b35c --- /dev/null +++ b/orion-ops-vue/src/views/user/UserList.vue @@ -0,0 +1,366 @@ + + + + + diff --git a/orion-ops-vue/vue.config.js b/orion-ops-vue/vue.config.js new file mode 100644 index 0000000..6012fa6 --- /dev/null +++ b/orion-ops-vue/vue.config.js @@ -0,0 +1,42 @@ +// 判断开发环境 +// const isProduction = process.env.NODE_ENV === 'production' + +module.exports = { + publicPath: '/ops/', + // 打包时不生成.map文件 + productionSourceMap: false, + devServer: { + port: 10010, + proxy: { + '/orion': { + target: 'http://localhost:9119', + ws: true, + secure: false, + changeOrigin: true + } + } + }, + transpileDependencies: true + // configureWebpack: config => { + // if (isProduction) { + // // 开启分离js + // config.optimization = { + // runtimeChunk: 'single', + // splitChunks: { + // chunks: 'all', + // maxInitialRequests: Infinity, + // minSize: 20000, + // cacheGroups: { + // vendor: { + // test: /[\\/]node_modules[\\/]/, + // name(module) { + // const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1] + // return `pkg.${packageName.replace('@', '')}` + // } + // } + // } + // } + // } + // } + // } +} diff --git a/orion-ops-vue/yarn.lock b/orion-ops-vue/yarn.lock new file mode 100644 index 0000000..23bc77b --- /dev/null +++ b/orion-ops-vue/yarn.lock @@ -0,0 +1,6896 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@achrinza/node-ipc@^9.2.5": + version "9.2.8" + resolved "https://registry.npmmirror.com/@achrinza/node-ipc/-/node-ipc-9.2.8.tgz#aabfe9fe84406c90bfb7319d5e68b5b517dd8686" + integrity sha512-DSzEEkbMYbAUVlhy7fg+BzccoRuSQzqHbIPGxGv19OJ2WKwS3/9ChAnQcII4g+GujcHhyJ8BUuOVAx/S5uAfQg== + dependencies: + "@node-ipc/js-queue" "2.0.3" + event-pubsub "4.3.0" + js-message "1.0.7" + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@ant-design/colors@^3.1.0": + version "3.2.2" + resolved "https://registry.npmmirror.com/@ant-design/colors/-/colors-3.2.2.tgz#5ad43d619e911f3488ebac303d606e66a8423903" + integrity sha512-YKgNbG2dlzqMhA9NtI3/pbY16m3Yl/EeWBRa+lB1X1YaYxHrxNexiQYCLTWO/uDvAjLFMEDU+zR901waBtMtjQ== + dependencies: + tinycolor2 "^1.4.1" + +"@ant-design/icons-vue@^2.0.0": + version "2.0.0" + resolved "https://registry.npmmirror.com/@ant-design/icons-vue/-/icons-vue-2.0.0.tgz#0357f5010a404e9f34a87a4b41b2a08df691dbce" + integrity sha512-2c0QQE5hL4N48k5NkPG5sdpMl9YnvyNhf0U7YkdZYDlLnspoRU7vIA0UK9eHBs6OpFLcJB6o8eJrIl2ajBskPg== + dependencies: + "@ant-design/colors" "^3.1.0" + babel-runtime "^6.26.0" + +"@ant-design/icons@^2.1.1": + version "2.1.1" + resolved "https://registry.npmmirror.com/@ant-design/icons/-/icons-2.1.1.tgz#7b9c08dffd4f5d41db667d9dbe5e0107d0bd9a4a" + integrity sha512-jCH+k2Vjlno4YWl6g535nHR09PwCEmTBKAG6VqF+rhkrSPRLfgpU2maagwbZPLjaHuU5Jd1DFQ2KJpQuI6uG8w== + +"@antv/adjust@^0.2.1": + version "0.2.5" + resolved "https://registry.npmmirror.com/@antv/adjust/-/adjust-0.2.5.tgz#bb37bb4a0a87ca3f4b660848bc9ac07f02bcf5db" + integrity sha512-MfWZOkD9CqXRES6MBGRNe27Q577a72EIwyMnE29wIlPliFvJfWwsrONddpGU7lilMpVKecS3WAzOoip3RfPTRQ== + dependencies: + "@antv/util" "~2.0.0" + tslib "^1.10.0" + +"@antv/attr@^0.3.1": + version "0.3.5" + resolved "https://registry.npmmirror.com/@antv/attr/-/attr-0.3.5.tgz#0708c74fed5ad6ee03ad1e2913099ed8248f7ebf" + integrity sha512-wuj2gUo6C8Q2ASSMrVBuTcb5LcV+Tc0Egiy6bC42D0vxcQ+ta13CLxgMmHz8mjD0FxTPJDXSciyszRSC5TdLsg== + dependencies: + "@antv/color-util" "^2.0.1" + "@antv/scale" "^0.3.0" + "@antv/util" "~2.0.0" + tslib "^2.3.1" + +"@antv/color-util@^2.0.1", "@antv/color-util@^2.0.2", "@antv/color-util@^2.0.3": + version "2.0.6" + resolved "https://registry.npmmirror.com/@antv/color-util/-/color-util-2.0.6.tgz#5e129bb9ce3f2b9309b52102b3dc929430ccc016" + integrity sha512-KnPEaAH+XNJMjax9U35W67nzPI+QQ2x27pYlzmSIWrbj4/k8PGrARXfzDTjwoozHJY8qG62Z+Ww6Alhu2FctXQ== + dependencies: + "@antv/util" "^2.0.9" + tslib "^2.0.3" + +"@antv/component@^0.8.27": + version "0.8.35" + resolved "https://registry.npmmirror.com/@antv/component/-/component-0.8.35.tgz#1d5b8e11bd496cb505e646f505f5f58f0c5173e9" + integrity sha512-VnRa5X77nBPI952o2xePEEMSNZ6g2mcUDrQY8mVL2kino/8TFhqDq5fTRmDXZyWyIYd4ulJTz5zgeSwAnX/INQ== + dependencies: + "@antv/color-util" "^2.0.3" + "@antv/dom-util" "~2.0.1" + "@antv/g-base" "^0.5.9" + "@antv/matrix-util" "^3.1.0-beta.1" + "@antv/path-util" "~2.0.7" + "@antv/scale" "~0.3.1" + "@antv/util" "~2.0.0" + fecha "~4.2.0" + tslib "^2.0.3" + +"@antv/coord@^0.3.0": + version "0.3.1" + resolved "https://registry.npmmirror.com/@antv/coord/-/coord-0.3.1.tgz#982e261d8a1e06a198eb518ea7acc20ed875a019" + integrity sha512-rFE94C8Xzbx4xmZnHh2AnlB3Qm1n5x0VT3OROy257IH6Rm4cuzv1+tZaUBATviwZd99S+rOY9telw/+6C9GbRw== + dependencies: + "@antv/matrix-util" "^3.1.0-beta.2" + "@antv/util" "~2.0.12" + tslib "^2.1.0" + +"@antv/dom-util@^2.0.2", "@antv/dom-util@~2.0.1": + version "2.0.4" + resolved "https://registry.npmmirror.com/@antv/dom-util/-/dom-util-2.0.4.tgz#b09b56c56fec42896fc856edad56b595b47ab514" + integrity sha512-2shXUl504fKwt82T3GkuT4Uoc6p9qjCKnJ8gXGLSW4T1W37dqf9AV28aCfoVPHp2BUXpSsB+PAJX2rG/jLHsLQ== + dependencies: + tslib "^2.0.3" + +"@antv/event-emitter@^0.1.1", "@antv/event-emitter@~0.1.0": + version "0.1.3" + resolved "https://registry.npmmirror.com/@antv/event-emitter/-/event-emitter-0.1.3.tgz#3e06323b9dcd55a3241ddc7c5458cfabd2095164" + integrity sha512-4ddpsiHN9Pd4UIlWuKVK1C4IiZIdbwQvy9i7DUSI3xNJ89FPUFt8lxDYj8GzzfdllV0NkJTRxnG+FvLk0llidg== + +"@antv/g-base@^0.5.12", "@antv/g-base@^0.5.9", "@antv/g-base@~0.5.6": + version "0.5.16" + resolved "https://registry.npmmirror.com/@antv/g-base/-/g-base-0.5.16.tgz#22a0cbbfc810e6292e4d25e5708d0abe165912bf" + integrity sha512-jP06wggTubDPHXoKwFg3/f1lyxBX9ywwN3E/HG74Nd7DXqOXQis8tsIWW+O6dS/h9vyuXLd1/wDWkMMm3ZzXdg== + dependencies: + "@antv/event-emitter" "^0.1.1" + "@antv/g-math" "^0.1.9" + "@antv/matrix-util" "^3.1.0-beta.1" + "@antv/path-util" "~2.0.5" + "@antv/util" "~2.0.13" + "@types/d3-timer" "^2.0.0" + d3-ease "^1.0.5" + d3-interpolate "^3.0.1" + d3-timer "^1.0.9" + detect-browser "^5.1.0" + tslib "^2.0.3" + +"@antv/g-canvas@~0.5.10": + version "0.5.17" + resolved "https://registry.npmmirror.com/@antv/g-canvas/-/g-canvas-0.5.17.tgz#2e0d263a355e167b9da5e606fbd1ad1500474fcf" + integrity sha512-sXYJMWTOlb/Ycb6sTKu00LcJqInXJY4t99+kSM40u2OfqrXYmaXDjHR7D2V0roMkbK/QWiWS9UnEidCR1VtMOA== + dependencies: + "@antv/g-base" "^0.5.12" + "@antv/g-math" "^0.1.9" + "@antv/matrix-util" "^3.1.0-beta.1" + "@antv/path-util" "~2.0.5" + "@antv/util" "~2.0.0" + gl-matrix "^3.0.0" + tslib "^2.0.3" + +"@antv/g-math@^0.1.9": + version "0.1.9" + resolved "https://registry.npmmirror.com/@antv/g-math/-/g-math-0.1.9.tgz#1f981b9aebf5c024f284389aa3e5cba8cefa1f28" + integrity sha512-KHMSfPfZ5XHM1PZnG42Q2gxXfOitYveNTA7L61lR6mhZ8Y/aExsYmHqaKBsSarU0z+6WLrl9C07PQJZaw0uljQ== + dependencies: + "@antv/util" "~2.0.0" + gl-matrix "^3.0.0" + +"@antv/g-svg@~0.5.6": + version "0.5.7" + resolved "https://registry.npmmirror.com/@antv/g-svg/-/g-svg-0.5.7.tgz#d63db5f8590a5f3ceab097c183ec80ed143f0a50" + integrity sha512-jUbWoPgr4YNsOat2Y/rGAouNQYGpw4R0cvlN0YafwOyacFFYy2zC8RslNd6KkPhhR3XHNSqJOuCYZj/YmLUwYw== + dependencies: + "@antv/g-base" "^0.5.12" + "@antv/g-math" "^0.1.9" + "@antv/util" "~2.0.0" + detect-browser "^5.0.0" + tslib "^2.0.3" + +"@antv/g2@^4.1.48": + version "4.2.11" + resolved "https://registry.npmmirror.com/@antv/g2/-/g2-4.2.11.tgz#a3b257aca4db6004a0c7fe002dc9272795f9c18b" + integrity sha512-QiqxLLYDWkv9c4oTcXscs6NMxBuWZ1JCarHPZ27J43IN2BV+qUKw8yce0A8CBR8fCILEFqQAfS00Szqpye036Q== + dependencies: + "@antv/adjust" "^0.2.1" + "@antv/attr" "^0.3.1" + "@antv/color-util" "^2.0.2" + "@antv/component" "^0.8.27" + "@antv/coord" "^0.3.0" + "@antv/dom-util" "^2.0.2" + "@antv/event-emitter" "~0.1.0" + "@antv/g-base" "~0.5.6" + "@antv/g-canvas" "~0.5.10" + "@antv/g-svg" "~0.5.6" + "@antv/matrix-util" "^3.1.0-beta.3" + "@antv/path-util" "^2.0.15" + "@antv/scale" "^0.3.14" + "@antv/util" "~2.0.5" + tslib "^2.0.0" + +"@antv/matrix-util@^3.0.4": + version "3.0.4" + resolved "https://registry.npmmirror.com/@antv/matrix-util/-/matrix-util-3.0.4.tgz#ea13f158aa2fb4ba2fb8d6b6b561ec467ea3ac20" + integrity sha512-BAPyu6dUliHcQ7fm9hZSGKqkwcjEDVLVAstlHULLvcMZvANHeLXgHEgV7JqcAV/GIhIz8aZChIlzM1ZboiXpYQ== + dependencies: + "@antv/util" "^2.0.9" + gl-matrix "^3.3.0" + tslib "^2.0.3" + +"@antv/matrix-util@^3.1.0-beta.1", "@antv/matrix-util@^3.1.0-beta.2", "@antv/matrix-util@^3.1.0-beta.3": + version "3.1.0-beta.3" + resolved "https://registry.npmmirror.com/@antv/matrix-util/-/matrix-util-3.1.0-beta.3.tgz#e061de8fa7be04605a155c69cc5ce9082eedddee" + integrity sha512-W2R6Za3A6CmG51Y/4jZUM/tFgYSq7vTqJL1VD9dKrvwxS4sE0ZcXINtkp55CdyBwJ6Cwm8pfoRpnD4FnHahN0A== + dependencies: + "@antv/util" "^2.0.9" + gl-matrix "^3.4.3" + tslib "^2.0.3" + +"@antv/path-util@^2.0.15", "@antv/path-util@~2.0.5", "@antv/path-util@~2.0.7": + version "2.0.15" + resolved "https://registry.npmmirror.com/@antv/path-util/-/path-util-2.0.15.tgz#a6f691dfc8b7bce5be7f0aabb5bd614964325631" + integrity sha512-R2VLZ5C8PLPtr3VciNyxtjKqJ0XlANzpFb5sE9GE61UQqSRuSVSzIakMxjEPrpqbgc+s+y8i+fmc89Snu7qbNw== + dependencies: + "@antv/matrix-util" "^3.0.4" + "@antv/util" "^2.0.9" + tslib "^2.0.3" + +"@antv/scale@^0.3.0", "@antv/scale@^0.3.14", "@antv/scale@~0.3.1": + version "0.3.18" + resolved "https://registry.npmmirror.com/@antv/scale/-/scale-0.3.18.tgz#b911f431b3e0b9547b6a65f66d0d3fa295b5ef32" + integrity sha512-GHwE6Lo7S/Q5fgaLPaCsW+CH+3zl4aXpnN1skOiEY0Ue9/u+s2EySv6aDXYkAqs//i0uilMDD/0/4n8caX9U9w== + dependencies: + "@antv/util" "~2.0.3" + fecha "~4.2.0" + tslib "^2.0.0" + +"@antv/util@^2.0.9", "@antv/util@~2.0.0", "@antv/util@~2.0.12", "@antv/util@~2.0.13", "@antv/util@~2.0.3", "@antv/util@~2.0.5": + version "2.0.17" + resolved "https://registry.npmmirror.com/@antv/util/-/util-2.0.17.tgz#e8ef42aca7892815b229269f3dd10c6b3c7597a9" + integrity sha512-o6I9hi5CIUvLGDhth0RxNSFDRwXeywmt6ExR4+RmVAzIi48ps6HUy+svxOCayvrPBN37uE6TAc2KDofRo0nK9Q== + dependencies: + csstype "^3.0.8" + tslib "^2.0.3" + +"@babel/code-frame@7.12.11": + version "7.12.11" + resolved "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== + dependencies: + "@babel/highlight" "^7.10.4" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.23.5", "@babel/code-frame@^7.24.2": + version "7.24.2" + resolved "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" + integrity sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ== + dependencies: + "@babel/highlight" "^7.24.2" + picocolors "^1.0.0" + +"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.23.5", "@babel/compat-data@^7.24.4": + version "7.24.4" + resolved "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.24.4.tgz#6f102372e9094f25d908ca0d34fc74c74606059a" + integrity sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ== + +"@babel/core@^7.12.16": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/core/-/core-7.24.5.tgz#15ab5b98e101972d171aeef92ac70d8d6718f06a" + integrity sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.2" + "@babel/generator" "^7.24.5" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-module-transforms" "^7.24.5" + "@babel/helpers" "^7.24.5" + "@babel/parser" "^7.24.5" + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.5" + "@babel/types" "^7.24.5" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/eslint-parser@^7.12.16": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/eslint-parser/-/eslint-parser-7.24.5.tgz#3b0f7d383a540329a30a6a9937cfc89461d26217" + integrity sha512-gsUcqS/fPlgAw1kOtpss7uhY6E9SFFANQ6EFX5GTvzUwaV0+sGaZWk6xq22MOdeT9wfxyokW3ceCUvOiRtZciQ== + dependencies: + "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1" + eslint-visitor-keys "^2.1.0" + semver "^6.3.1" + +"@babel/generator@^7.24.5": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/generator/-/generator-7.24.5.tgz#e5afc068f932f05616b66713e28d0f04e99daeb3" + integrity sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA== + dependencies: + "@babel/types" "^7.24.5" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + +"@babel/helper-annotate-as-pure@^7.22.5": + version "7.22.5" + resolved "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" + integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.22.15": + version "7.22.15" + resolved "https://registry.npmmirror.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz#5426b109cf3ad47b91120f8328d8ab1be8b0b956" + integrity sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw== + dependencies: + "@babel/types" "^7.22.15" + +"@babel/helper-compilation-targets@^7.12.16", "@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.23.6": + version "7.23.6" + resolved "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" + integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== + dependencies: + "@babel/compat-data" "^7.23.5" + "@babel/helper-validator-option" "^7.23.5" + browserslist "^4.22.2" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.24.1", "@babel/helper-create-class-features-plugin@^7.24.4", "@babel/helper-create-class-features-plugin@^7.24.5": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz#7d19da92c7e0cd8d11c09af2ce1b8e7512a6e723" + integrity sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-member-expression-to-functions" "^7.24.5" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-replace-supers" "^7.24.1" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.24.5" + semver "^6.3.1" + +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.15", "@babel/helper-create-regexp-features-plugin@^7.22.5": + version "7.22.15" + resolved "https://registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1" + integrity sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + regexpu-core "^5.3.1" + semver "^6.3.1" + +"@babel/helper-define-polyfill-provider@^0.6.1", "@babel/helper-define-polyfill-provider@^0.6.2": + version "0.6.2" + resolved "https://registry.npmmirror.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz#18594f789c3594acb24cfdb4a7f7b7d2e8bd912d" + integrity sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ== + dependencies: + "@babel/helper-compilation-targets" "^7.22.6" + "@babel/helper-plugin-utils" "^7.22.5" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + +"@babel/helper-environment-visitor@^7.22.20": + version "7.22.20" + resolved "https://registry.npmmirror.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" + integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== + +"@babel/helper-function-name@^7.23.0": + version "7.23.0" + resolved "https://registry.npmmirror.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" + integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== + dependencies: + "@babel/template" "^7.22.15" + "@babel/types" "^7.23.0" + +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.npmmirror.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-member-expression-to-functions@^7.23.0", "@babel/helper-member-expression-to-functions@^7.24.5": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.5.tgz#5981e131d5c7003c7d1fa1ad49e86c9b097ec475" + integrity sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA== + dependencies: + "@babel/types" "^7.24.5" + +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.24.1", "@babel/helper-module-imports@^7.24.3": + version "7.24.3" + resolved "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz#6ac476e6d168c7c23ff3ba3cf4f7841d46ac8128" + integrity sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg== + dependencies: + "@babel/types" "^7.24.0" + +"@babel/helper-module-imports@~7.22.15": + version "7.22.15" + resolved "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== + dependencies: + "@babel/types" "^7.22.15" + +"@babel/helper-module-transforms@^7.23.3", "@babel/helper-module-transforms@^7.24.5": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz#ea6c5e33f7b262a0ae762fd5986355c45f54a545" + integrity sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.24.3" + "@babel/helper-simple-access" "^7.24.5" + "@babel/helper-split-export-declaration" "^7.24.5" + "@babel/helper-validator-identifier" "^7.24.5" + +"@babel/helper-optimise-call-expression@^7.22.5": + version "7.22.5" + resolved "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" + integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.0", "@babel/helper-plugin-utils@^7.24.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz#a924607dd254a65695e5bd209b98b902b3b2f11a" + integrity sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ== + +"@babel/helper-remap-async-to-generator@^7.22.20": + version "7.22.20" + resolved "https://registry.npmmirror.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz#7b68e1cb4fa964d2996fd063723fb48eca8498e0" + integrity sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-wrap-function" "^7.22.20" + +"@babel/helper-replace-supers@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz#7085bd19d4a0b7ed8f405c1ed73ccb70f323abc1" + integrity sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-member-expression-to-functions" "^7.23.0" + "@babel/helper-optimise-call-expression" "^7.22.5" + +"@babel/helper-simple-access@^7.22.5", "@babel/helper-simple-access@^7.24.5": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz#50da5b72f58c16b07fbd992810be6049478e85ba" + integrity sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ== + dependencies: + "@babel/types" "^7.24.5" + +"@babel/helper-skip-transparent-expression-wrappers@^7.22.5": + version "7.22.5" + resolved "https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847" + integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-split-export-declaration@^7.24.5": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz#b9a67f06a46b0b339323617c8c6213b9055a78b6" + integrity sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q== + dependencies: + "@babel/types" "^7.24.5" + +"@babel/helper-string-parser@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz#f99c36d3593db9540705d0739a1f10b5e20c696e" + integrity sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ== + +"@babel/helper-validator-identifier@^7.22.20", "@babel/helper-validator-identifier@^7.24.5": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz#918b1a7fa23056603506370089bd990d8720db62" + integrity sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA== + +"@babel/helper-validator-option@^7.23.5": + version "7.23.5" + resolved "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" + integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== + +"@babel/helper-wrap-function@^7.22.20": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/helper-wrap-function/-/helper-wrap-function-7.24.5.tgz#335f934c0962e2c1ed1fb9d79e06a56115067c09" + integrity sha512-/xxzuNvgRl4/HLNKvnFwdhdgN3cpLxgLROeLDl83Yx0AJ1SGvq1ak0OszTOjDfiB8Vx03eJbeDWh9r+jCCWttw== + dependencies: + "@babel/helper-function-name" "^7.23.0" + "@babel/template" "^7.24.0" + "@babel/types" "^7.24.5" + +"@babel/helpers@^7.24.5": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.24.5.tgz#fedeb87eeafa62b621160402181ad8585a22a40a" + integrity sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q== + dependencies: + "@babel/template" "^7.24.0" + "@babel/traverse" "^7.24.5" + "@babel/types" "^7.24.5" + +"@babel/highlight@^7.10.4", "@babel/highlight@^7.24.2": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/highlight/-/highlight-7.24.5.tgz#bc0613f98e1dd0720e99b2a9ee3760194a704b6e" + integrity sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw== + dependencies: + "@babel/helper-validator-identifier" "^7.24.5" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/parser@^7.23.5", "@babel/parser@^7.23.9", "@babel/parser@^7.24.0", "@babel/parser@^7.24.4", "@babel/parser@^7.24.5": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.24.5.tgz#4a4d5ab4315579e5398a82dcf636ca80c3392790" + integrity sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg== + +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.5": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.5.tgz#4c3685eb9cd790bcad2843900fe0250c91ccf895" + integrity sha512-LdXRi1wEMTrHVR4Zc9F8OewC3vdm5h4QB6L71zy6StmYeqGi1b3ttIO8UC+BfZKcH9jdr4aI249rBkm+3+YvHw== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-plugin-utils" "^7.24.5" + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz#b645d9ba8c2bc5b7af50f0fe949f9edbeb07c8cf" + integrity sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz#da8261f2697f0f41b0855b91d3a20a1fbfd271d3" + integrity sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-transform-optional-chaining" "^7.24.1" + +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz#1181d9685984c91d657b8ddf14f0487a6bab2988" + integrity sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-proposal-class-properties@^7.12.13": + version "7.18.6" + resolved "https://registry.npmmirror.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" + integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-proposal-decorators@^7.12.13": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.24.1.tgz#bab2b9e174a2680f0a80f341f3ec70f809f8bb4b" + integrity sha512-zPEvzFijn+hRvJuX2Vu3KbEBN39LN3f7tW3MQO2LsIs57B26KU+kUc82BdAktS1VCM6libzh45eKGI65lg0cpA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-decorators" "^7.24.1" + +"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": + version "7.21.0-placeholder-for-preset-env.2" + resolved "https://registry.npmmirror.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" + integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-decorators@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.24.1.tgz#71d9ad06063a6ac5430db126b5df48c70ee885fa" + integrity sha512-05RJdO/cCrtVWuAaSn1tS3bH8jbsJa/Y1uD186u6J4C/1mnHFxseeuWpsqr9anvo7TUulev7tm7GDwRV+VuhDw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-syntax-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-export-namespace-from@^7.8.3": + version "7.8.3" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" + integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-import-assertions@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz#db3aad724153a00eaac115a3fb898de544e34971" + integrity sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-syntax-import-attributes@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz#c66b966c63b714c4eec508fcf5763b1f2d381093" + integrity sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@^7.12.13", "@babel/plugin-syntax-jsx@^7.2.0", "@babel/plugin-syntax-jsx@^7.23.3": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.1.tgz#3f6ca04b8c841811dbc3c5c5f837934e0d626c10" + integrity sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-unicode-sets-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.npmmirror.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" + integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-arrow-functions@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz#2bf263617060c9cc45bcdbf492b8cc805082bf27" + integrity sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-async-generator-functions@^7.24.3": + version "7.24.3" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.3.tgz#8fa7ae481b100768cc9842c8617808c5352b8b89" + integrity sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-remap-async-to-generator" "^7.22.20" + "@babel/plugin-syntax-async-generators" "^7.8.4" + +"@babel/plugin-transform-async-to-generator@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz#0e220703b89f2216800ce7b1c53cb0cf521c37f4" + integrity sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw== + dependencies: + "@babel/helper-module-imports" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-remap-async-to-generator" "^7.22.20" + +"@babel/plugin-transform-block-scoped-functions@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz#1c94799e20fcd5c4d4589523bbc57b7692979380" + integrity sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-block-scoping@^7.24.5": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.5.tgz#89574191397f85661d6f748d4b89ee4d9ee69a2a" + integrity sha512-sMfBc3OxghjC95BkYrYocHL3NaOplrcaunblzwXhGmlPwpmfsxr4vK+mBBt49r+S240vahmv+kUxkeKgs+haCw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.5" + +"@babel/plugin-transform-class-properties@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz#bcbf1aef6ba6085cfddec9fc8d58871cf011fc29" + integrity sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-class-static-block@^7.24.4": + version "7.24.4" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz#1a4653c0cf8ac46441ec406dece6e9bc590356a4" + integrity sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.4" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + +"@babel/plugin-transform-classes@^7.24.5": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.5.tgz#05e04a09df49a46348299a0e24bfd7e901129339" + integrity sha512-gWkLP25DFj2dwe9Ck8uwMOpko4YsqyfZJrOmqqcegeDYEbp7rmn4U6UQZNj08UF6MaX39XenSpKRCvpDRBtZ7Q== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-plugin-utils" "^7.24.5" + "@babel/helper-replace-supers" "^7.24.1" + "@babel/helper-split-export-declaration" "^7.24.5" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz#bc7e787f8e021eccfb677af5f13c29a9934ed8a7" + integrity sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/template" "^7.24.0" + +"@babel/plugin-transform-destructuring@^7.24.5": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.5.tgz#80843ee6a520f7362686d1a97a7b53544ede453c" + integrity sha512-SZuuLyfxvsm+Ah57I/i1HVjveBENYK9ue8MJ7qkc7ndoNjqquJiElzA7f5yaAXjyW2hKojosOTAQQRX50bPSVg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.5" + +"@babel/plugin-transform-dotall-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz#d56913d2f12795cc9930801b84c6f8c47513ac13" + integrity sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-duplicate-keys@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz#5347a797fe82b8d09749d10e9f5b83665adbca88" + integrity sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-dynamic-import@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz#2a5a49959201970dd09a5fca856cb651e44439dd" + integrity sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + +"@babel/plugin-transform-exponentiation-operator@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz#6650ebeb5bd5c012d5f5f90a26613a08162e8ba4" + integrity sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.15" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-export-namespace-from@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz#f033541fc036e3efb2dcb58eedafd4f6b8078acd" + integrity sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + +"@babel/plugin-transform-for-of@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz#67448446b67ab6c091360ce3717e7d3a59e202fd" + integrity sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + +"@babel/plugin-transform-function-name@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz#8cba6f7730626cc4dfe4ca2fa516215a0592b361" + integrity sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA== + dependencies: + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-json-strings@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz#08e6369b62ab3e8a7b61089151b161180c8299f7" + integrity sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-json-strings" "^7.8.3" + +"@babel/plugin-transform-literals@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz#0a1982297af83e6b3c94972686067df588c5c096" + integrity sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-logical-assignment-operators@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz#719d8aded1aa94b8fb34e3a785ae8518e24cfa40" + integrity sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + +"@babel/plugin-transform-member-expression-literals@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz#896d23601c92f437af8b01371ad34beb75df4489" + integrity sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-modules-amd@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz#b6d829ed15258536977e9c7cc6437814871ffa39" + integrity sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ== + dependencies: + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-modules-commonjs@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz#e71ba1d0d69e049a22bf90b3867e263823d3f1b9" + integrity sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw== + dependencies: + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-simple-access" "^7.22.5" + +"@babel/plugin-transform-modules-systemjs@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz#2b9625a3d4e445babac9788daec39094e6b11e3e" + integrity sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA== + dependencies: + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-validator-identifier" "^7.22.20" + +"@babel/plugin-transform-modules-umd@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz#69220c66653a19cf2c0872b9c762b9a48b8bebef" + integrity sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg== + dependencies: + "@babel/helper-module-transforms" "^7.23.3" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.22.5": + version "7.22.5" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz#67fe18ee8ce02d57c855185e27e3dc959b2e991f" + integrity sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.5" + "@babel/helper-plugin-utils" "^7.22.5" + +"@babel/plugin-transform-new-target@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz#29c59988fa3d0157de1c871a28cd83096363cc34" + integrity sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-nullish-coalescing-operator@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz#0cd494bb97cb07d428bd651632cb9d4140513988" + integrity sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + +"@babel/plugin-transform-numeric-separator@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz#5bc019ce5b3435c1cadf37215e55e433d674d4e8" + integrity sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + +"@babel/plugin-transform-object-rest-spread@^7.24.5": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.5.tgz#f91bbcb092ff957c54b4091c86bda8372f0b10ef" + integrity sha512-7EauQHszLGM3ay7a161tTQH7fj+3vVM/gThlz5HpFtnygTxjrlvoeq7MPVA1Vy9Q555OB8SnAOsMkLShNkkrHA== + dependencies: + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-plugin-utils" "^7.24.5" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.24.5" + +"@babel/plugin-transform-object-super@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz#e71d6ab13483cca89ed95a474f542bbfc20a0520" + integrity sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-replace-supers" "^7.24.1" + +"@babel/plugin-transform-optional-catch-binding@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz#92a3d0efe847ba722f1a4508669b23134669e2da" + integrity sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + +"@babel/plugin-transform-optional-chaining@^7.24.1", "@babel/plugin-transform-optional-chaining@^7.24.5": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.5.tgz#a6334bebd7f9dd3df37447880d0bd64b778e600f" + integrity sha512-xWCkmwKT+ihmA6l7SSTpk8e4qQl/274iNbSKRRS8mpqFR32ksy36+a+LWY8OXCCEefF8WFlnOHVsaDI2231wBg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-transform-parameters@^7.24.5": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.5.tgz#5c3b23f3a6b8fed090f9b98f2926896d3153cc62" + integrity sha512-9Co00MqZ2aoky+4j2jhofErthm6QVLKbpQrvz20c3CH9KQCLHyNB+t2ya4/UrRpQGR+Wrwjg9foopoeSdnHOkA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.5" + +"@babel/plugin-transform-private-methods@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz#a0faa1ae87eff077e1e47a5ec81c3aef383dc15a" + integrity sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.1" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-private-property-in-object@^7.24.5": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.5.tgz#f5d1fcad36e30c960134cb479f1ca98a5b06eda5" + integrity sha512-JM4MHZqnWR04jPMujQDTBVRnqxpLLpx2tkn7iPn+Hmsc0Gnb79yvRWOkvqFOx3Z7P7VxiRIR22c4eGSNj87OBQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.24.5" + "@babel/helper-plugin-utils" "^7.24.5" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + +"@babel/plugin-transform-property-literals@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz#d6a9aeab96f03749f4eebeb0b6ea8e90ec958825" + integrity sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-regenerator@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz#625b7545bae52363bdc1fbbdc7252b5046409c8c" + integrity sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + regenerator-transform "^0.15.2" + +"@babel/plugin-transform-reserved-words@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz#8de729f5ecbaaf5cf83b67de13bad38a21be57c1" + integrity sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-runtime@^7.12.15": + version "7.24.3" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.3.tgz#dc58ad4a31810a890550365cc922e1ff5acb5d7f" + integrity sha512-J0BuRPNlNqlMTRJ72eVptpt9VcInbxO6iP3jaxr+1NPhC0UkKL+6oeX6VXMEYdADnuqmMmsBspt4d5w8Y/TCbQ== + dependencies: + "@babel/helper-module-imports" "^7.24.3" + "@babel/helper-plugin-utils" "^7.24.0" + babel-plugin-polyfill-corejs2 "^0.4.10" + babel-plugin-polyfill-corejs3 "^0.10.1" + babel-plugin-polyfill-regenerator "^0.6.1" + semver "^6.3.1" + +"@babel/plugin-transform-shorthand-properties@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz#ba9a09144cf55d35ec6b93a32253becad8ee5b55" + integrity sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-spread@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz#a1acf9152cbf690e4da0ba10790b3ac7d2b2b391" + integrity sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" + +"@babel/plugin-transform-sticky-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz#f03e672912c6e203ed8d6e0271d9c2113dc031b9" + integrity sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-template-literals@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz#15e2166873a30d8617e3e2ccadb86643d327aab7" + integrity sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-typeof-symbol@^7.24.5": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.5.tgz#703cace5ef74155fb5eecab63cbfc39bdd25fe12" + integrity sha512-UTGnhYVZtTAjdwOTzT+sCyXmTn8AhaxOS/MjG9REclZ6ULHWF9KoCZur0HSGU7hk8PdBFKKbYe6+gqdXWz84Jg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.5" + +"@babel/plugin-transform-unicode-escapes@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz#fb3fa16676549ac7c7449db9b342614985c2a3a4" + integrity sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-unicode-property-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz#56704fd4d99da81e5e9f0c0c93cabd91dbc4889e" + integrity sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-unicode-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz#57c3c191d68f998ac46b708380c1ce4d13536385" + integrity sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/plugin-transform-unicode-sets-regex@^7.24.1": + version "7.24.1" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz#c1ea175b02afcffc9cf57a9c4658326625165b7f" + integrity sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.22.15" + "@babel/helper-plugin-utils" "^7.24.0" + +"@babel/preset-env@^7.12.16": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/preset-env/-/preset-env-7.24.5.tgz#6a9ac90bd5a5a9dae502af60dfc58c190551bbcd" + integrity sha512-UGK2ifKtcC8i5AI4cH+sbLLuLc2ktYSFJgBAXorKAsHUZmrQ1q6aQ6i3BvU24wWs2AAKqQB6kq3N9V9Gw1HiMQ== + dependencies: + "@babel/compat-data" "^7.24.4" + "@babel/helper-compilation-targets" "^7.23.6" + "@babel/helper-plugin-utils" "^7.24.5" + "@babel/helper-validator-option" "^7.23.5" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.24.5" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.24.1" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.24.1" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.24.1" + "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.24.1" + "@babel/plugin-syntax-import-attributes" "^7.24.1" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.24.1" + "@babel/plugin-transform-async-generator-functions" "^7.24.3" + "@babel/plugin-transform-async-to-generator" "^7.24.1" + "@babel/plugin-transform-block-scoped-functions" "^7.24.1" + "@babel/plugin-transform-block-scoping" "^7.24.5" + "@babel/plugin-transform-class-properties" "^7.24.1" + "@babel/plugin-transform-class-static-block" "^7.24.4" + "@babel/plugin-transform-classes" "^7.24.5" + "@babel/plugin-transform-computed-properties" "^7.24.1" + "@babel/plugin-transform-destructuring" "^7.24.5" + "@babel/plugin-transform-dotall-regex" "^7.24.1" + "@babel/plugin-transform-duplicate-keys" "^7.24.1" + "@babel/plugin-transform-dynamic-import" "^7.24.1" + "@babel/plugin-transform-exponentiation-operator" "^7.24.1" + "@babel/plugin-transform-export-namespace-from" "^7.24.1" + "@babel/plugin-transform-for-of" "^7.24.1" + "@babel/plugin-transform-function-name" "^7.24.1" + "@babel/plugin-transform-json-strings" "^7.24.1" + "@babel/plugin-transform-literals" "^7.24.1" + "@babel/plugin-transform-logical-assignment-operators" "^7.24.1" + "@babel/plugin-transform-member-expression-literals" "^7.24.1" + "@babel/plugin-transform-modules-amd" "^7.24.1" + "@babel/plugin-transform-modules-commonjs" "^7.24.1" + "@babel/plugin-transform-modules-systemjs" "^7.24.1" + "@babel/plugin-transform-modules-umd" "^7.24.1" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5" + "@babel/plugin-transform-new-target" "^7.24.1" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.24.1" + "@babel/plugin-transform-numeric-separator" "^7.24.1" + "@babel/plugin-transform-object-rest-spread" "^7.24.5" + "@babel/plugin-transform-object-super" "^7.24.1" + "@babel/plugin-transform-optional-catch-binding" "^7.24.1" + "@babel/plugin-transform-optional-chaining" "^7.24.5" + "@babel/plugin-transform-parameters" "^7.24.5" + "@babel/plugin-transform-private-methods" "^7.24.1" + "@babel/plugin-transform-private-property-in-object" "^7.24.5" + "@babel/plugin-transform-property-literals" "^7.24.1" + "@babel/plugin-transform-regenerator" "^7.24.1" + "@babel/plugin-transform-reserved-words" "^7.24.1" + "@babel/plugin-transform-shorthand-properties" "^7.24.1" + "@babel/plugin-transform-spread" "^7.24.1" + "@babel/plugin-transform-sticky-regex" "^7.24.1" + "@babel/plugin-transform-template-literals" "^7.24.1" + "@babel/plugin-transform-typeof-symbol" "^7.24.5" + "@babel/plugin-transform-unicode-escapes" "^7.24.1" + "@babel/plugin-transform-unicode-property-regex" "^7.24.1" + "@babel/plugin-transform-unicode-regex" "^7.24.1" + "@babel/plugin-transform-unicode-sets-regex" "^7.24.1" + "@babel/preset-modules" "0.1.6-no-external-plugins" + babel-plugin-polyfill-corejs2 "^0.4.10" + babel-plugin-polyfill-corejs3 "^0.10.4" + babel-plugin-polyfill-regenerator "^0.6.1" + core-js-compat "^3.31.0" + semver "^6.3.1" + +"@babel/preset-modules@0.1.6-no-external-plugins": + version "0.1.6-no-external-plugins" + resolved "https://registry.npmmirror.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" + integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/regjsgen@^0.8.0": + version "0.8.0" + resolved "https://registry.npmmirror.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" + integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== + +"@babel/runtime@^7.12.13", "@babel/runtime@^7.21.0", "@babel/runtime@^7.8.4": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.24.5.tgz#230946857c053a36ccc66e1dd03b17dd0c4ed02c" + integrity sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g== + dependencies: + regenerator-runtime "^0.14.0" + +"@babel/template@^7.22.15", "@babel/template@^7.23.9", "@babel/template@^7.24.0": + version "7.24.0" + resolved "https://registry.npmmirror.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" + integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/parser" "^7.24.0" + "@babel/types" "^7.24.0" + +"@babel/traverse@^7.23.9", "@babel/traverse@^7.24.5": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.24.5.tgz#972aa0bc45f16983bf64aa1f877b2dd0eea7e6f8" + integrity sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA== + dependencies: + "@babel/code-frame" "^7.24.2" + "@babel/generator" "^7.24.5" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.24.5" + "@babel/parser" "^7.24.5" + "@babel/types" "^7.24.5" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.9", "@babel/types@^7.24.0", "@babel/types@^7.24.5", "@babel/types@^7.4.4": + version "7.24.5" + resolved "https://registry.npmmirror.com/@babel/types/-/types-7.24.5.tgz#7661930afc638a5383eb0c4aee59b74f38db84d7" + integrity sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ== + dependencies: + "@babel/helper-string-parser" "^7.24.1" + "@babel/helper-validator-identifier" "^7.24.5" + to-fast-properties "^2.0.0" + +"@discoveryjs/json-ext@0.5.7": + version "0.5.7" + resolved "https://registry.npmmirror.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" + integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== + +"@eslint/eslintrc@^0.4.3": + version "0.4.3" + resolved "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" + integrity sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw== + dependencies: + ajv "^6.12.4" + debug "^4.1.1" + espree "^7.3.0" + globals "^13.9.0" + ignore "^4.0.6" + import-fresh "^3.2.1" + js-yaml "^3.13.1" + minimatch "^3.0.4" + strip-json-comments "^3.1.1" + +"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": + version "9.3.0" + resolved "https://registry.npmmirror.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" + integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== + +"@hapi/topo@^5.1.0": + version "5.1.0" + resolved "https://registry.npmmirror.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" + integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@humanwhocodes/config-array@^0.5.0": + version "0.5.0" + resolved "https://registry.npmmirror.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" + integrity sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg== + dependencies: + "@humanwhocodes/object-schema" "^1.2.0" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.0": + version "1.2.1" + resolved "https://registry.npmmirror.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/source-map@^0.3.3": + version "0.3.6" + resolved "https://registry.npmmirror.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" + integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": + version "1.4.15" + resolved "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@leichtgewicht/ip-codec@^2.0.1": + version "2.0.5" + resolved "https://registry.npmmirror.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1" + integrity sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw== + +"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": + version "5.1.1-v1" + resolved "https://registry.npmmirror.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" + integrity sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg== + dependencies: + eslint-scope "5.1.1" + +"@node-ipc/js-queue@2.0.3": + version "2.0.3" + resolved "https://registry.npmmirror.com/@node-ipc/js-queue/-/js-queue-2.0.3.tgz#ac7fe33d766fa53e233ef8fedaf3443a01c5a4cd" + integrity sha512-fL1wpr8hhD5gT2dA1qifeVaoDFlQR5es8tFuKqjHX+kdOtdNHnxkVZbtIrR2rxnMFvehkjaZRNV2H/gPXlb0hw== + dependencies: + easy-stack "1.0.1" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@polka/url@^1.0.0-next.24": + version "1.0.0-next.25" + resolved "https://registry.npmmirror.com/@polka/url/-/url-1.0.0-next.25.tgz#f077fdc0b5d0078d30893396ff4827a13f99e817" + integrity sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ== + +"@sideway/address@^4.1.5": + version "4.1.5" + resolved "https://registry.npmmirror.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" + integrity sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.1": + version "3.0.1" + resolved "https://registry.npmmirror.com/@sideway/formula/-/formula-3.0.1.tgz#80fcbcbaf7ce031e0ef2dd29b1bfc7c3f583611f" + integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.npmmirror.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + +"@simonwep/pickr@~1.7.0": + version "1.7.4" + resolved "https://registry.npmmirror.com/@simonwep/pickr/-/pickr-1.7.4.tgz#b14fcd945890388b870cd6db4d6c78d531f25141" + integrity sha512-fq7jgKJT21uWGC1mARBHvvd1JYlEf93o7SuVOB4Lr0x/2UPuNC9Oe9n/GzVeg4oVtqMDfh1wIEJpsdOJEZb+3g== + dependencies: + core-js "^3.6.5" + nanopop "^2.1.0" + +"@soda/friendly-errors-webpack-plugin@^1.8.0": + version "1.8.1" + resolved "https://registry.npmmirror.com/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.1.tgz#4d4fbb1108993aaa362116247c3d18188a2c6c85" + integrity sha512-h2ooWqP8XuFqTXT+NyAFbrArzfQA7R6HTezADrvD9Re8fxMLTPPniLdqVTdDaO0eIoLaAwKT+d6w+5GeTk7Vbg== + dependencies: + chalk "^3.0.0" + error-stack-parser "^2.0.6" + string-width "^4.2.3" + strip-ansi "^6.0.1" + +"@soda/get-current-script@^1.0.2": + version "1.0.2" + resolved "https://registry.npmmirror.com/@soda/get-current-script/-/get-current-script-1.0.2.tgz#a53515db25d8038374381b73af20bb4f2e508d87" + integrity sha512-T7VNNlYVM1SgQ+VsMYhnDkcGmWhQdL0bDyGm5TlQ3GBXnJscEClUUOKduWTmm2zCnvNLC1hc3JpuXjs/nFOc5w== + +"@trysound/sax@0.2.0": + version "0.2.0" + resolved "https://registry.npmmirror.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" + integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== + +"@types/body-parser@*": + version "1.19.5" + resolved "https://registry.npmmirror.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" + integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/bonjour@^3.5.9": + version "3.5.13" + resolved "https://registry.npmmirror.com/@types/bonjour/-/bonjour-3.5.13.tgz#adf90ce1a105e81dd1f9c61fdc5afda1bfb92956" + integrity sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ== + dependencies: + "@types/node" "*" + +"@types/connect-history-api-fallback@^1.3.5": + version "1.5.4" + resolved "https://registry.npmmirror.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz#7de71645a103056b48ac3ce07b3520b819c1d5b3" + integrity sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw== + dependencies: + "@types/express-serve-static-core" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.npmmirror.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/d3-timer@^2.0.0": + version "2.0.3" + resolved "https://registry.npmmirror.com/@types/d3-timer/-/d3-timer-2.0.3.tgz#d74350a9eb5991f054b2cf8e92efaf22be3e1a25" + integrity sha512-jhAJzaanK5LqyLQ50jJNIrB8fjL9gwWZTgYjevPvkDLMU+kTAZkYsobI59nYoeSrH1PucuyJEi247Pb90t6XUg== + +"@types/eslint-scope@^3.7.3": + version "3.7.7" + resolved "https://registry.npmmirror.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" + integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*", "@types/eslint@^7.29.0 || ^8.4.1": + version "8.56.10" + resolved "https://registry.npmmirror.com/@types/eslint/-/eslint-8.56.10.tgz#eb2370a73bf04a901eeba8f22595c7ee0f7eb58d" + integrity sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*", "@types/estree@^1.0.5": + version "1.0.5" + resolved "https://registry.npmmirror.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": + version "4.19.1" + resolved "https://registry.npmmirror.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.1.tgz#57d34698bb580720fd6e3c360d4b2fdef579b979" + integrity sha512-ej0phymbFLoCB26dbbq5PGScsf2JAJ4IJHjG10LalgUV36XKTmA4GdA+PVllKvRk0sEKt64X8975qFnkSi0hqA== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@*", "@types/express@^4.17.13": + version "4.17.21" + resolved "https://registry.npmmirror.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" + integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/html-minifier-terser@^6.0.0": + version "6.1.0" + resolved "https://registry.npmmirror.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" + integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== + +"@types/http-errors@*": + version "2.0.4" + resolved "https://registry.npmmirror.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" + integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== + +"@types/http-proxy@^1.17.8": + version "1.17.14" + resolved "https://registry.npmmirror.com/@types/http-proxy/-/http-proxy-1.17.14.tgz#57f8ccaa1c1c3780644f8a94f9c6b5000b5e2eec" + integrity sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w== + dependencies: + "@types/node" "*" + +"@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": + version "7.0.15" + resolved "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/mime@^1": + version "1.3.5" + resolved "https://registry.npmmirror.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" + integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== + +"@types/minimist@^1.2.0": + version "1.2.5" + resolved "https://registry.npmmirror.com/@types/minimist/-/minimist-1.2.5.tgz#ec10755e871497bcd83efe927e43ec46e8c0747e" + integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag== + +"@types/node-forge@^1.3.0": + version "1.3.11" + resolved "https://registry.npmmirror.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" + integrity sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ== + dependencies: + "@types/node" "*" + +"@types/node@*": + version "20.12.12" + resolved "https://registry.npmmirror.com/@types/node/-/node-20.12.12.tgz#7cbecdf902085cec634fdb362172dfe12b8f2050" + integrity sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw== + dependencies: + undici-types "~5.26.4" + +"@types/normalize-package-data@^2.4.0": + version "2.4.4" + resolved "https://registry.npmmirror.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901" + integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA== + +"@types/parse-json@^4.0.0": + version "4.0.2" + resolved "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" + integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== + +"@types/qs@*": + version "6.9.15" + resolved "https://registry.npmmirror.com/@types/qs/-/qs-6.9.15.tgz#adde8a060ec9c305a82de1babc1056e73bd64dce" + integrity sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.npmmirror.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + +"@types/retry@0.12.0": + version "0.12.0" + resolved "https://registry.npmmirror.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" + integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== + +"@types/send@*": + version "0.17.4" + resolved "https://registry.npmmirror.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" + integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-index@^1.9.1": + version "1.9.4" + resolved "https://registry.npmmirror.com/@types/serve-index/-/serve-index-1.9.4.tgz#e6ae13d5053cb06ed36392110b4f9a49ac4ec898" + integrity sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug== + dependencies: + "@types/express" "*" + +"@types/serve-static@*", "@types/serve-static@^1.13.10": + version "1.15.7" + resolved "https://registry.npmmirror.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714" + integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + "@types/send" "*" + +"@types/sockjs@^0.3.33": + version "0.3.36" + resolved "https://registry.npmmirror.com/@types/sockjs/-/sockjs-0.3.36.tgz#ce322cf07bcc119d4cbf7f88954f3a3bd0f67535" + integrity sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q== + dependencies: + "@types/node" "*" + +"@types/ws@^8.5.5": + version "8.5.10" + resolved "https://registry.npmmirror.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" + integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== + dependencies: + "@types/node" "*" + +"@vue/babel-helper-vue-jsx-merge-props@^1.4.0": + version "1.4.0" + resolved "https://registry.npmmirror.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.4.0.tgz#8d53a1e21347db8edbe54d339902583176de09f2" + integrity sha512-JkqXfCkUDp4PIlFdDQ0TdXoIejMtTHP67/pvxlgeY+u5k3LEdKuWZ3LK6xkxo52uDoABIVyRwqVkfLQJhk7VBA== + +"@vue/babel-helper-vue-transform-on@1.2.2": + version "1.2.2" + resolved "https://registry.npmmirror.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.2.2.tgz#7f1f817a4f00ad531651a8d1d22e22d9e42807ef" + integrity sha512-nOttamHUR3YzdEqdM/XXDyCSdxMA9VizUKoroLX6yTyRtggzQMHXcmwh8a7ZErcJttIBIc9s68a1B8GZ+Dmvsw== + +"@vue/babel-plugin-jsx@^1.0.3": + version "1.2.2" + resolved "https://registry.npmmirror.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.2.2.tgz#eb426fb4660aa510bb8d188ff0ec140405a97d8a" + integrity sha512-nYTkZUVTu4nhP199UoORePsql0l+wj7v/oyQjtThUVhJl1U+6qHuoVhIvR3bf7eVKjbCK+Cs2AWd7mi9Mpz9rA== + dependencies: + "@babel/helper-module-imports" "~7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/plugin-syntax-jsx" "^7.23.3" + "@babel/template" "^7.23.9" + "@babel/traverse" "^7.23.9" + "@babel/types" "^7.23.9" + "@vue/babel-helper-vue-transform-on" "1.2.2" + "@vue/babel-plugin-resolve-type" "1.2.2" + camelcase "^6.3.0" + html-tags "^3.3.1" + svg-tags "^1.0.0" + +"@vue/babel-plugin-resolve-type@1.2.2": + version "1.2.2" + resolved "https://registry.npmmirror.com/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.2.2.tgz#66844898561da6449e0f4a261b0c875118e0707b" + integrity sha512-EntyroPwNg5IPVdUJupqs0CFzuf6lUrVvCspmv2J1FITLeGnUCuoGNNk78dgCusxEiYj6RMkTJflGSxk5aIC4A== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/helper-module-imports" "~7.22.15" + "@babel/helper-plugin-utils" "^7.22.5" + "@babel/parser" "^7.23.9" + "@vue/compiler-sfc" "^3.4.15" + +"@vue/babel-plugin-transform-vue-jsx@^1.4.0": + version "1.4.0" + resolved "https://registry.npmmirror.com/@vue/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-1.4.0.tgz#4d4b3d46a39ea62b7467dd6e26ce47f7ceafb2fe" + integrity sha512-Fmastxw4MMx0vlgLS4XBX0XiBbUFzoMGeVXuMV08wyOfXdikAFqBTuYPR0tlk+XskL19EzHc39SgjrPGY23JnA== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/plugin-syntax-jsx" "^7.2.0" + "@vue/babel-helper-vue-jsx-merge-props" "^1.4.0" + html-tags "^2.0.0" + lodash.kebabcase "^4.1.1" + svg-tags "^1.0.0" + +"@vue/babel-preset-app@^5.0.8": + version "5.0.8" + resolved "https://registry.npmmirror.com/@vue/babel-preset-app/-/babel-preset-app-5.0.8.tgz#ce38f76314f5265d62a89756ef264c21f1d351a1" + integrity sha512-yl+5qhpjd8e1G4cMXfORkkBlvtPCIgmRf3IYCWYDKIQ7m+PPa5iTm4feiNmCMD6yGqQWMhhK/7M3oWGL9boKwg== + dependencies: + "@babel/core" "^7.12.16" + "@babel/helper-compilation-targets" "^7.12.16" + "@babel/helper-module-imports" "^7.12.13" + "@babel/plugin-proposal-class-properties" "^7.12.13" + "@babel/plugin-proposal-decorators" "^7.12.13" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-jsx" "^7.12.13" + "@babel/plugin-transform-runtime" "^7.12.15" + "@babel/preset-env" "^7.12.16" + "@babel/runtime" "^7.12.13" + "@vue/babel-plugin-jsx" "^1.0.3" + "@vue/babel-preset-jsx" "^1.1.2" + babel-plugin-dynamic-import-node "^2.3.3" + core-js "^3.8.3" + core-js-compat "^3.8.3" + semver "^7.3.4" + +"@vue/babel-preset-jsx@^1.1.2": + version "1.4.0" + resolved "https://registry.npmmirror.com/@vue/babel-preset-jsx/-/babel-preset-jsx-1.4.0.tgz#f4914ba314235ab097bc4372ed67473c0780bfcc" + integrity sha512-QmfRpssBOPZWL5xw7fOuHNifCQcNQC1PrOo/4fu6xlhlKJJKSA3HqX92Nvgyx8fqHZTUGMPHmFA+IDqwXlqkSA== + dependencies: + "@vue/babel-helper-vue-jsx-merge-props" "^1.4.0" + "@vue/babel-plugin-transform-vue-jsx" "^1.4.0" + "@vue/babel-sugar-composition-api-inject-h" "^1.4.0" + "@vue/babel-sugar-composition-api-render-instance" "^1.4.0" + "@vue/babel-sugar-functional-vue" "^1.4.0" + "@vue/babel-sugar-inject-h" "^1.4.0" + "@vue/babel-sugar-v-model" "^1.4.0" + "@vue/babel-sugar-v-on" "^1.4.0" + +"@vue/babel-sugar-composition-api-inject-h@^1.4.0": + version "1.4.0" + resolved "https://registry.npmmirror.com/@vue/babel-sugar-composition-api-inject-h/-/babel-sugar-composition-api-inject-h-1.4.0.tgz#187e1389f8871d89ece743bb50aed713be9d6c85" + integrity sha512-VQq6zEddJHctnG4w3TfmlVp5FzDavUSut/DwR0xVoe/mJKXyMcsIibL42wPntozITEoY90aBV0/1d2KjxHU52g== + dependencies: + "@babel/plugin-syntax-jsx" "^7.2.0" + +"@vue/babel-sugar-composition-api-render-instance@^1.4.0": + version "1.4.0" + resolved "https://registry.npmmirror.com/@vue/babel-sugar-composition-api-render-instance/-/babel-sugar-composition-api-render-instance-1.4.0.tgz#2c1607ae6dffdab47e785bc01fa45ba756e992c1" + integrity sha512-6ZDAzcxvy7VcnCjNdHJ59mwK02ZFuP5CnucloidqlZwVQv5CQLijc3lGpR7MD3TWFi78J7+a8J56YxbCtHgT9Q== + dependencies: + "@babel/plugin-syntax-jsx" "^7.2.0" + +"@vue/babel-sugar-functional-vue@^1.4.0": + version "1.4.0" + resolved "https://registry.npmmirror.com/@vue/babel-sugar-functional-vue/-/babel-sugar-functional-vue-1.4.0.tgz#60da31068567082287c7337c66ef4df04e0a1029" + integrity sha512-lTEB4WUFNzYt2In6JsoF9sAYVTo84wC4e+PoZWSgM6FUtqRJz7wMylaEhSRgG71YF+wfLD6cc9nqVeXN2rwBvw== + dependencies: + "@babel/plugin-syntax-jsx" "^7.2.0" + +"@vue/babel-sugar-inject-h@^1.4.0": + version "1.4.0" + resolved "https://registry.npmmirror.com/@vue/babel-sugar-inject-h/-/babel-sugar-inject-h-1.4.0.tgz#bf39aa6631fb1d0399b1c49b4c59e1c8899b4363" + integrity sha512-muwWrPKli77uO2fFM7eA3G1lAGnERuSz2NgAxuOLzrsTlQl8W4G+wwbM4nB6iewlKbwKRae3nL03UaF5ffAPMA== + dependencies: + "@babel/plugin-syntax-jsx" "^7.2.0" + +"@vue/babel-sugar-v-model@^1.4.0": + version "1.4.0" + resolved "https://registry.npmmirror.com/@vue/babel-sugar-v-model/-/babel-sugar-v-model-1.4.0.tgz#a51d986609f430c4f70ada3a93cc560a2970f720" + integrity sha512-0t4HGgXb7WHYLBciZzN5s0Hzqan4Ue+p/3FdQdcaHAb7s5D9WZFGoSxEZHrR1TFVZlAPu1bejTKGeAzaaG3NCQ== + dependencies: + "@babel/plugin-syntax-jsx" "^7.2.0" + "@vue/babel-helper-vue-jsx-merge-props" "^1.4.0" + "@vue/babel-plugin-transform-vue-jsx" "^1.4.0" + camelcase "^5.0.0" + html-tags "^2.0.0" + svg-tags "^1.0.0" + +"@vue/babel-sugar-v-on@^1.4.0": + version "1.4.0" + resolved "https://registry.npmmirror.com/@vue/babel-sugar-v-on/-/babel-sugar-v-on-1.4.0.tgz#43b7106a9672d8cbeefc0eb8afe1d376edc6166e" + integrity sha512-m+zud4wKLzSKgQrWwhqRObWzmTuyzl6vOP7024lrpeJM4x2UhQtRDLgYjXAw9xBXjCwS0pP9kXjg91F9ZNo9JA== + dependencies: + "@babel/plugin-syntax-jsx" "^7.2.0" + "@vue/babel-plugin-transform-vue-jsx" "^1.4.0" + camelcase "^5.0.0" + +"@vue/cli-overlay@^5.0.8": + version "5.0.8" + resolved "https://registry.npmmirror.com/@vue/cli-overlay/-/cli-overlay-5.0.8.tgz#b61477acdc43bbd42fce6326d228471201ecdcdd" + integrity sha512-KmtievE/B4kcXp6SuM2gzsnSd8WebkQpg3XaB6GmFh1BJGRqa1UiW9up7L/Q67uOdTigHxr5Ar2lZms4RcDjwQ== + +"@vue/cli-plugin-babel@~5.0.0": + version "5.0.8" + resolved "https://registry.npmmirror.com/@vue/cli-plugin-babel/-/cli-plugin-babel-5.0.8.tgz#54f9a07900f29baff54803dcfa916c602894feb7" + integrity sha512-a4qqkml3FAJ3auqB2kN2EMPocb/iu0ykeELwed+9B1c1nQ1HKgslKMHMPavYx3Cd/QAx2mBD4hwKBqZXEI/CsQ== + dependencies: + "@babel/core" "^7.12.16" + "@vue/babel-preset-app" "^5.0.8" + "@vue/cli-shared-utils" "^5.0.8" + babel-loader "^8.2.2" + thread-loader "^3.0.0" + webpack "^5.54.0" + +"@vue/cli-plugin-eslint@~5.0.0": + version "5.0.8" + resolved "https://registry.npmmirror.com/@vue/cli-plugin-eslint/-/cli-plugin-eslint-5.0.8.tgz#754939265c2c5b746fa36c7d7705a89138e193bf" + integrity sha512-d11+I5ONYaAPW1KyZj9GlrV/E6HZePq5L5eAF5GgoVdu6sxr6bDgEoxzhcS1Pk2eh8rn1MxG/FyyR+eCBj/CNg== + dependencies: + "@vue/cli-shared-utils" "^5.0.8" + eslint-webpack-plugin "^3.1.0" + globby "^11.0.2" + webpack "^5.54.0" + yorkie "^2.0.0" + +"@vue/cli-plugin-router@^5.0.8", "@vue/cli-plugin-router@~5.0.0": + version "5.0.8" + resolved "https://registry.npmmirror.com/@vue/cli-plugin-router/-/cli-plugin-router-5.0.8.tgz#a113ec626f3d4216d20496c42d35533bce9e889f" + integrity sha512-Gmv4dsGdAsWPqVijz3Ux2OS2HkMrWi1ENj2cYL75nUeL+Xj5HEstSqdtfZ0b1q9NCce+BFB6QnHfTBXc/fCvMg== + dependencies: + "@vue/cli-shared-utils" "^5.0.8" + +"@vue/cli-plugin-vuex@^5.0.8": + version "5.0.8" + resolved "https://registry.npmmirror.com/@vue/cli-plugin-vuex/-/cli-plugin-vuex-5.0.8.tgz#0d4cb3020f9102bea9288d750729dde176c66ccd" + integrity sha512-HSYWPqrunRE5ZZs8kVwiY6oWcn95qf/OQabwLfprhdpFWAGtLStShjsGED2aDpSSeGAskQETrtR/5h7VqgIlBA== + +"@vue/cli-service@~5.0.0": + version "5.0.8" + resolved "https://registry.npmmirror.com/@vue/cli-service/-/cli-service-5.0.8.tgz#cf3f6f1b7bf0fba9cdab86b6bec4f9897f982dac" + integrity sha512-nV7tYQLe7YsTtzFrfOMIHc5N2hp5lHG2rpYr0aNja9rNljdgcPZLyQRb2YRivTHqTv7lI962UXFURcpStHgyFw== + dependencies: + "@babel/helper-compilation-targets" "^7.12.16" + "@soda/friendly-errors-webpack-plugin" "^1.8.0" + "@soda/get-current-script" "^1.0.2" + "@types/minimist" "^1.2.0" + "@vue/cli-overlay" "^5.0.8" + "@vue/cli-plugin-router" "^5.0.8" + "@vue/cli-plugin-vuex" "^5.0.8" + "@vue/cli-shared-utils" "^5.0.8" + "@vue/component-compiler-utils" "^3.3.0" + "@vue/vue-loader-v15" "npm:vue-loader@^15.9.7" + "@vue/web-component-wrapper" "^1.3.0" + acorn "^8.0.5" + acorn-walk "^8.0.2" + address "^1.1.2" + autoprefixer "^10.2.4" + browserslist "^4.16.3" + case-sensitive-paths-webpack-plugin "^2.3.0" + cli-highlight "^2.1.10" + clipboardy "^2.3.0" + cliui "^7.0.4" + copy-webpack-plugin "^9.0.1" + css-loader "^6.5.0" + css-minimizer-webpack-plugin "^3.0.2" + cssnano "^5.0.0" + debug "^4.1.1" + default-gateway "^6.0.3" + dotenv "^10.0.0" + dotenv-expand "^5.1.0" + fs-extra "^9.1.0" + globby "^11.0.2" + hash-sum "^2.0.0" + html-webpack-plugin "^5.1.0" + is-file-esm "^1.0.0" + launch-editor-middleware "^2.2.1" + lodash.defaultsdeep "^4.6.1" + lodash.mapvalues "^4.6.0" + mini-css-extract-plugin "^2.5.3" + minimist "^1.2.5" + module-alias "^2.2.2" + portfinder "^1.0.26" + postcss "^8.2.6" + postcss-loader "^6.1.1" + progress-webpack-plugin "^1.0.12" + ssri "^8.0.1" + terser-webpack-plugin "^5.1.1" + thread-loader "^3.0.0" + vue-loader "^17.0.0" + vue-style-loader "^4.1.3" + webpack "^5.54.0" + webpack-bundle-analyzer "^4.4.0" + webpack-chain "^6.5.1" + webpack-dev-server "^4.7.3" + webpack-merge "^5.7.3" + webpack-virtual-modules "^0.4.2" + whatwg-fetch "^3.6.2" + +"@vue/cli-shared-utils@^5.0.8": + version "5.0.8" + resolved "https://registry.npmmirror.com/@vue/cli-shared-utils/-/cli-shared-utils-5.0.8.tgz#75fc96528eba2b1c7e33cb7e989a984ddef99c8a" + integrity sha512-uK2YB7bBVuQhjOJF+O52P9yFMXeJVj7ozqJkwYE9PlMHL1LMHjtCYm4cSdOebuPzyP+/9p0BimM/OqxsevIopQ== + dependencies: + "@achrinza/node-ipc" "^9.2.5" + chalk "^4.1.2" + execa "^1.0.0" + joi "^17.4.0" + launch-editor "^2.2.1" + lru-cache "^6.0.0" + node-fetch "^2.6.7" + open "^8.0.2" + ora "^5.3.0" + read-pkg "^5.1.1" + semver "^7.3.4" + strip-ansi "^6.0.0" + +"@vue/compiler-core@3.4.27": + version "3.4.27" + resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.4.27.tgz#e69060f4b61429fe57976aa5872cfa21389e4d91" + integrity sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg== + dependencies: + "@babel/parser" "^7.24.4" + "@vue/shared" "3.4.27" + entities "^4.5.0" + estree-walker "^2.0.2" + source-map-js "^1.2.0" + +"@vue/compiler-dom@3.4.27": + version "3.4.27" + resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz#d51d35f40d00ce235d7afc6ad8b09dfd92b1cc1c" + integrity sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw== + dependencies: + "@vue/compiler-core" "3.4.27" + "@vue/shared" "3.4.27" + +"@vue/compiler-sfc@2.7.16": + version "2.7.16" + resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-2.7.16.tgz#ff81711a0fac9c68683d8bb00b63f857de77dc83" + integrity sha512-KWhJ9k5nXuNtygPU7+t1rX6baZeqOYLEforUPjgNDBnLicfHCoi48H87Q8XyLZOrNNsmhuwKqtpDQWjEFe6Ekg== + dependencies: + "@babel/parser" "^7.23.5" + postcss "^8.4.14" + source-map "^0.6.1" + optionalDependencies: + prettier "^1.18.2 || ^2.0.0" + +"@vue/compiler-sfc@^3.4.15": + version "3.4.27" + resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz#399cac1b75c6737bf5440dc9cf3c385bb2959701" + integrity sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA== + dependencies: + "@babel/parser" "^7.24.4" + "@vue/compiler-core" "3.4.27" + "@vue/compiler-dom" "3.4.27" + "@vue/compiler-ssr" "3.4.27" + "@vue/shared" "3.4.27" + estree-walker "^2.0.2" + magic-string "^0.30.10" + postcss "^8.4.38" + source-map-js "^1.2.0" + +"@vue/compiler-ssr@3.4.27": + version "3.4.27" + resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz#2a8ecfef1cf448b09be633901a9c020360472e3d" + integrity sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw== + dependencies: + "@vue/compiler-dom" "3.4.27" + "@vue/shared" "3.4.27" + +"@vue/component-compiler-utils@^3.1.0", "@vue/component-compiler-utils@^3.3.0": + version "3.3.0" + resolved "https://registry.npmmirror.com/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz#f9f5fb53464b0c37b2c8d2f3fbfe44df60f61dc9" + integrity sha512-97sfH2mYNU+2PzGrmK2haqffDpVASuib9/w2/noxiFi31Z54hW+q3izKQXXQZSNhtiUpAI36uSuYepeBe4wpHQ== + dependencies: + consolidate "^0.15.1" + hash-sum "^1.0.2" + lru-cache "^4.1.2" + merge-source-map "^1.1.0" + postcss "^7.0.36" + postcss-selector-parser "^6.0.2" + source-map "~0.6.1" + vue-template-es2015-compiler "^1.9.0" + optionalDependencies: + prettier "^1.18.2 || ^2.0.0" + +"@vue/shared@3.4.27": + version "3.4.27" + resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.4.27.tgz#f05e3cd107d157354bb4ae7a7b5fc9cf73c63b50" + integrity sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA== + +"@vue/vue-loader-v15@npm:vue-loader@^15.9.7": + version "15.11.1" + resolved "https://registry.npmmirror.com/vue-loader/-/vue-loader-15.11.1.tgz#dee91169211276ed43c5715caef88a56b1f497b0" + integrity sha512-0iw4VchYLePqJfJu9s62ACWUXeSqM30SQqlIftbYWM3C+jpPcEHKSPUZBLjSF9au4HTHQ/naF6OGnO3Q/qGR3Q== + dependencies: + "@vue/component-compiler-utils" "^3.1.0" + hash-sum "^1.0.2" + loader-utils "^1.1.0" + vue-hot-reload-api "^2.3.0" + vue-style-loader "^4.1.0" + +"@vue/web-component-wrapper@^1.3.0": + version "1.3.0" + resolved "https://registry.npmmirror.com/@vue/web-component-wrapper/-/web-component-wrapper-1.3.0.tgz#b6b40a7625429d2bd7c2281ddba601ed05dc7f1a" + integrity sha512-Iu8Tbg3f+emIIMmI2ycSI8QcEuAUgPTgHwesDU1eKMLE4YC/c/sFbGc70QgMq31ijRftV0R7vCm9co6rldCeOA== + +"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1": + version "1.12.1" + resolved "https://registry.npmmirror.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb" + integrity sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + +"@webassemblyjs/floating-point-hex-parser@1.11.6": + version "1.11.6" + resolved "https://registry.npmmirror.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431" + integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== + +"@webassemblyjs/helper-api-error@1.11.6": + version "1.11.6" + resolved "https://registry.npmmirror.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" + integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== + +"@webassemblyjs/helper-buffer@1.12.1": + version "1.12.1" + resolved "https://registry.npmmirror.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz#6df20d272ea5439bf20ab3492b7fb70e9bfcb3f6" + integrity sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw== + +"@webassemblyjs/helper-numbers@1.11.6": + version "1.11.6" + resolved "https://registry.npmmirror.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5" + integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.6" + "@webassemblyjs/helper-api-error" "1.11.6" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.11.6": + version "1.11.6" + resolved "https://registry.npmmirror.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" + integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== + +"@webassemblyjs/helper-wasm-section@1.12.1": + version "1.12.1" + resolved "https://registry.npmmirror.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz#3da623233ae1a60409b509a52ade9bc22a37f7bf" + integrity sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/wasm-gen" "1.12.1" + +"@webassemblyjs/ieee754@1.11.6": + version "1.11.6" + resolved "https://registry.npmmirror.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a" + integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.11.6": + version "1.11.6" + resolved "https://registry.npmmirror.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7" + integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.11.6": + version "1.11.6" + resolved "https://registry.npmmirror.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" + integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== + +"@webassemblyjs/wasm-edit@^1.12.1": + version "1.12.1" + resolved "https://registry.npmmirror.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz#9f9f3ff52a14c980939be0ef9d5df9ebc678ae3b" + integrity sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/helper-wasm-section" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-opt" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + "@webassemblyjs/wast-printer" "1.12.1" + +"@webassemblyjs/wasm-gen@1.12.1": + version "1.12.1" + resolved "https://registry.npmmirror.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz#a6520601da1b5700448273666a71ad0a45d78547" + integrity sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wasm-opt@1.12.1": + version "1.12.1" + resolved "https://registry.npmmirror.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz#9e6e81475dfcfb62dab574ac2dda38226c232bc5" + integrity sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + +"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.12.1": + version "1.12.1" + resolved "https://registry.npmmirror.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz#c47acb90e6f083391e3fa61d113650eea1e95937" + integrity sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-api-error" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wast-printer@1.12.1": + version "1.12.1" + resolved "https://registry.npmmirror.com/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz#bcecf661d7d1abdaf989d8341a4833e33e2b31ac" + integrity sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@xtuc/long" "4.2.2" + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.npmmirror.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.npmmirror.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.npmmirror.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-import-assertions@^1.9.0: + version "1.9.0" + resolved "https://registry.npmmirror.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" + integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== + +acorn-jsx@^5.3.1, acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^8.0.0, acorn-walk@^8.0.2: + version "8.3.2" + resolved "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" + integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== + +acorn@^7.4.0: + version "7.4.1" + resolved "https://registry.npmmirror.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +acorn@^8.0.4, acorn@^8.0.5, acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0: + version "8.11.3" + resolved "https://registry.npmmirror.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + +add-dom-event-listener@^1.0.2: + version "1.1.0" + resolved "https://registry.npmmirror.com/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz#6a92db3a0dd0abc254e095c0f1dc14acbbaae310" + integrity sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw== + dependencies: + object-assign "4.x" + +address@^1.1.2: + version "1.2.2" + resolved "https://registry.npmmirror.com/address/-/address-1.2.2.tgz#2b5248dac5485a6390532c6a517fda2e3faac89e" + integrity sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA== + +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.npmmirror.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + +ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv-keywords@^5.1.0: + version "5.1.0" + resolved "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== + dependencies: + fast-deep-equal "^3.1.3" + +ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^8.0.0, ajv@^8.0.1, ajv@^8.9.0: + version "8.13.0" + resolved "https://registry.npmmirror.com/ajv/-/ajv-8.13.0.tgz#a3939eaec9fb80d217ddf0c3376948c023f28c91" + integrity sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA== + dependencies: + fast-deep-equal "^3.1.3" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.4.1" + +ansi-colors@^4.1.1: + version "4.1.3" + resolved "https://registry.npmmirror.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== + +ansi-escapes@^3.0.0: + version "3.2.0" + resolved "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== + +ansi-html-community@^0.0.8: + version "0.0.8" + resolved "https://registry.npmmirror.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" + integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== + +ansi-regex@^3.0.0: + version "3.0.1" + resolved "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1" + integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ant-design-vue@^1.7.8: + version "1.7.8" + resolved "https://registry.npmmirror.com/ant-design-vue/-/ant-design-vue-1.7.8.tgz#1abbf86b68a4f5b1000bea0487b8031dc0001661" + integrity sha512-F1hmiS9vwbyfuFvlamdW5l9bHKqRlj9wHaGDIE41NZMWXyWy8qL0UFa/+I0Wl8gQWZCqODW5pN6Yfoyn85At3A== + dependencies: + "@ant-design/icons" "^2.1.1" + "@ant-design/icons-vue" "^2.0.0" + "@simonwep/pickr" "~1.7.0" + add-dom-event-listener "^1.0.2" + array-tree-filter "^2.1.0" + async-validator "^3.0.3" + babel-helper-vue-jsx-merge-props "^2.0.3" + babel-runtime "6.x" + classnames "^2.2.5" + component-classes "^1.2.6" + dom-align "^1.10.4" + dom-closest "^0.2.0" + dom-scroll-into-view "^2.0.0" + enquire.js "^2.1.6" + intersperse "^1.0.0" + is-mobile "^2.2.1" + is-negative-zero "^2.0.0" + ismobilejs "^1.0.0" + json2mq "^0.2.0" + lodash "^4.17.5" + moment "^2.21.0" + mutationobserver-shim "^0.3.2" + node-emoji "^1.10.0" + omit.js "^1.0.0" + raf "^3.4.0" + resize-observer-polyfill "^1.5.1" + shallow-equal "^1.0.0" + shallowequal "^1.0.2" + vue-ref "^2.0.0" + warning "^4.0.0" + +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arch@^2.1.1: + version "2.2.0" + resolved "https://registry.npmmirror.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" + integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.npmmirror.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +array-tree-filter@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/array-tree-filter/-/array-tree-filter-2.1.0.tgz#873ac00fec83749f255ac8dd083814b4f6329190" + integrity sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +asciinema-player@^3.0.1: + version "3.7.1" + resolved "https://registry.npmmirror.com/asciinema-player/-/asciinema-player-3.7.1.tgz#26284c71d5b89b4594e75847599bcc929f3acc56" + integrity sha512-zDJteGjBzNQhHEnD0aG7GqV3E53sOyKb1WCxKNRm2PquU70Lq3s4xxb91wyDS0hBJ3J/TB8aY3y8gjGPN+T23A== + dependencies: + "@babel/runtime" "^7.21.0" + solid-js "^1.3.0" + +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + +async-validator@^3.0.3: + version "3.5.2" + resolved "https://registry.npmmirror.com/async-validator/-/async-validator-3.5.2.tgz#68e866a96824e8b2694ff7a831c1a25c44d5e500" + integrity sha512-8eLCg00W9pIRZSB781UUX/H6Oskmm8xloZfr09lz5bikRpBVDlJ3hRVuxxP1SxcwsEYfJ4IU8Q19Y8/893r3rQ== + +async@^2.6.4: + version "2.6.4" + resolved "https://registry.npmmirror.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" + integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== + dependencies: + lodash "^4.17.14" + +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + +autoprefixer@^10.2.4: + version "10.4.19" + resolved "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.19.tgz#ad25a856e82ee9d7898c59583c1afeb3fa65f89f" + integrity sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew== + dependencies: + browserslist "^4.23.0" + caniuse-lite "^1.0.30001599" + fraction.js "^4.3.7" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" + +axios@^0.23.0: + version "0.23.0" + resolved "https://registry.npmmirror.com/axios/-/axios-0.23.0.tgz#b0fa5d0948a8d1d75e3d5635238b6c4625b05149" + integrity sha512-NmvAE4i0YAv5cKq8zlDoPd1VLKAqX5oLuZKs8xkJa4qi6RGn0uhCYFjWtHHC9EM/MwOwYWOs53W+V0aqEXq1sg== + dependencies: + follow-redirects "^1.14.4" + +babel-helper-vue-jsx-merge-props@^2.0.3: + version "2.0.3" + resolved "https://registry.npmmirror.com/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz#22aebd3b33902328e513293a8e4992b384f9f1b6" + integrity sha512-gsLiKK7Qrb7zYJNgiXKpXblxbV5ffSwR0f5whkPAaBAR4fhi6bwRZxX9wBlIc5M/v8CCkXUbXZL4N/nSE97cqg== + +babel-loader@^8.2.2: + version "8.3.0" + resolved "https://registry.npmmirror.com/babel-loader/-/babel-loader-8.3.0.tgz#124936e841ba4fe8176786d6ff28add1f134d6a8" + integrity sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q== + dependencies: + find-cache-dir "^3.3.1" + loader-utils "^2.0.0" + make-dir "^3.1.0" + schema-utils "^2.6.5" + +babel-plugin-dynamic-import-node@^2.3.3: + version "2.3.3" + resolved "https://registry.npmmirror.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" + integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== + dependencies: + object.assign "^4.1.0" + +babel-plugin-polyfill-corejs2@^0.4.10: + version "0.4.11" + resolved "https://registry.npmmirror.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz#30320dfe3ffe1a336c15afdcdafd6fd615b25e33" + integrity sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q== + dependencies: + "@babel/compat-data" "^7.22.6" + "@babel/helper-define-polyfill-provider" "^0.6.2" + semver "^6.3.1" + +babel-plugin-polyfill-corejs3@^0.10.1, babel-plugin-polyfill-corejs3@^0.10.4: + version "0.10.4" + resolved "https://registry.npmmirror.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz#789ac82405ad664c20476d0233b485281deb9c77" + integrity sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.1" + core-js-compat "^3.36.1" + +babel-plugin-polyfill-regenerator@^0.6.1: + version "0.6.2" + resolved "https://registry.npmmirror.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz#addc47e240edd1da1058ebda03021f382bba785e" + integrity sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.2" + +babel-runtime@6.x, babel-runtime@^6.23.0, babel-runtime@^6.26.0: + version "6.26.0" + resolved "https://registry.npmmirror.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + integrity sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g== + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +batch@0.6.1: + version "0.6.1" + resolved "https://registry.npmmirror.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" + integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== + +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.npmmirror.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +bl@^4.1.0: + version "4.1.0" + resolved "https://registry.npmmirror.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +bluebird@^3.1.1: + version "3.7.2" + resolved "https://registry.npmmirror.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + +body-parser@1.20.2: + version "1.20.2" + resolved "https://registry.npmmirror.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" + integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + +bonjour-service@^1.0.11: + version "1.2.1" + resolved "https://registry.npmmirror.com/bonjour-service/-/bonjour-service-1.2.1.tgz#eb41b3085183df3321da1264719fbada12478d02" + integrity sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw== + dependencies: + fast-deep-equal "^3.1.3" + multicast-dns "^7.2.5" + +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace@^0.11.0: + version "0.11.1" + resolved "https://registry.npmmirror.com/brace/-/brace-0.11.1.tgz#4896fcc9d544eef45f4bb7660db320d3b379fe58" + integrity sha512-Fc8Ne62jJlKHiG/ajlonC4Sd66Pq68fFwK4ihJGNZpGqboc324SQk+lRvMzpPRuJOmfrJefdG8/7JdWX4bzJ2Q== + +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.0.0, browserslist@^4.16.3, browserslist@^4.21.10, browserslist@^4.21.4, browserslist@^4.22.2, browserslist@^4.23.0: + version "4.23.0" + resolved "https://registry.npmmirror.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" + integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== + dependencies: + caniuse-lite "^1.0.30001587" + electron-to-chromium "^1.4.668" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.npmmirror.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +call-bind@^1.0.5, call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camel-case@^4.1.2: + version "4.1.2" + resolved "https://registry.npmmirror.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" + integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== + dependencies: + pascal-case "^3.1.2" + tslib "^2.0.3" + +camelcase@^5.0.0: + version "5.3.1" + resolved "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.3.0: + version "6.3.0" + resolved "https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-api@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" + integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== + dependencies: + browserslist "^4.0.0" + caniuse-lite "^1.0.0" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001599: + version "1.0.30001621" + resolved "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001621.tgz#4adcb443c8b9c8303e04498318f987616b8fea2e" + integrity sha512-+NLXZiviFFKX0fk8Piwv3PfLPGtRqJeq2TiNoUff/qB5KJgwecJTvCXDpmlyP/eCI/GUEmp/h/y5j0yckiiZrA== + +case-sensitive-paths-webpack-plugin@^2.3.0: + version "2.4.0" + resolved "https://registry.npmmirror.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4" + integrity sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw== + +chalk@^2.1.0, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chokidar@^3.5.3: + version "3.6.0" + resolved "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chrome-trace-event@^1.0.2: + version "1.0.3" + resolved "https://registry.npmmirror.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" + integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== + +ci-info@^1.5.0: + version "1.6.0" + resolved "https://registry.npmmirror.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" + integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A== + +classnames@^2.2.5: + version "2.5.1" + resolved "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" + integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== + +clean-css@^5.2.2: + version "5.3.3" + resolved "https://registry.npmmirror.com/clean-css/-/clean-css-5.3.3.tgz#b330653cd3bd6b75009cc25c714cae7b93351ccd" + integrity sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg== + dependencies: + source-map "~0.6.0" + +cli-cursor@^2.0.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + integrity sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw== + dependencies: + restore-cursor "^2.0.0" + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-highlight@^2.1.10: + version "2.1.11" + resolved "https://registry.npmmirror.com/cli-highlight/-/cli-highlight-2.1.11.tgz#49736fa452f0aaf4fae580e30acb26828d2dc1bf" + integrity sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg== + dependencies: + chalk "^4.0.0" + highlight.js "^10.7.1" + mz "^2.4.0" + parse5 "^5.1.1" + parse5-htmlparser2-tree-adapter "^6.0.0" + yargs "^16.0.0" + +cli-spinners@^2.5.0: + version "2.9.2" + resolved "https://registry.npmmirror.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" + integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== + +clipboardy@^2.3.0: + version "2.3.0" + resolved "https://registry.npmmirror.com/clipboardy/-/clipboardy-2.3.0.tgz#3c2903650c68e46a91b388985bc2774287dba290" + integrity sha512-mKhiIL2DrQIsuXMgBgnfEHOZOryC7kY7YO//TN6c63wlEm3NG5tz+YgY5rVi29KCmq/QQjKYvM7a19+MDOTHOQ== + dependencies: + arch "^2.1.1" + execa "^1.0.0" + is-wsl "^2.1.1" + +cliui@^7.0.2, cliui@^7.0.4: + version "7.0.4" + resolved "https://registry.npmmirror.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.npmmirror.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.npmmirror.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +colord@^2.9.1: + version "2.9.3" + resolved "https://registry.npmmirror.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" + integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== + +colorette@^2.0.10: + version "2.0.20" + resolved "https://registry.npmmirror.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^7.2.0: + version "7.2.0" + resolved "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +commander@^8.3.0: + version "8.3.0" + resolved "https://registry.npmmirror.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" + integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== + +component-classes@^1.2.6: + version "1.2.6" + resolved "https://registry.npmmirror.com/component-classes/-/component-classes-1.2.6.tgz#c642394c3618a4d8b0b8919efccbbd930e5cd691" + integrity sha512-hPFGULxdwugu1QWW3SvVOCUHLzO34+a2J6Wqy0c5ASQkfi9/8nZcBB0ZohaEbXOQlCflMAEMmEWk7u7BVs4koA== + dependencies: + component-indexof "0.0.3" + +component-indexof@0.0.3: + version "0.0.3" + resolved "https://registry.npmmirror.com/component-indexof/-/component-indexof-0.0.3.tgz#11d091312239eb8f32c8f25ae9cb002ffe8d3c24" + integrity sha512-puDQKvx/64HZXb4hBwIcvQLaLgux8o1CbWl39s41hrIIZDl1lJiD5jc22gj3RBeGK0ovxALDYpIbyjqDUUl0rw== + +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.npmmirror.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.4: + version "1.7.4" + resolved "https://registry.npmmirror.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +connect-history-api-fallback@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" + integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== + +consolidate@^0.15.1: + version "0.15.1" + resolved "https://registry.npmmirror.com/consolidate/-/consolidate-0.15.1.tgz#21ab043235c71a07d45d9aad98593b0dba56bab7" + integrity sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw== + dependencies: + bluebird "^3.1.1" + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.npmmirror.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4, content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.6.0: + version "0.6.0" + resolved "https://registry.npmmirror.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== + +copy-anything@^2.0.1: + version "2.0.6" + resolved "https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz#092454ea9584a7b7ad5573062b2a87f5900fc480" + integrity sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw== + dependencies: + is-what "^3.14.1" + +copy-webpack-plugin@^9.0.1: + version "9.1.0" + resolved "https://registry.npmmirror.com/copy-webpack-plugin/-/copy-webpack-plugin-9.1.0.tgz#2d2c460c4c4695ec0a58afb2801a1205256c4e6b" + integrity sha512-rxnR7PaGigJzhqETHGmAcxKnLZSR5u1Y3/bcIv/1FnqXedcL/E2ewK7ZCNrArJKCiSv8yVXhTqetJh8inDvfsA== + dependencies: + fast-glob "^3.2.7" + glob-parent "^6.0.1" + globby "^11.0.3" + normalize-path "^3.0.0" + schema-utils "^3.1.1" + serialize-javascript "^6.0.0" + +core-js-compat@^3.31.0, core-js-compat@^3.36.1, core-js-compat@^3.8.3: + version "3.37.1" + resolved "https://registry.npmmirror.com/core-js-compat/-/core-js-compat-3.37.1.tgz#c844310c7852f4bdf49b8d339730b97e17ff09ee" + integrity sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg== + dependencies: + browserslist "^4.23.0" + +core-js@^2.4.0: + version "2.6.12" + resolved "https://registry.npmmirror.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" + integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== + +core-js@^3.6.5, core-js@^3.8.3: + version "3.37.1" + resolved "https://registry.npmmirror.com/core-js/-/core-js-3.37.1.tgz#d21751ddb756518ac5a00e4d66499df981a62db9" + integrity sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw== + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +cosmiconfig@^7.0.0: + version "7.1.0" + resolved "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" + integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + +cross-spawn@^5.0.1: + version "5.1.0" + resolved "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + integrity sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A== + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + +cross-spawn@^6.0.0: + version "6.0.5" + resolved "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +css-declaration-sorter@^6.3.1: + version "6.4.1" + resolved "https://registry.npmmirror.com/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz#28beac7c20bad7f1775be3a7129d7eae409a3a71" + integrity sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g== + +css-loader@^6.5.0: + version "6.11.0" + resolved "https://registry.npmmirror.com/css-loader/-/css-loader-6.11.0.tgz#33bae3bf6363d0a7c2cf9031c96c744ff54d85ba" + integrity sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g== + dependencies: + icss-utils "^5.1.0" + postcss "^8.4.33" + postcss-modules-extract-imports "^3.1.0" + postcss-modules-local-by-default "^4.0.5" + postcss-modules-scope "^3.2.0" + postcss-modules-values "^4.0.0" + postcss-value-parser "^4.2.0" + semver "^7.5.4" + +css-minimizer-webpack-plugin@^3.0.2: + version "3.4.1" + resolved "https://registry.npmmirror.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz#ab78f781ced9181992fe7b6e4f3422e76429878f" + integrity sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q== + dependencies: + cssnano "^5.0.6" + jest-worker "^27.0.2" + postcss "^8.3.5" + schema-utils "^4.0.0" + serialize-javascript "^6.0.0" + source-map "^0.6.1" + +css-select@^4.1.3: + version "4.3.0" + resolved "https://registry.npmmirror.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" + integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== + dependencies: + boolbase "^1.0.0" + css-what "^6.0.1" + domhandler "^4.3.1" + domutils "^2.8.0" + nth-check "^2.0.1" + +css-tree@^1.1.2, css-tree@^1.1.3: + version "1.1.3" + resolved "https://registry.npmmirror.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + +css-what@^6.0.1: + version "6.1.0" + resolved "https://registry.npmmirror.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +cssnano-preset-default@^5.2.14: + version "5.2.14" + resolved "https://registry.npmmirror.com/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz#309def4f7b7e16d71ab2438052093330d9ab45d8" + integrity sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A== + dependencies: + css-declaration-sorter "^6.3.1" + cssnano-utils "^3.1.0" + postcss-calc "^8.2.3" + postcss-colormin "^5.3.1" + postcss-convert-values "^5.1.3" + postcss-discard-comments "^5.1.2" + postcss-discard-duplicates "^5.1.0" + postcss-discard-empty "^5.1.1" + postcss-discard-overridden "^5.1.0" + postcss-merge-longhand "^5.1.7" + postcss-merge-rules "^5.1.4" + postcss-minify-font-values "^5.1.0" + postcss-minify-gradients "^5.1.1" + postcss-minify-params "^5.1.4" + postcss-minify-selectors "^5.2.1" + postcss-normalize-charset "^5.1.0" + postcss-normalize-display-values "^5.1.0" + postcss-normalize-positions "^5.1.1" + postcss-normalize-repeat-style "^5.1.1" + postcss-normalize-string "^5.1.0" + postcss-normalize-timing-functions "^5.1.0" + postcss-normalize-unicode "^5.1.1" + postcss-normalize-url "^5.1.0" + postcss-normalize-whitespace "^5.1.1" + postcss-ordered-values "^5.1.3" + postcss-reduce-initial "^5.1.2" + postcss-reduce-transforms "^5.1.0" + postcss-svgo "^5.1.0" + postcss-unique-selectors "^5.1.1" + +cssnano-utils@^3.1.0: + version "3.1.0" + resolved "https://registry.npmmirror.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861" + integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== + +cssnano@^5.0.0, cssnano@^5.0.6: + version "5.1.15" + resolved "https://registry.npmmirror.com/cssnano/-/cssnano-5.1.15.tgz#ded66b5480d5127fcb44dac12ea5a983755136bf" + integrity sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw== + dependencies: + cssnano-preset-default "^5.2.14" + lilconfig "^2.0.3" + yaml "^1.10.2" + +csso@^4.2.0: + version "4.2.0" + resolved "https://registry.npmmirror.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" + integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== + dependencies: + css-tree "^1.1.2" + +csstype@^3.0.8, csstype@^3.1.0: + version "3.1.3" + resolved "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + +"d3-color@1 - 3": + version "3.1.0" + resolved "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" + integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== + +d3-ease@^1.0.5: + version "1.0.7" + resolved "https://registry.npmmirror.com/d3-ease/-/d3-ease-1.0.7.tgz#9a834890ef8b8ae8c558b2fe55bd57f5993b85e2" + integrity sha512-lx14ZPYkhNx0s/2HX5sLFUI3mbasHjSSpwO/KaaNACweVwxUruKyWVcb293wMv1RqTPZyZ8kSZ2NogUZNcLOFQ== + +d3-interpolate@^3.0.1: + version "3.0.1" + resolved "https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + +d3-timer@^1.0.9: + version "1.0.10" + resolved "https://registry.npmmirror.com/d3-timer/-/d3-timer-1.0.10.tgz#dfe76b8a91748831b13b6d9c793ffbd508dd9de5" + integrity sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw== + +de-indent@^1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" + integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg== + +debounce@^1.2.1: + version "1.2.1" + resolved "https://registry.npmmirror.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" + integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.npmmirror.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: + version "4.3.4" + resolved "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^1.5.2: + version "1.5.2" + resolved "https://registry.npmmirror.com/deepmerge/-/deepmerge-1.5.2.tgz#10499d868844cdad4fee0842df8c7f6f0c95a753" + integrity sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ== + +default-gateway@^6.0.3: + version "6.0.3" + resolved "https://registry.npmmirror.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" + integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== + dependencies: + execa "^5.0.0" + +defaults@^1.0.3: + version "1.0.4" + resolved "https://registry.npmmirror.com/defaults/-/defaults-1.0.4.tgz#b0b02062c1e2aa62ff5d9528f0f98baa90978d7a" + integrity sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A== + dependencies: + clone "^1.0.2" + +define-data-property@^1.0.1, define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== + +define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.npmmirror.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.npmmirror.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +detect-browser@^5.0.0, detect-browser@^5.1.0: + version "5.3.0" + resolved "https://registry.npmmirror.com/detect-browser/-/detect-browser-5.3.0.tgz#9705ef2bddf46072d0f7265a1fe300e36fe7ceca" + integrity sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w== + +detect-node@^2.0.4: + version "2.1.0" + resolved "https://registry.npmmirror.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +dns-packet@^5.2.2: + version "5.6.1" + resolved "https://registry.npmmirror.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f" + integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw== + dependencies: + "@leichtgewicht/ip-codec" "^2.0.1" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dom-align@^1.10.4: + version "1.12.4" + resolved "https://registry.npmmirror.com/dom-align/-/dom-align-1.12.4.tgz#3503992eb2a7cfcb2ed3b2a6d21e0b9c00d54511" + integrity sha512-R8LUSEay/68zE5c8/3BDxiTEvgb4xZTF0RKmAHfiEVN3klfIpXfi2/QCoiWPccVQ0J/ZGdz9OjzL4uJEP/MRAw== + +dom-closest@^0.2.0: + version "0.2.0" + resolved "https://registry.npmmirror.com/dom-closest/-/dom-closest-0.2.0.tgz#ebd9f91d1bf22e8d6f477876bbcd3ec90216c0cf" + integrity sha512-6neTn1BtJlTSt+XSISXpnOsF1uni1CHsP/tmzZMGWxasYFHsBOqrHPnzmneqEgKhpagnfnfSfbvRRW0xFsBHAA== + dependencies: + dom-matches ">=1.0.1" + +dom-converter@^0.2.0: + version "0.2.0" + resolved "https://registry.npmmirror.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" + integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== + dependencies: + utila "~0.4" + +dom-matches@>=1.0.1: + version "2.0.0" + resolved "https://registry.npmmirror.com/dom-matches/-/dom-matches-2.0.0.tgz#d2728b416a87533980eb089b848d253cf23a758c" + integrity sha512-2VI856xEDCLXi19W+4BechR5/oIS6bKCKqcf16GR8Pg7dGLJ/eBOWVbCmQx2ISvYH6wTNx5Ef7JTOw1dRGRx6A== + +dom-scroll-into-view@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/dom-scroll-into-view/-/dom-scroll-into-view-2.0.1.tgz#0decc8522801fd8d3f1c6ba355a74d382c5f989b" + integrity sha512-bvVTQe1lfaUr1oFzZX80ce9KLDlZ3iU+XGNE/bz9HnGdklTieqsbmsLHe+rT2XWqopvL0PckkYqN7ksmm5pe3w== + +dom-serializer@^1.0.1: + version "1.4.1" + resolved "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" + integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + +domelementtype@^2.0.1, domelementtype@^2.2.0: + version "2.3.0" + resolved "https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: + version "4.3.1" + resolved "https://registry.npmmirror.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" + integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== + dependencies: + domelementtype "^2.2.0" + +domutils@^2.5.2, domutils@^2.8.0: + version "2.8.0" + resolved "https://registry.npmmirror.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + +dot-case@^3.0.4: + version "3.0.4" + resolved "https://registry.npmmirror.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" + integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +dotenv-expand@^5.1.0: + version "5.1.0" + resolved "https://registry.npmmirror.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" + integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== + +dotenv@^10.0.0: + version "10.0.0" + resolved "https://registry.npmmirror.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + +duplexer@^0.1.2: + version "0.1.2" + resolved "https://registry.npmmirror.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" + integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== + +easy-stack@1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/easy-stack/-/easy-stack-1.0.1.tgz#8afe4264626988cabb11f3c704ccd0c835411066" + integrity sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w== + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +electron-to-chromium@^1.4.668: + version "1.4.779" + resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.779.tgz#bb6f08b93092a564421adcadcc4b92c5055c7a77" + integrity sha512-oaTiIcszNfySXVJzKcjxd2YjPxziAd+GmXyb2HbidCeFo6Z88ygOT7EimlrEQhM2U08VhSrbKhLOXP0kKUCZ6g== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +enhanced-resolve@^5.16.0: + version "5.16.1" + resolved "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz#e8bc63d51b826d6f1cbc0a150ecb5a8b0c62e567" + integrity sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +enquire.js@^2.1.6: + version "2.1.6" + resolved "https://registry.npmmirror.com/enquire.js/-/enquire.js-2.1.6.tgz#3e8780c9b8b835084c3f60e166dbc3c2a3c89814" + integrity sha512-/KujNpO+PT63F7Hlpu4h3pE3TokKRHN26JYmQpPyjkRD/N57R7bPDNojMXdi7uveAKjYB7yQnartCxZnFWr0Xw== + +enquirer@^2.3.5: + version "2.4.1" + resolved "https://registry.npmmirror.com/enquirer/-/enquirer-2.4.1.tgz#93334b3fbd74fc7097b224ab4a8fb7e40bf4ae56" + integrity sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ== + dependencies: + ansi-colors "^4.1.1" + strip-ansi "^6.0.1" + +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.npmmirror.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + +entities@^4.5.0: + version "4.5.0" + resolved "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +errno@^0.1.1: + version "0.1.8" + resolved "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" + integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== + dependencies: + prr "~1.0.1" + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.npmmirror.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +error-stack-parser@^2.0.6: + version "2.1.4" + resolved "https://registry.npmmirror.com/error-stack-parser/-/error-stack-parser-2.1.4.tgz#229cb01cdbfa84440bfa91876285b94680188286" + integrity sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ== + dependencies: + stackframe "^1.3.4" + +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +es-module-lexer@^1.2.1: + version "1.5.3" + resolved "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-1.5.3.tgz#25969419de9c0b1fbe54279789023e8a9a788412" + integrity sha512-i1gCgmR9dCl6Vil6UKPI/trA69s08g/syhiDK9TG0Nf1RJjjFI+AzoWW7sPufzkgYAn861skuCwJa0pIIHYxvg== + +escalade@^3.1.1, escalade@^3.1.2: + version "3.1.2" + resolved "https://registry.npmmirror.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-plugin-vue@^8.0.3: + version "8.7.1" + resolved "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-8.7.1.tgz#f13c53547a0c9d64588a675cc5ecc6ccaf63703f" + integrity sha512-28sbtm4l4cOzoO1LtzQPxfxhQABararUb1JtqusQqObJpWX2e/gmVyeYVfepizPFne0Q5cILkYGiBoV36L12Wg== + dependencies: + eslint-utils "^3.0.0" + natural-compare "^1.4.0" + nth-check "^2.0.1" + postcss-selector-parser "^6.0.9" + semver "^7.3.5" + vue-eslint-parser "^8.0.1" + +eslint-scope@5.1.1, eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-scope@^7.0.0: + version "7.2.2" + resolved "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + +eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: + version "1.3.0" + resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" + integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== + +eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint-visitor-keys@^3.1.0, eslint-visitor-keys@^3.4.1: + version "3.4.3" + resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint-webpack-plugin@^3.1.0: + version "3.2.0" + resolved "https://registry.npmmirror.com/eslint-webpack-plugin/-/eslint-webpack-plugin-3.2.0.tgz#1978cdb9edc461e4b0195a20da950cf57988347c" + integrity sha512-avrKcGncpPbPSUHX6B3stNGzkKFto3eL+DKM4+VyMrVnhPc3vRczVlCq3uhuFOdRvDHTVXuzwk1ZKUrqDQHQ9w== + dependencies: + "@types/eslint" "^7.29.0 || ^8.4.1" + jest-worker "^28.0.2" + micromatch "^4.0.5" + normalize-path "^3.0.0" + schema-utils "^4.0.0" + +eslint@^7.32.0: + version "7.32.0" + resolved "https://registry.npmmirror.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" + integrity sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA== + dependencies: + "@babel/code-frame" "7.12.11" + "@eslint/eslintrc" "^0.4.3" + "@humanwhocodes/config-array" "^0.5.0" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.0.1" + doctrine "^3.0.0" + enquirer "^2.3.5" + escape-string-regexp "^4.0.0" + eslint-scope "^5.1.1" + eslint-utils "^2.1.0" + eslint-visitor-keys "^2.0.0" + espree "^7.3.1" + esquery "^1.4.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.1.2" + globals "^13.6.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + progress "^2.0.0" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^6.0.9" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^7.3.0, espree@^7.3.1: + version "7.3.1" + resolved "https://registry.npmmirror.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== + dependencies: + acorn "^7.4.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^1.3.0" + +espree@^9.0.0: + version "9.6.1" + resolved "https://registry.npmmirror.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.0: + version "1.5.0" + resolved "https://registry.npmmirror.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.npmmirror.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +estree-walker@^2.0.2: + version "2.0.2" + resolved "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +event-pubsub@4.3.0: + version "4.3.0" + resolved "https://registry.npmmirror.com/event-pubsub/-/event-pubsub-4.3.0.tgz#f68d816bc29f1ec02c539dc58c8dd40ce72cb36e" + integrity sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ== + +eventemitter3@^4.0.0: + version "4.0.7" + resolved "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +events@^3.2.0: + version "3.3.0" + resolved "https://registry.npmmirror.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +execa@^0.8.0: + version "0.8.0" + resolved "https://registry.npmmirror.com/execa/-/execa-0.8.0.tgz#d8d76bbc1b55217ed190fd6dd49d3c774ecfc8da" + integrity sha512-zDWS+Rb1E8BlqqhALSt9kUhss8Qq4nN3iof3gsOdyINksElaPyNBtKUMTR62qhvgVWR0CqCX7sdnKe4MnUbFEA== + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +execa@^5.0.0: + version "5.1.1" + resolved "https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +express@^4.17.3: + version "4.19.2" + resolved "https://registry.npmmirror.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" + integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.2" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.6.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.11.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.7, fast-glob@^3.2.9: + version "3.3.2" + resolved "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.npmmirror.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + +faye-websocket@^0.11.3: + version "0.11.4" + resolved "https://registry.npmmirror.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== + dependencies: + websocket-driver ">=0.5.1" + +fecha@~4.2.0: + version "4.2.3" + resolved "https://registry.npmmirror.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" + integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== + +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + integrity sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.npmmirror.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +find-cache-dir@^3.3.1: + version "3.3.2" + resolved "https://registry.npmmirror.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" + integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.npmmirror.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.npmmirror.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +flatted@^3.2.9: + version "3.3.1" + resolved "https://registry.npmmirror.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== + +follow-redirects@^1.0.0, follow-redirects@^1.14.4: + version "1.15.6" + resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fraction.js@^4.3.7: + version "4.3.7" + resolved "https://registry.npmmirror.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" + integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +fs-extra@^9.1.0: + version "9.1.0" + resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-monkey@^1.0.4: + version "1.0.6" + resolved "https://registry.npmmirror.com/fs-monkey/-/fs-monkey-1.0.6.tgz#8ead082953e88d992cf3ff844faa907b26756da2" + integrity sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + integrity sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ== + +get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.npmmirror.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +gl-matrix@^3.0.0, gl-matrix@^3.3.0, gl-matrix@^3.4.3: + version "3.4.3" + resolved "https://registry.npmmirror.com/gl-matrix/-/gl-matrix-3.4.3.tgz#fc1191e8320009fd4d20e9339595c6041ddc22c9" + integrity sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA== + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.1: + version "6.0.2" + resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.npmmirror.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +glob@^7.1.3: + version "7.2.3" + resolved "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.6.0, globals@^13.9.0: + version "13.24.0" + resolved "https://registry.npmmirror.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +globby@^11.0.2, globby@^11.0.3: + version "11.1.0" + resolved "https://registry.npmmirror.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6: + version "4.2.11" + resolved "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +gzip-size@^6.0.0: + version "6.0.0" + resolved "https://registry.npmmirror.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" + integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== + dependencies: + duplexer "^0.1.2" + +handle-thing@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0, has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1: + version "1.0.3" + resolved "https://registry.npmmirror.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + +has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +hash-sum@^1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04" + integrity sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA== + +hash-sum@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/hash-sum/-/hash-sum-2.0.0.tgz#81d01bb5de8ea4a214ad5d6ead1b523460b0b45a" + integrity sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg== + +hasown@^2.0.0: + version "2.0.2" + resolved "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +he@^1.2.0: + version "1.2.0" + resolved "https://registry.npmmirror.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +highlight.js@^10.7.1: + version "10.7.3" + resolved "https://registry.npmmirror.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== + +hosted-git-info@^2.1.4: + version "2.8.9" + resolved "https://registry.npmmirror.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== + +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.npmmirror.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + +html-entities@^2.3.2: + version "2.5.2" + resolved "https://registry.npmmirror.com/html-entities/-/html-entities-2.5.2.tgz#201a3cf95d3a15be7099521620d19dfb4f65359f" + integrity sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA== + +html-escaper@^2.0.2: + version "2.0.2" + resolved "https://registry.npmmirror.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +html-minifier-terser@^6.0.2: + version "6.1.0" + resolved "https://registry.npmmirror.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab" + integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw== + dependencies: + camel-case "^4.1.2" + clean-css "^5.2.2" + commander "^8.3.0" + he "^1.2.0" + param-case "^3.0.4" + relateurl "^0.2.7" + terser "^5.10.0" + +html-tags@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/html-tags/-/html-tags-2.0.0.tgz#10b30a386085f43cede353cc8fa7cb0deeea668b" + integrity sha512-+Il6N8cCo2wB/Vd3gqy/8TZhTD3QvcVeQLCnZiGkGCH3JP28IgGAY41giccp2W4R3jfyJPAP318FQTa1yU7K7g== + +html-tags@^3.3.1: + version "3.3.1" + resolved "https://registry.npmmirror.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce" + integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ== + +html-webpack-plugin@^5.1.0: + version "5.6.0" + resolved "https://registry.npmmirror.com/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz#50a8fa6709245608cb00e811eacecb8e0d7b7ea0" + integrity sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw== + dependencies: + "@types/html-minifier-terser" "^6.0.0" + html-minifier-terser "^6.0.2" + lodash "^4.17.21" + pretty-error "^4.0.0" + tapable "^2.0.0" + +htmlparser2@^6.1.0: + version "6.1.0" + resolved "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" + integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.0.0" + domutils "^2.5.2" + entities "^2.0.0" + +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.npmmirror.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.npmmirror.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + +http-parser-js@>=0.5.1: + version "0.5.8" + resolved "https://registry.npmmirror.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" + integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== + +http-proxy-middleware@^2.0.3: + version "2.0.6" + resolved "https://registry.npmmirror.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" + integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== + dependencies: + "@types/http-proxy" "^1.17.8" + http-proxy "^1.18.1" + is-glob "^4.0.1" + is-plain-obj "^3.0.0" + micromatch "^4.0.2" + +http-proxy@^1.18.1: + version "1.18.1" + resolved "https://registry.npmmirror.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@^0.6.3: + version "0.6.3" + resolved "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +icss-utils@^5.0.0, icss-utils@^5.1.0: + version "5.1.0" + resolved "https://registry.npmmirror.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" + integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.npmmirror.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +ignore@^5.2.0: + version "5.3.1" + resolved "https://registry.npmmirror.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + +image-size@~0.5.0: + version "0.5.5" + resolved "https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" + integrity sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ== + +import-fresh@^3.0.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== + +intersperse@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/intersperse/-/intersperse-1.0.0.tgz#f2561fb1cfef9f5277cc3347a22886b4351a5181" + integrity sha512-LGcfug7OTeWkaQ8PEq8XbTy9Jl6uCNg8DrPnQUmwxSY8UETj1Y+LLmpdD0qHdEj6KVchuH3BE3ZzIXQ1t3oFUw== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +ipaddr.js@^2.0.1: + version "2.2.0" + resolved "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8" + integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-ci@^1.0.10: + version "1.2.1" + resolved "https://registry.npmmirror.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c" + integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg== + dependencies: + ci-info "^1.5.0" + +is-core-module@^2.13.0: + version "2.13.1" + resolved "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + dependencies: + hasown "^2.0.0" + +is-docker@^2.0.0, is-docker@^2.1.1: + version "2.2.1" + resolved "https://registry.npmmirror.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-file-esm@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/is-file-esm/-/is-file-esm-1.0.0.tgz#987086b0f5a5318179e9d30f4f2f8d37321e1b5f" + integrity sha512-rZlaNKb4Mr8WlRu2A9XdeoKgnO5aA53XdPHgCKVyCrQ/rWi89RET1+bq37Ru46obaQXeiX4vmFIm1vks41hoSA== + dependencies: + read-pkg-up "^7.0.1" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + +is-mobile@^2.2.1: + version "2.2.2" + resolved "https://registry.npmmirror.com/is-mobile/-/is-mobile-2.2.2.tgz#f6c9c5d50ee01254ce05e739bdd835f1ed4e9954" + integrity sha512-wW/SXnYJkTjs++tVK5b6kVITZpAZPtUrt9SF80vvxGiF/Oywal+COk1jlRkiVq15RFNEQKQY31TkV24/1T5cVg== + +is-negative-zero@^2.0.0: + version "2.0.3" + resolved "https://registry.npmmirror.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-obj@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" + integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== + +is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.npmmirror.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +is-what@^3.14.1: + version "3.14.1" + resolved "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz#e1222f46ddda85dead0fd1c9df131760e77755c1" + integrity sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA== + +is-wsl@^2.1.1, is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.npmmirror.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +ismobilejs@^1.0.0: + version "1.1.1" + resolved "https://registry.npmmirror.com/ismobilejs/-/ismobilejs-1.1.1.tgz#c56ca0ae8e52b24ca0f22ba5ef3215a2ddbbaa0e" + integrity sha512-VaFW53yt8QO61k2WJui0dHf4SlL8lxBofUuUmwBo0ljPk0Drz2TiuDW4jo3wDcv41qy/SxrJ+VAzJ/qYqsmzRw== + +isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.npmmirror.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== + +javascript-stringify@^2.0.1: + version "2.1.0" + resolved "https://registry.npmmirror.com/javascript-stringify/-/javascript-stringify-2.1.0.tgz#27c76539be14d8bd128219a2d731b09337904e79" + integrity sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg== + +jest-worker@^27.0.2, jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.npmmirror.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest-worker@^28.0.2: + version "28.1.3" + resolved "https://registry.npmmirror.com/jest-worker/-/jest-worker-28.1.3.tgz#7e3c4ce3fa23d1bb6accb169e7f396f98ed4bb98" + integrity sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +joi@^17.4.0: + version "17.13.1" + resolved "https://registry.npmmirror.com/joi/-/joi-17.13.1.tgz#9c7b53dc3b44dd9ae200255cc3b398874918a6ca" + integrity sha512-vaBlIKCyo4FCUtCm7Eu4QZd/q02bWcxfUO6YSXAZOWF6gzcLBeba8kwotUdYJjDLW8Cz8RywsSOqiNJZW0mNvg== + dependencies: + "@hapi/hoek" "^9.3.0" + "@hapi/topo" "^5.1.0" + "@sideway/address" "^4.1.5" + "@sideway/formula" "^3.0.1" + "@sideway/pinpoint" "^2.0.0" + +js-md5@^0.7.3: + version "0.7.3" + resolved "https://registry.npmmirror.com/js-md5/-/js-md5-0.7.3.tgz#b4f2fbb0b327455f598d6727e38ec272cd09c3f2" + integrity sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ== + +js-message@1.0.7: + version "1.0.7" + resolved "https://registry.npmmirror.com/js-message/-/js-message-1.0.7.tgz#fbddd053c7a47021871bb8b2c95397cc17c20e47" + integrity sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA== + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.npmmirror.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.npmmirror.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-better-errors@^1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: + version "2.3.1" + resolved "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json2mq@^0.2.0: + version "0.2.0" + resolved "https://registry.npmmirror.com/json2mq/-/json2mq-0.2.0.tgz#b637bd3ba9eabe122c83e9720483aeb10d2c904a" + integrity sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA== + dependencies: + string-convert "^0.2.0" + +json5@^1.0.1: + version "1.0.2" + resolved "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== + dependencies: + minimist "^1.2.0" + +json5@^2.1.2, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.npmmirror.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +klona@^2.0.4, klona@^2.0.5: + version "2.0.6" + resolved "https://registry.npmmirror.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22" + integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== + +launch-editor-middleware@^2.2.1: + version "2.6.1" + resolved "https://registry.npmmirror.com/launch-editor-middleware/-/launch-editor-middleware-2.6.1.tgz#7f2f400d8dda2283b69d02e9d83b1d272fef2bfb" + integrity sha512-Fg/xYhf7ARmRp40n18wIfJyuAMEjXo67Yull7uF7d0OJ3qA4EYJISt1XfPPn69IIJ5jKgQwzcg6DqHYo95LL/g== + dependencies: + launch-editor "^2.6.1" + +launch-editor@^2.2.1, launch-editor@^2.6.0, launch-editor@^2.6.1: + version "2.6.1" + resolved "https://registry.npmmirror.com/launch-editor/-/launch-editor-2.6.1.tgz#f259c9ef95cbc9425620bbbd14b468fcdb4ffe3c" + integrity sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw== + dependencies: + picocolors "^1.0.0" + shell-quote "^1.8.1" + +less-loader@^8.0.0: + version "8.1.1" + resolved "https://registry.npmmirror.com/less-loader/-/less-loader-8.1.1.tgz#ababe912580457ad00a4318146aac5b53e023f42" + integrity sha512-K93jJU7fi3n6rxVvzp8Cb88Uy9tcQKfHlkoezHwKILXhlNYiRQl4yowLIkQqmBXOH/5I8yoKiYeIf781HGkW9g== + dependencies: + klona "^2.0.4" + +less@^4.0.0: + version "4.2.0" + resolved "https://registry.npmmirror.com/less/-/less-4.2.0.tgz#cbefbfaa14a4cd388e2099b2b51f956e1465c450" + integrity sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA== + dependencies: + copy-anything "^2.0.1" + parse-node-version "^1.0.1" + tslib "^2.3.0" + optionalDependencies: + errno "^0.1.1" + graceful-fs "^4.1.2" + image-size "~0.5.0" + make-dir "^2.1.0" + mime "^1.4.1" + needle "^3.1.0" + source-map "~0.6.0" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lilconfig@^2.0.3: + version "2.1.0" + resolved "https://registry.npmmirror.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" + integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +loader-runner@^4.1.0, loader-runner@^4.2.0: + version "4.3.0" + resolved "https://registry.npmmirror.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== + +loader-utils@^1.0.2, loader-utils@^1.1.0: + version "1.4.2" + resolved "https://registry.npmmirror.com/loader-utils/-/loader-utils-1.4.2.tgz#29a957f3a63973883eb684f10ffd3d151fec01a3" + integrity sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^1.0.1" + +loader-utils@^2.0.0: + version "2.0.4" + resolved "https://registry.npmmirror.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" + integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== + +lodash.defaultsdeep@^4.6.1: + version "4.6.1" + resolved "https://registry.npmmirror.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz#512e9bd721d272d94e3d3a63653fa17516741ca6" + integrity sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA== + +lodash.kebabcase@^4.1.1: + version "4.1.1" + resolved "https://registry.npmmirror.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" + integrity sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g== + +lodash.mapvalues@^4.6.0: + version "4.6.0" + resolved "https://registry.npmmirror.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c" + integrity sha512-JPFqXFeZQ7BfS00H58kClY7SPVeHertPE0lNuCyZ26/XlN8TvakYD7b9bGyNmXbT/D3BbtPAAmq90gPWqLkxlQ== + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.npmmirror.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash.truncate@^4.4.2: + version "4.4.2" + resolved "https://registry.npmmirror.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" + integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.npmmirror.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== + +lodash@^4.17.14, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.5: + version "4.17.21" + resolved "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.npmmirror.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +log-update@^2.3.0: + version "2.3.0" + resolved "https://registry.npmmirror.com/log-update/-/log-update-2.3.0.tgz#88328fd7d1ce7938b29283746f0b1bc126b24708" + integrity sha512-vlP11XfFGyeNQlmEn9tJ66rEW1coA/79m5z6BCkudjbAGE83uhAcGYrBFwfs3AdLiLzGRusRPAbSPK9xZteCmg== + dependencies: + ansi-escapes "^3.0.0" + cli-cursor "^2.0.0" + wrap-ansi "^3.0.1" + +loose-envify@^1.0.0: + version "1.4.0" + resolved "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.npmmirror.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + dependencies: + tslib "^2.0.3" + +lru-cache@^4.0.1, lru-cache@^4.1.2: + version "4.1.5" + resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" + integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +magic-string@^0.30.10: + version "0.30.10" + resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.10.tgz#123d9c41a0cb5640c892b041d4cfb3bd0aa4b39e" + integrity sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" + +make-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +make-dir@^3.0.2, make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.npmmirror.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.npmmirror.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +memfs@^3.4.3: + version "3.6.0" + resolved "https://registry.npmmirror.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" + integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== + dependencies: + fs-monkey "^1.0.4" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + +merge-source-map@^1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/merge-source-map/-/merge-source-map-1.1.0.tgz#2fdde7e6020939f70906a68f2d7ae685e4c8c646" + integrity sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw== + dependencies: + source-map "^0.6.1" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: + version "4.0.7" + resolved "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5" + integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": + version "1.52.0" + resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0, mime@^1.4.1: + version "1.6.0" + resolved "https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mini-css-extract-plugin@^2.5.3: + version "2.9.0" + resolved "https://registry.npmmirror.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz#c73a1327ccf466f69026ac22a8e8fd707b78a235" + integrity sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA== + dependencies: + schema-utils "^4.0.0" + tapable "^2.2.1" + +minimalistic-assert@^1.0.0: + version "1.0.1" + resolved "https://registry.npmmirror.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimatch@^3.0.4, minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +minipass@^3.1.1: + version "3.3.6" + resolved "https://registry.npmmirror.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +mkdirp@^0.5.6: + version "0.5.6" + resolved "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +module-alias@^2.2.2: + version "2.2.3" + resolved "https://registry.npmmirror.com/module-alias/-/module-alias-2.2.3.tgz#ec2e85c68973bda6ab71ce7c93b763ec96053221" + integrity sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q== + +moment@^2.21.0: + version "2.30.1" + resolved "https://registry.npmmirror.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== + +mrmime@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/mrmime/-/mrmime-2.0.0.tgz#151082a6e06e59a9a39b46b3e14d5cfe92b3abb4" + integrity sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.npmmirror.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3, ms@^2.1.1: + version "2.1.3" + resolved "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +multicast-dns@^7.2.5: + version "7.2.5" + resolved "https://registry.npmmirror.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" + integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== + dependencies: + dns-packet "^5.2.2" + thunky "^1.0.2" + +mutationobserver-shim@^0.3.2: + version "0.3.7" + resolved "https://registry.npmmirror.com/mutationobserver-shim/-/mutationobserver-shim-0.3.7.tgz#8bf633b0c0b0291a1107255ed32c13088a8c5bf3" + integrity sha512-oRIDTyZQU96nAiz2AQyngwx1e89iApl2hN5AOYwyxLUB47UYsU3Wv9lJWqH5y/QdiYkc5HQLi23ZNB3fELdHcQ== + +mz@^2.4.0: + version "2.7.0" + resolved "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + +nanopop@^2.1.0: + version "2.4.2" + resolved "https://registry.npmmirror.com/nanopop/-/nanopop-2.4.2.tgz#b55482135be7e64f2d0f5aa8ef51a58104ac7b13" + integrity sha512-NzOgmMQ+elxxHeIha+OG/Pv3Oc3p4RU2aBhwWwAqDpXrdTbtRylbRLQztLy8dMMwfl6pclznBdfUhccEn9ZIzw== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +needle@^3.1.0: + version "3.3.1" + resolved "https://registry.npmmirror.com/needle/-/needle-3.3.1.tgz#63f75aec580c2e77e209f3f324e2cdf3d29bd049" + integrity sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q== + dependencies: + iconv-lite "^0.6.3" + sax "^1.2.4" + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.npmmirror.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.npmmirror.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.npmmirror.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== + dependencies: + lower-case "^2.0.2" + tslib "^2.0.3" + +node-emoji@^1.10.0: + version "1.11.0" + resolved "https://registry.npmmirror.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c" + integrity sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A== + dependencies: + lodash "^4.17.21" + +node-fetch@^2.6.7: + version "2.7.0" + resolved "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + +node-forge@^1: + version "1.3.1" + resolved "https://registry.npmmirror.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + +normalize-package-data@^2.5.0: + version "2.5.0" + resolved "https://registry.npmmirror.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/normalize-path/-/normalize-path-1.0.0.tgz#32d0e472f91ff345701c15a8311018d3b0a90379" + integrity sha512-7WyT0w8jhpDStXRq5836AMmihQwq2nrUVQrgjvUo/p/NZf9uy/MeJ246lBJVmWuYXMlJuG9BNZHF0hWjfTbQUA== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== + +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.npmmirror.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw== + dependencies: + path-key "^2.0.0" + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + +object-assign@4.x, object-assign@^4.0.1: + version "4.1.1" + resolved "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.13.1: + version "1.13.1" + resolved "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" + integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.0: + version "4.1.5" + resolved "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" + integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== + dependencies: + call-bind "^1.0.5" + define-properties "^1.2.1" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +obuf@^1.0.0, obuf@^1.1.2: + version "1.1.2" + resolved "https://registry.npmmirror.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + +omit.js@^1.0.0: + version "1.0.2" + resolved "https://registry.npmmirror.com/omit.js/-/omit.js-1.0.2.tgz#91a14f0eba84066dfa015bf30e474c47f30bc858" + integrity sha512-/QPc6G2NS+8d4L/cQhbk6Yit1WTB6Us2g84A7A/1+w9d/eRGHyEqC5kkQtHVoHZ5NFWGG7tUGgrhVZwgZanKrQ== + dependencies: + babel-runtime "^6.23.0" + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.npmmirror.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + integrity sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ== + dependencies: + mimic-fn "^1.0.0" + +onetime@^5.1.0, onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +open@^8.0.2, open@^8.0.9: + version "8.4.2" + resolved "https://registry.npmmirror.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" + integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== + dependencies: + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" + +opener@^1.5.2: + version "1.5.2" + resolved "https://registry.npmmirror.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" + integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== + +optionator@^0.9.1: + version "0.9.4" + resolved "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +ora@^5.3.0: + version "5.4.1" + resolved "https://registry.npmmirror.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== + dependencies: + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-retry@^4.5.0: + version "4.6.2" + resolved "https://registry.npmmirror.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" + integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== + dependencies: + "@types/retry" "0.12.0" + retry "^0.13.1" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +param-case@^3.0.4: + version "3.0.4" + resolved "https://registry.npmmirror.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" + integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse-node-version@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b" + integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA== + +parse5-htmlparser2-tree-adapter@^6.0.0: + version "6.0.1" + resolved "https://registry.npmmirror.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" + integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== + dependencies: + parse5 "^6.0.1" + +parse5@^5.1.1: + version "5.1.1" + resolved "https://registry.npmmirror.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" + integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== + +parse5@^6.0.1: + version "6.0.1" + resolved "https://registry.npmmirror.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + +parseurl@~1.3.2, parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pascal-case@^3.1.2: + version "3.1.2" + resolved "https://registry.npmmirror.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" + integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^2.0.0, path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.npmmirror.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== + +picocolors@^0.2.1: + version "0.2.1" + resolved "https://registry.npmmirror.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" + integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== + +picocolors@^1.0.0, picocolors@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" + integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +pkg-dir@^4.1.0: + version "4.2.0" + resolved "https://registry.npmmirror.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +portfinder@^1.0.26: + version "1.0.32" + resolved "https://registry.npmmirror.com/portfinder/-/portfinder-1.0.32.tgz#2fe1b9e58389712429dc2bea5beb2146146c7f81" + integrity sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg== + dependencies: + async "^2.6.4" + debug "^3.2.7" + mkdirp "^0.5.6" + +postcss-calc@^8.2.3: + version "8.2.4" + resolved "https://registry.npmmirror.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5" + integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q== + dependencies: + postcss-selector-parser "^6.0.9" + postcss-value-parser "^4.2.0" + +postcss-colormin@^5.3.1: + version "5.3.1" + resolved "https://registry.npmmirror.com/postcss-colormin/-/postcss-colormin-5.3.1.tgz#86c27c26ed6ba00d96c79e08f3ffb418d1d1988f" + integrity sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ== + dependencies: + browserslist "^4.21.4" + caniuse-api "^3.0.0" + colord "^2.9.1" + postcss-value-parser "^4.2.0" + +postcss-convert-values@^5.1.3: + version "5.1.3" + resolved "https://registry.npmmirror.com/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz#04998bb9ba6b65aa31035d669a6af342c5f9d393" + integrity sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA== + dependencies: + browserslist "^4.21.4" + postcss-value-parser "^4.2.0" + +postcss-discard-comments@^5.1.2: + version "5.1.2" + resolved "https://registry.npmmirror.com/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz#8df5e81d2925af2780075840c1526f0660e53696" + integrity sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ== + +postcss-discard-duplicates@^5.1.0: + version "5.1.0" + resolved "https://registry.npmmirror.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848" + integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw== + +postcss-discard-empty@^5.1.1: + version "5.1.1" + resolved "https://registry.npmmirror.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c" + integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A== + +postcss-discard-overridden@^5.1.0: + version "5.1.0" + resolved "https://registry.npmmirror.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e" + integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw== + +postcss-loader@^6.1.1: + version "6.2.1" + resolved "https://registry.npmmirror.com/postcss-loader/-/postcss-loader-6.2.1.tgz#0895f7346b1702103d30fdc66e4d494a93c008ef" + integrity sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q== + dependencies: + cosmiconfig "^7.0.0" + klona "^2.0.5" + semver "^7.3.5" + +postcss-merge-longhand@^5.1.7: + version "5.1.7" + resolved "https://registry.npmmirror.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz#24a1bdf402d9ef0e70f568f39bdc0344d568fb16" + integrity sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ== + dependencies: + postcss-value-parser "^4.2.0" + stylehacks "^5.1.1" + +postcss-merge-rules@^5.1.4: + version "5.1.4" + resolved "https://registry.npmmirror.com/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz#2f26fa5cacb75b1402e213789f6766ae5e40313c" + integrity sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g== + dependencies: + browserslist "^4.21.4" + caniuse-api "^3.0.0" + cssnano-utils "^3.1.0" + postcss-selector-parser "^6.0.5" + +postcss-minify-font-values@^5.1.0: + version "5.1.0" + resolved "https://registry.npmmirror.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b" + integrity sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-minify-gradients@^5.1.1: + version "5.1.1" + resolved "https://registry.npmmirror.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz#f1fe1b4f498134a5068240c2f25d46fcd236ba2c" + integrity sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw== + dependencies: + colord "^2.9.1" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" + +postcss-minify-params@^5.1.4: + version "5.1.4" + resolved "https://registry.npmmirror.com/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz#c06a6c787128b3208b38c9364cfc40c8aa5d7352" + integrity sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw== + dependencies: + browserslist "^4.21.4" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" + +postcss-minify-selectors@^5.2.1: + version "5.2.1" + resolved "https://registry.npmmirror.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz#d4e7e6b46147b8117ea9325a915a801d5fe656c6" + integrity sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg== + dependencies: + postcss-selector-parser "^6.0.5" + +postcss-modules-extract-imports@^3.1.0: + version "3.1.0" + resolved "https://registry.npmmirror.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz#b4497cb85a9c0c4b5aabeb759bb25e8d89f15002" + integrity sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q== + +postcss-modules-local-by-default@^4.0.5: + version "4.0.5" + resolved "https://registry.npmmirror.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz#f1b9bd757a8edf4d8556e8d0f4f894260e3df78f" + integrity sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw== + dependencies: + icss-utils "^5.0.0" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.1.0" + +postcss-modules-scope@^3.2.0: + version "3.2.0" + resolved "https://registry.npmmirror.com/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz#a43d28289a169ce2c15c00c4e64c0858e43457d5" + integrity sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ== + dependencies: + postcss-selector-parser "^6.0.4" + +postcss-modules-values@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" + integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== + dependencies: + icss-utils "^5.0.0" + +postcss-normalize-charset@^5.1.0: + version "5.1.0" + resolved "https://registry.npmmirror.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed" + integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg== + +postcss-normalize-display-values@^5.1.0: + version "5.1.0" + resolved "https://registry.npmmirror.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8" + integrity sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-positions@^5.1.1: + version "5.1.1" + resolved "https://registry.npmmirror.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz#ef97279d894087b59325b45c47f1e863daefbb92" + integrity sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-repeat-style@^5.1.1: + version "5.1.1" + resolved "https://registry.npmmirror.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz#e9eb96805204f4766df66fd09ed2e13545420fb2" + integrity sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-string@^5.1.0: + version "5.1.0" + resolved "https://registry.npmmirror.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228" + integrity sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-timing-functions@^5.1.0: + version "5.1.0" + resolved "https://registry.npmmirror.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb" + integrity sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-unicode@^5.1.1: + version "5.1.1" + resolved "https://registry.npmmirror.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz#f67297fca3fea7f17e0d2caa40769afc487aa030" + integrity sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA== + dependencies: + browserslist "^4.21.4" + postcss-value-parser "^4.2.0" + +postcss-normalize-url@^5.1.0: + version "5.1.0" + resolved "https://registry.npmmirror.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc" + integrity sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew== + dependencies: + normalize-url "^6.0.1" + postcss-value-parser "^4.2.0" + +postcss-normalize-whitespace@^5.1.1: + version "5.1.1" + resolved "https://registry.npmmirror.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa" + integrity sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-ordered-values@^5.1.3: + version "5.1.3" + resolved "https://registry.npmmirror.com/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz#b6fd2bd10f937b23d86bc829c69e7732ce76ea38" + integrity sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ== + dependencies: + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" + +postcss-reduce-initial@^5.1.2: + version "5.1.2" + resolved "https://registry.npmmirror.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz#798cd77b3e033eae7105c18c9d371d989e1382d6" + integrity sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg== + dependencies: + browserslist "^4.21.4" + caniuse-api "^3.0.0" + +postcss-reduce-transforms@^5.1.0: + version "5.1.0" + resolved "https://registry.npmmirror.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9" + integrity sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9: + version "6.0.16" + resolved "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz#3b88b9f5c5abd989ef4e2fc9ec8eedd34b20fb04" + integrity sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-svgo@^5.1.0: + version "5.1.0" + resolved "https://registry.npmmirror.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d" + integrity sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA== + dependencies: + postcss-value-parser "^4.2.0" + svgo "^2.7.0" + +postcss-unique-selectors@^5.1.1: + version "5.1.1" + resolved "https://registry.npmmirror.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6" + integrity sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA== + dependencies: + postcss-selector-parser "^6.0.5" + +postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@^7.0.36: + version "7.0.39" + resolved "https://registry.npmmirror.com/postcss/-/postcss-7.0.39.tgz#9624375d965630e2e1f2c02a935c82a59cb48309" + integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA== + dependencies: + picocolors "^0.2.1" + source-map "^0.6.1" + +postcss@^8.2.6, postcss@^8.3.5, postcss@^8.4.14, postcss@^8.4.33, postcss@^8.4.38: + version "8.4.38" + resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" + integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.2.0" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +"prettier@^1.18.2 || ^2.0.0": + version "2.8.8" + resolved "https://registry.npmmirror.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + +pretty-error@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6" + integrity sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw== + dependencies: + lodash "^4.17.20" + renderkid "^3.0.0" + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +progress-webpack-plugin@^1.0.12: + version "1.0.16" + resolved "https://registry.npmmirror.com/progress-webpack-plugin/-/progress-webpack-plugin-1.0.16.tgz#278f5c1afd21af783aad72c5ec95241520230fe5" + integrity sha512-sdiHuuKOzELcBANHfrupYo+r99iPRyOnw15qX+rNlVUqXGfjXdH4IgxriKwG1kNJwVswKQHMdj1hYZMcb9jFaA== + dependencies: + chalk "^2.1.0" + figures "^2.0.0" + log-update "^2.3.0" + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" + integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + integrity sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ== + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +qs@6.11.0: + version "6.11.0" + resolved "https://registry.npmmirror.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +raf@^3.4.0: + version "3.4.1" + resolved "https://registry.npmmirror.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== + dependencies: + performance-now "^2.1.0" + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +range-parser@^1.2.1, range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.npmmirror.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +read-pkg-up@^7.0.1: + version "7.0.1" + resolved "https://registry.npmmirror.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" + integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== + dependencies: + find-up "^4.1.0" + read-pkg "^5.2.0" + type-fest "^0.8.1" + +read-pkg@^5.1.1, read-pkg@^5.2.0: + version "5.2.0" + resolved "https://registry.npmmirror.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + +readable-stream@^2.0.1: + version "2.3.8" + resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.0.6, readable-stream@^3.4.0: + version "3.6.2" + resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +regenerate-unicode-properties@^10.1.0: + version "10.1.1" + resolved "https://registry.npmmirror.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz#6b0e05489d9076b04c436f318d9b067bba459480" + integrity sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q== + dependencies: + regenerate "^1.4.2" + +regenerate@^1.4.2: + version "1.4.2" + resolved "https://registry.npmmirror.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + +regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== + +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + +regenerator-transform@^0.15.2: + version "0.15.2" + resolved "https://registry.npmmirror.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz#5bbae58b522098ebdf09bca2f83838929001c7a4" + integrity sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg== + dependencies: + "@babel/runtime" "^7.8.4" + +regexpp@^3.1.0: + version "3.2.0" + resolved "https://registry.npmmirror.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + +regexpu-core@^5.3.1: + version "5.3.2" + resolved "https://registry.npmmirror.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b" + integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ== + dependencies: + "@babel/regjsgen" "^0.8.0" + regenerate "^1.4.2" + regenerate-unicode-properties "^10.1.0" + regjsparser "^0.9.1" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.1.0" + +regjsparser@^0.9.1: + version "0.9.1" + resolved "https://registry.npmmirror.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" + integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== + dependencies: + jsesc "~0.5.0" + +relateurl@^0.2.7: + version "0.2.7" + resolved "https://registry.npmmirror.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" + integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== + +renderkid@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/renderkid/-/renderkid-3.0.0.tgz#5fd823e4d6951d37358ecc9a58b1f06836b6268a" + integrity sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg== + dependencies: + css-select "^4.1.3" + dom-converter "^0.2.0" + htmlparser2 "^6.1.0" + lodash "^4.17.21" + strip-ansi "^6.0.1" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + +resize-observer-polyfill@^1.5.1: + version "1.5.1" + resolved "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" + integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve@^1.10.0, resolve@^1.14.2: + version "1.22.8" + resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + integrity sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q== + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +retry@^0.13.1: + version "0.13.1" + resolved "https://registry.npmmirror.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": + version "2.1.2" + resolved "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@^1.2.4: + version "1.3.0" + resolved "https://registry.npmmirror.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0" + integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA== + +schema-utils@^2.6.5: + version "2.7.1" + resolved "https://registry.npmmirror.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" + integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== + dependencies: + "@types/json-schema" "^7.0.5" + ajv "^6.12.4" + ajv-keywords "^3.5.2" + +schema-utils@^3.0.0, schema-utils@^3.1.1, schema-utils@^3.2.0: + version "3.3.0" + resolved "https://registry.npmmirror.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" + integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +schema-utils@^4.0.0: + version "4.2.0" + resolved "https://registry.npmmirror.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b" + integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.9.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.1.0" + +select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== + +selfsigned@^2.1.1: + version "2.4.1" + resolved "https://registry.npmmirror.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0" + integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q== + dependencies: + "@types/node-forge" "^1.3.0" + node-forge "^1" + +"semver@2 || 3 || 4 || 5", semver@^5.5.0, semver@^5.6.0: + version "5.7.2" + resolved "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== + +semver@^6.0.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.2.1, semver@^7.3.4, semver@^7.3.5, semver@^7.5.4: + version "7.6.2" + resolved "https://registry.npmmirror.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" + integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== + +send@0.18.0: + version "0.18.0" + resolved "https://registry.npmmirror.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serialize-javascript@^6.0.0, serialize-javascript@^6.0.1: + version "6.0.2" + resolved "https://registry.npmmirror.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== + dependencies: + randombytes "^2.1.0" + +seroval-plugins@^1.0.3: + version "1.0.7" + resolved "https://registry.npmmirror.com/seroval-plugins/-/seroval-plugins-1.0.7.tgz#c02511a1807e9bc8f68a91fbec13474fa9cea670" + integrity sha512-GO7TkWvodGp6buMEX9p7tNyIkbwlyuAWbI6G9Ec5bhcm7mQdu3JOK1IXbEUwb3FVzSc363GraG/wLW23NSavIw== + +seroval@^1.0.4: + version "1.0.7" + resolved "https://registry.npmmirror.com/seroval/-/seroval-1.0.7.tgz#ee48ad8ba69f1595bdd5c55d1a0d1da29dee7455" + integrity sha512-n6ZMQX5q0Vn19Zq7CIKNIo7E75gPkGCFUEqDpa8jgwpYr/vScjqnQ6H09t1uIiZ0ZSK0ypEGvrYK2bhBGWsGdw== + +serve-index@^1.9.1: + version "1.9.1" + resolved "https://registry.npmmirror.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" + integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== + dependencies: + accepts "~1.3.4" + batch "0.6.1" + debug "2.6.9" + escape-html "~1.0.3" + http-errors "~1.6.2" + mime-types "~2.1.17" + parseurl "~1.3.2" + +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.npmmirror.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.npmmirror.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shallow-equal@^1.0.0: + version "1.2.1" + resolved "https://registry.npmmirror.com/shallow-equal/-/shallow-equal-1.2.1.tgz#4c16abfa56043aa20d050324efa68940b0da79da" + integrity sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA== + +shallowequal@^1.0.2: + version "1.1.0" + resolved "https://registry.npmmirror.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.npmmirror.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== + dependencies: + shebang-regex "^1.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shell-quote@^1.8.1: + version "1.8.1" + resolved "https://registry.npmmirror.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" + integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== + +side-channel@^1.0.4: + version "1.0.6" + resolved "https://registry.npmmirror.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: + version "3.0.7" + resolved "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +sirv@^2.0.3: + version "2.0.4" + resolved "https://registry.npmmirror.com/sirv/-/sirv-2.0.4.tgz#5dd9a725c578e34e449f332703eb2a74e46a29b0" + integrity sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ== + dependencies: + "@polka/url" "^1.0.0-next.24" + mrmime "^2.0.0" + totalist "^3.0.0" + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + +sockjs@^0.3.24: + version "0.3.24" + resolved "https://registry.npmmirror.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" + integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== + dependencies: + faye-websocket "^0.11.3" + uuid "^8.3.2" + websocket-driver "^0.7.4" + +solid-js@^1.3.0: + version "1.8.17" + resolved "https://registry.npmmirror.com/solid-js/-/solid-js-1.8.17.tgz#780ed6f0fd8633009d1b3c29d56bf6b6bb33bd50" + integrity sha512-E0FkUgv9sG/gEBWkHr/2XkBluHb1fkrHywUgA6o6XolPDCJ4g1HaLmQufcBBhiF36ee40q+HpG/vCZu7fLpI3Q== + dependencies: + csstype "^3.1.0" + seroval "^1.0.4" + seroval-plugins "^1.0.3" + +source-map-js@^1.2.0: + version "1.2.0" + resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" + integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== + +source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +spdx-correct@^3.0.0: + version "3.2.0" + resolved "https://registry.npmmirror.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" + integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.5.0" + resolved "https://registry.npmmirror.com/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz#5d607d27fc806f66d7b64a766650fa890f04ed66" + integrity sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.npmmirror.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.17" + resolved "https://registry.npmmirror.com/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz#887da8aa73218e51a1d917502d79863161a93f9c" + integrity sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg== + +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== + dependencies: + debug "^4.1.0" + detect-node "^2.0.4" + hpack.js "^2.1.6" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" + +spdy@^4.0.2: + version "4.0.2" + resolved "https://registry.npmmirror.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== + dependencies: + debug "^4.1.0" + handle-thing "^2.0.0" + http-deceiver "^1.2.7" + select-hose "^2.0.0" + spdy-transport "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +ssri@^8.0.1: + version "8.0.1" + resolved "https://registry.npmmirror.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== + dependencies: + minipass "^3.1.1" + +stable@^0.1.8: + version "0.1.8" + resolved "https://registry.npmmirror.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" + integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== + +stackframe@^1.3.4: + version "1.3.4" + resolved "https://registry.npmmirror.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310" + integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw== + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +"statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.npmmirror.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== + +string-convert@^0.2.0: + version "0.2.1" + resolved "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz#6982cc3049fbb4cd85f8b24568b9d9bf39eeff97" + integrity sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A== + +string-width@^2.1.1: + version "2.1.1" + resolved "https://registry.npmmirror.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow== + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-indent@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" + integrity sha512-RsSNPLpq6YUL7QYy44RnPVTn/lcVZtb48Uof3X5JLbF4zD/Gs7ZFDv2HWol+leoQN2mT86LAzSshGfkTlSOpsA== + +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +stylehacks@^5.1.1: + version "5.1.1" + resolved "https://registry.npmmirror.com/stylehacks/-/stylehacks-5.1.1.tgz#7934a34eb59d7152149fa69d6e9e56f2fc34bcc9" + integrity sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw== + dependencies: + browserslist "^4.21.4" + postcss-selector-parser "^6.0.4" + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +svg-tags@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764" + integrity sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA== + +svgo@^2.7.0: + version "2.8.0" + resolved "https://registry.npmmirror.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" + integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^4.1.3" + css-tree "^1.1.3" + csso "^4.2.0" + picocolors "^1.0.0" + stable "^0.1.8" + +table@^6.0.9: + version "6.8.2" + resolved "https://registry.npmmirror.com/table/-/table-6.8.2.tgz#c5504ccf201213fa227248bdc8c5569716ac6c58" + integrity sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA== + dependencies: + ajv "^8.0.1" + lodash.truncate "^4.4.2" + slice-ansi "^4.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + +tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1: + version "2.2.1" + resolved "https://registry.npmmirror.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +terser-webpack-plugin@^5.1.1, terser-webpack-plugin@^5.3.10: + version "5.3.10" + resolved "https://registry.npmmirror.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199" + integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w== + dependencies: + "@jridgewell/trace-mapping" "^0.3.20" + jest-worker "^27.4.5" + schema-utils "^3.1.1" + serialize-javascript "^6.0.1" + terser "^5.26.0" + +terser@^5.10.0, terser@^5.26.0: + version "5.31.0" + resolved "https://registry.npmmirror.com/terser/-/terser-5.31.0.tgz#06eef86f17007dbad4593f11a574c7f5eb02c6a1" + integrity sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" + commander "^2.20.0" + source-map-support "~0.5.20" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + +thread-loader@^3.0.0: + version "3.0.4" + resolved "https://registry.npmmirror.com/thread-loader/-/thread-loader-3.0.4.tgz#c392e4c0241fbc80430eb680e4886819b504a31b" + integrity sha512-ByaL2TPb+m6yArpqQUZvP+5S1mZtXsEP7nWKKlAUTm7fCml8kB5s1uI3+eHRP2bk5mVYfRSBI7FFf+tWEyLZwA== + dependencies: + json-parse-better-errors "^1.0.2" + loader-runner "^4.1.0" + loader-utils "^2.0.0" + neo-async "^2.6.2" + schema-utils "^3.0.0" + +thunky@^1.0.2: + version "1.1.0" + resolved "https://registry.npmmirror.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" + integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== + +tinycolor2@^1.4.1: + version "1.6.0" + resolved "https://registry.npmmirror.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e" + integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +totalist@^3.0.0: + version "3.0.1" + resolved "https://registry.npmmirror.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8" + integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ== + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +tslib@^1.10.0: + version "1.14.1" + resolved "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1: + version "2.6.2" + resolved "https://registry.npmmirror.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.6.0: + version "0.6.0" + resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.npmmirror.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.npmmirror.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +unicode-canonical-property-names-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" + integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== + +unicode-match-property-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" + integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== + dependencies: + unicode-canonical-property-names-ecmascript "^2.0.0" + unicode-property-aliases-ecmascript "^2.0.0" + +unicode-match-property-value-ecmascript@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0" + integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== + +unicode-property-aliases-ecmascript@^2.0.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" + integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== + +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +update-browserslist-db@^1.0.13: + version "1.0.16" + resolved "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz#f6d489ed90fb2f07d67784eb3f53d7891f736356" + integrity sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ== + dependencies: + escalade "^3.1.2" + picocolors "^1.0.1" + +uri-js@^4.2.2, uri-js@^4.4.1: + version "4.4.1" + resolved "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +utila@~0.4: + version "0.4.0" + resolved "https://registry.npmmirror.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" + integrity sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +v8-compile-cache@^2.0.3: + version "2.4.0" + resolved "https://registry.npmmirror.com/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz#cdada8bec61e15865f05d097c5f4fd30e94dc128" + integrity sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw== + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.npmmirror.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +vue-eslint-parser@^8.0.1: + version "8.3.0" + resolved "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz#5d31129a1b3dd89c0069ca0a1c88f970c360bd0d" + integrity sha512-dzHGG3+sYwSf6zFBa0Gi9ZDshD7+ad14DGOdTLjruRVgZXe2J+DcZ9iUhyR48z5g1PqRa20yt3Njna/veLJL/g== + dependencies: + debug "^4.3.2" + eslint-scope "^7.0.0" + eslint-visitor-keys "^3.1.0" + espree "^9.0.0" + esquery "^1.4.0" + lodash "^4.17.21" + semver "^7.3.5" + +vue-hot-reload-api@^2.3.0: + version "2.3.4" + resolved "https://registry.npmmirror.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2" + integrity sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog== + +vue-loader@^17.0.0: + version "17.4.2" + resolved "https://registry.npmmirror.com/vue-loader/-/vue-loader-17.4.2.tgz#f87f0d8adfcbbe8623de9eba1979d41ba223c6da" + integrity sha512-yTKOA4R/VN4jqjw4y5HrynFL8AK0Z3/Jt7eOJXEitsm0GMRHDBjCfCiuTiLP7OESvsZYo2pATCWhDqxC5ZrM6w== + dependencies: + chalk "^4.1.0" + hash-sum "^2.0.0" + watchpack "^2.4.0" + +vue-ref@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/vue-ref/-/vue-ref-2.0.0.tgz#483084d732abed11da796778a8266a3af0ea1a9c" + integrity sha512-uKNKpFOVeWNqS2mrBZqnpLyXJo5Q+vnkex6JvpENvhXHFNBW/SJTP8vJywLuVT3DpxwXcF9N0dyIiZ4/NpTexQ== + +vue-router@^3.5.1: + version "3.6.5" + resolved "https://registry.npmmirror.com/vue-router/-/vue-router-3.6.5.tgz#95847d52b9a7e3f1361cb605c8e6441f202afad8" + integrity sha512-VYXZQLtjuvKxxcshuRAwjHnciqZVoXAjTjcqBTz4rKc8qih9g9pI3hbDjmqXaHdgL3v8pV6P8Z335XvHzESxLQ== + +vue-style-loader@^4.1.0, vue-style-loader@^4.1.3: + version "4.1.3" + resolved "https://registry.npmmirror.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz#6d55863a51fa757ab24e89d9371465072aa7bc35" + integrity sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg== + dependencies: + hash-sum "^1.0.2" + loader-utils "^1.0.2" + +vue-template-compiler@^2.6.14: + version "2.7.16" + resolved "https://registry.npmmirror.com/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz#c81b2d47753264c77ac03b9966a46637482bb03b" + integrity sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ== + dependencies: + de-indent "^1.0.2" + he "^1.2.0" + +vue-template-es2015-compiler@^1.9.0: + version "1.9.1" + resolved "https://registry.npmmirror.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz#1ee3bc9a16ecbf5118be334bb15f9c46f82f5825" + integrity sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw== + +vue2-ace-editor@^0.0.15: + version "0.0.15" + resolved "https://registry.npmmirror.com/vue2-ace-editor/-/vue2-ace-editor-0.0.15.tgz#569b208e54ae771ae1edd3b8902ac42f0edc74e3" + integrity sha512-e3TR9OGXc71cGpvYcW068lNpRcFt3+OONCC81oxHL/0vwl/V3OgqnNMw2/RRolgQkO/CA5AjqVHWmANWKOtNnQ== + dependencies: + brace "^0.11.0" + +vue@^2.6.14: + version "2.7.16" + resolved "https://registry.npmmirror.com/vue/-/vue-2.7.16.tgz#98c60de9def99c0e3da8dae59b304ead43b967c9" + integrity sha512-4gCtFXaAA3zYZdTp5s4Hl2sozuySsgz4jy1EnpBHNfpMa9dK1ZCG7viqBPCwXtmgc8nHqUsAu3G4gtmXkkY3Sw== + dependencies: + "@vue/compiler-sfc" "2.7.16" + csstype "^3.1.0" + +warning@^4.0.0: + version "4.0.3" + resolved "https://registry.npmmirror.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + +watchpack@^2.4.0, watchpack@^2.4.1: + version "2.4.1" + resolved "https://registry.npmmirror.com/watchpack/-/watchpack-2.4.1.tgz#29308f2cac150fa8e4c92f90e0ec954a9fed7fff" + integrity sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +wbuf@^1.1.0, wbuf@^1.7.3: + version "1.7.3" + resolved "https://registry.npmmirror.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" + integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== + dependencies: + minimalistic-assert "^1.0.0" + +wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== + dependencies: + defaults "^1.0.3" + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +webpack-bundle-analyzer@^4.4.0: + version "4.10.2" + resolved "https://registry.npmmirror.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz#633af2862c213730be3dbdf40456db171b60d5bd" + integrity sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw== + dependencies: + "@discoveryjs/json-ext" "0.5.7" + acorn "^8.0.4" + acorn-walk "^8.0.0" + commander "^7.2.0" + debounce "^1.2.1" + escape-string-regexp "^4.0.0" + gzip-size "^6.0.0" + html-escaper "^2.0.2" + opener "^1.5.2" + picocolors "^1.0.0" + sirv "^2.0.3" + ws "^7.3.1" + +webpack-chain@^6.5.1: + version "6.5.1" + resolved "https://registry.npmmirror.com/webpack-chain/-/webpack-chain-6.5.1.tgz#4f27284cbbb637e3c8fbdef43eef588d4d861206" + integrity sha512-7doO/SRtLu8q5WM0s7vPKPWX580qhi0/yBHkOxNkv50f6qB76Zy9o2wRTrrPULqYTvQlVHuvbA8v+G5ayuUDsA== + dependencies: + deepmerge "^1.5.2" + javascript-stringify "^2.0.1" + +webpack-dev-middleware@^5.3.4: + version "5.3.4" + resolved "https://registry.npmmirror.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz#eb7b39281cbce10e104eb2b8bf2b63fce49a3517" + integrity sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q== + dependencies: + colorette "^2.0.10" + memfs "^3.4.3" + mime-types "^2.1.31" + range-parser "^1.2.1" + schema-utils "^4.0.0" + +webpack-dev-server@^4.7.3: + version "4.15.2" + resolved "https://registry.npmmirror.com/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz#9e0c70a42a012560860adb186986da1248333173" + integrity sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g== + dependencies: + "@types/bonjour" "^3.5.9" + "@types/connect-history-api-fallback" "^1.3.5" + "@types/express" "^4.17.13" + "@types/serve-index" "^1.9.1" + "@types/serve-static" "^1.13.10" + "@types/sockjs" "^0.3.33" + "@types/ws" "^8.5.5" + ansi-html-community "^0.0.8" + bonjour-service "^1.0.11" + chokidar "^3.5.3" + colorette "^2.0.10" + compression "^1.7.4" + connect-history-api-fallback "^2.0.0" + default-gateway "^6.0.3" + express "^4.17.3" + graceful-fs "^4.2.6" + html-entities "^2.3.2" + http-proxy-middleware "^2.0.3" + ipaddr.js "^2.0.1" + launch-editor "^2.6.0" + open "^8.0.9" + p-retry "^4.5.0" + rimraf "^3.0.2" + schema-utils "^4.0.0" + selfsigned "^2.1.1" + serve-index "^1.9.1" + sockjs "^0.3.24" + spdy "^4.0.2" + webpack-dev-middleware "^5.3.4" + ws "^8.13.0" + +webpack-merge@^5.7.3: + version "5.10.0" + resolved "https://registry.npmmirror.com/webpack-merge/-/webpack-merge-5.10.0.tgz#a3ad5d773241e9c682803abf628d4cd62b8a4177" + integrity sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA== + dependencies: + clone-deep "^4.0.1" + flat "^5.0.2" + wildcard "^2.0.0" + +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.npmmirror.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +webpack-virtual-modules@^0.4.2: + version "0.4.6" + resolved "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.4.6.tgz#3e4008230731f1db078d9cb6f68baf8571182b45" + integrity sha512-5tyDlKLqPfMqjT3Q9TAqf2YqjwmnUleZwzJi1A5qXnlBCdj2AtOJ6wAWdglTIDOPgOiOrXeBeFcsQ8+aGQ6QbA== + +webpack@^5.54.0: + version "5.91.0" + resolved "https://registry.npmmirror.com/webpack/-/webpack-5.91.0.tgz#ffa92c1c618d18c878f06892bbdc3373c71a01d9" + integrity sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw== + dependencies: + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^1.0.5" + "@webassemblyjs/ast" "^1.12.1" + "@webassemblyjs/wasm-edit" "^1.12.1" + "@webassemblyjs/wasm-parser" "^1.12.1" + acorn "^8.7.1" + acorn-import-assertions "^1.9.0" + browserslist "^4.21.10" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.16.0" + es-module-lexer "^1.2.1" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.11" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.2.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.3.10" + watchpack "^2.4.1" + webpack-sources "^3.2.3" + +websocket-driver@>=0.5.1, websocket-driver@^0.7.4: + version "0.7.4" + resolved "https://registry.npmmirror.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.npmmirror.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + +whatwg-fetch@^3.6.2: + version "3.6.20" + resolved "https://registry.npmmirror.com/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz#580ce6d791facec91d37c72890995a0b48d31c70" + integrity sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.npmmirror.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.npmmirror.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wildcard@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" + integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +wrap-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba" + integrity sha512-iXR3tDXpbnTpzjKSylUJRkLuOrEC7hwEB221cgn6wtF8wpmz28puFXAEfPT5zrjM3wahygB//VuWEr1vTkDcNQ== + dependencies: + string-width "^2.1.1" + strip-ansi "^4.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +ws@^7.3.1: + version "7.5.9" + resolved "https://registry.npmmirror.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + +ws@^8.13.0: + version "8.17.0" + resolved "https://registry.npmmirror.com/ws/-/ws-8.17.0.tgz#d145d18eca2ed25aaf791a183903f7be5e295fea" + integrity sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow== + +xterm-addon-fit@^0.5.0: + version "0.5.0" + resolved "https://registry.npmmirror.com/xterm-addon-fit/-/xterm-addon-fit-0.5.0.tgz#2d51b983b786a97dcd6cde805e700c7f913bc596" + integrity sha512-DsS9fqhXHacEmsPxBJZvfj2la30Iz9xk+UKjhQgnYNkrUIN5CYLbw7WEfz117c7+S86S/tpHPfvNxJsF5/G8wQ== + +xterm-addon-search@^0.8.1: + version "0.8.2" + resolved "https://registry.npmmirror.com/xterm-addon-search/-/xterm-addon-search-0.8.2.tgz#be7aa74d5ff12c901707c6ff674229f214318032" + integrity sha512-I1863mjn8P6uVrqm/X+btalVsqjAKLhnhpbP7SavAOpEkI1jJhbHU2UTp7NjeRtcKTks6UWk/ycgds5snDSejg== + +xterm-addon-web-links@^0.4.0: + version "0.4.0" + resolved "https://registry.npmmirror.com/xterm-addon-web-links/-/xterm-addon-web-links-0.4.0.tgz#265cbf8221b9b315d0a748e1323bee331cd5da03" + integrity sha512-xv8GeiINmx0zENO9hf5k+5bnkaE8mRzF+OBAr9WeFq2eLaQSudioQSiT34M1ofKbzcdjSsKiZm19Rw3i4eXamg== + +xterm@^4.14.1: + version "4.19.0" + resolved "https://registry.npmmirror.com/xterm/-/xterm-4.19.0.tgz#c0f9d09cd61de1d658f43ca75f992197add9ef6d" + integrity sha512-c3Cp4eOVsYY5Q839dR5IejghRPpxciGmLWWaP9g+ppfMeBChMeLa1DCA+pmX/jyDZ+zxFOmlJL/82qVdayVoGQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.npmmirror.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@^1.10.0, yaml@^1.10.2: + version "1.10.2" + resolved "https://registry.npmmirror.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs@^16.0.0: + version "16.2.0" + resolved "https://registry.npmmirror.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yorkie@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/yorkie/-/yorkie-2.0.0.tgz#92411912d435214e12c51c2ae1093e54b6bb83d9" + integrity sha512-jcKpkthap6x63MB4TxwCyuIGkV0oYP/YRyuQU5UO0Yz/E/ZAu+653/uov+phdmO54n6BcvFRyyt0RRrWdN2mpw== + dependencies: + execa "^0.8.0" + is-ci "^1.0.10" + normalize-path "^1.0.0" + strip-indent "^2.0.0" diff --git a/sql/init-1-schema.sql b/sql/init-1-schema.sql new file mode 100644 index 0000000..3c92647 --- /dev/null +++ b/sql/init-1-schema.sql @@ -0,0 +1,940 @@ +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- 创建数据库 +CREATE DATABASE IF NOT EXISTS `youdu-ops` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; +USE `youdu-ops`; + +-- ---------------------------- +-- Table structure for alarm_group +-- ---------------------------- +DROP TABLE IF EXISTS `alarm_group`; +CREATE TABLE `alarm_group` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `group_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '报警组名称', + `group_description` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '报警组描述', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '报警组' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for alarm_group_notify +-- ---------------------------- +DROP TABLE IF EXISTS `alarm_group_notify`; +CREATE TABLE `alarm_group_notify` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `group_id` bigint(0) NULL DEFAULT NULL COMMENT '报警组id', + `notify_id` bigint(0) NULL DEFAULT NULL COMMENT '通知id', + `notify_type` int(0) NULL DEFAULT NULL COMMENT '通知类型 10 webhook', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `group_idx`(`group_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '报警组通知方式' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for alarm_group_user +-- ---------------------------- +DROP TABLE IF EXISTS `alarm_group_user`; +CREATE TABLE `alarm_group_user` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `group_id` bigint(0) NULL DEFAULT NULL COMMENT '报警组id', + `user_id` bigint(0) NULL DEFAULT NULL COMMENT '报警组成员id', + `username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '报警组成员用户名', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `group_id_idx`(`group_id`, `user_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '报警组成员' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for application_action +-- ---------------------------- +DROP TABLE IF EXISTS `application_action`; +CREATE TABLE `application_action` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `app_id` bigint(0) NULL DEFAULT NULL COMMENT 'appId', + `profile_id` bigint(0) NULL DEFAULT NULL COMMENT 'profileId', + `action_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '名称', + `action_type` int(0) NULL DEFAULT NULL COMMENT '类型 110: 构建代码检出 120: 构建主机命令 210: 发布产物传输 220: 发布目标机器命令', + `stage_type` tinyint(0) NULL DEFAULT NULL COMMENT '阶段类型 10构建 20发布', + `action_command` varchar(2048) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '执行命令', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `app_profile_idx`(`app_id`, `profile_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '应用构建发布执行块' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for application_action_log +-- ---------------------------- +DROP TABLE IF EXISTS `application_action_log`; +CREATE TABLE `application_action_log` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `stage_type` tinyint(0) NULL DEFAULT NULL COMMENT '阶段类型 10构建 20发布', + `rel_id` bigint(0) NULL DEFAULT NULL COMMENT '引用id 构建id 发布机器id', + `machine_id` bigint(0) NULL DEFAULT NULL COMMENT '执行机器id', + `action_id` bigint(0) NULL DEFAULT NULL COMMENT '操作id', + `action_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作名称', + `action_type` int(0) NULL DEFAULT NULL COMMENT '操作类型', + `action_command` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作命令', + `log_path` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '操作日志路径', + `run_status` tinyint(0) NULL DEFAULT 10 COMMENT '状态 10未开始 20进行中 30已完成 40执行失败 50已跳过 60已终止', + `exit_code` int(0) NULL DEFAULT NULL COMMENT '退出码', + `start_time` datetime(4) NULL DEFAULT NULL COMMENT '开始时间', + `end_time` datetime(4) NULL DEFAULT NULL COMMENT '结束时间', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `rel_id_idx`(`stage_type`, `rel_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '应用操作日志' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for application_build +-- ---------------------------- +DROP TABLE IF EXISTS `application_build`; +CREATE TABLE `application_build` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `app_id` bigint(0) NULL DEFAULT NULL COMMENT '应用id', + `app_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '应用名称', + `app_tag` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '应用唯一标识', + `profile_id` bigint(0) NULL DEFAULT NULL COMMENT '环境id', + `profile_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '环境名称', + `profile_tag` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '环境唯一标识', + `build_seq` int(0) NULL DEFAULT NULL COMMENT '构建序列', + `branch_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '构建分支', + `commit_id` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '构建提交id', + `repo_id` bigint(0) NULL DEFAULT NULL COMMENT '版本仓库id', + `log_path` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '构建日志路径', + `bundle_path` varchar(2048) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '构建产物路径', + `build_status` tinyint(0) NULL DEFAULT 10 COMMENT '状态 10未开始 20执行中 30已完成 40执行失败 50已取消', + `description` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述', + `create_user_id` bigint(0) NULL DEFAULT NULL COMMENT '创建人id', + `create_user_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人名称', + `build_start_time` datetime(4) NULL DEFAULT NULL COMMENT '构建开始时间', + `build_end_time` datetime(4) NULL DEFAULT NULL COMMENT '构建结束时间', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '应用构建' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for application_env +-- ---------------------------- +DROP TABLE IF EXISTS `application_env`; +CREATE TABLE `application_env` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `app_id` bigint(0) NULL DEFAULT NULL COMMENT '应用id', + `profile_id` bigint(0) NULL DEFAULT NULL COMMENT '环境id', + `attr_key` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'key', + `attr_value` varchar(2048) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'value', + `system_env` tinyint(0) NULL DEFAULT 2 COMMENT '是否为系统变量 1是 2否', + `description` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `app_profile_idx`(`app_id`, `profile_id`, `attr_key`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '应用环境变量' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for application_info +-- ---------------------------- +DROP TABLE IF EXISTS `application_info`; +CREATE TABLE `application_info` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `app_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '应用名称', + `app_tag` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '应用唯一标识', + `app_sort` int(0) NULL DEFAULT NULL COMMENT '排序', + `repo_id` bigint(0) NULL DEFAULT NULL COMMENT '版本仓库id', + `description` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '应用表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for application_machine +-- ---------------------------- +DROP TABLE IF EXISTS `application_machine`; +CREATE TABLE `application_machine` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `app_id` bigint(0) NULL DEFAULT NULL COMMENT '应用id', + `profile_id` bigint(0) NULL DEFAULT NULL COMMENT '环境id', + `machine_id` bigint(0) NULL DEFAULT NULL COMMENT '机器id', + `release_id` bigint(0) NULL DEFAULT NULL COMMENT '当前版本发布id', + `build_id` bigint(0) NULL DEFAULT NULL COMMENT '当前版本构建id', + `build_seq` int(0) NULL DEFAULT NULL COMMENT '当前版本构建序列', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `app_profile_idx`(`app_id`, `profile_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '应用依赖机器表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for application_pipeline +-- ---------------------------- +DROP TABLE IF EXISTS `application_pipeline`; +CREATE TABLE `application_pipeline` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `profile_id` bigint(0) NULL DEFAULT NULL COMMENT '环境id', + `pipeline_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '流水线名称', + `description` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '修改时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '应用流水线' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for application_pipeline_detail +-- ---------------------------- +DROP TABLE IF EXISTS `application_pipeline_detail`; +CREATE TABLE `application_pipeline_detail` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `pipeline_id` bigint(0) NULL DEFAULT NULL COMMENT '流水线id', + `app_id` bigint(0) NULL DEFAULT NULL COMMENT '应用id', + `profile_id` bigint(0) NULL DEFAULT NULL COMMENT '环境id', + `stage_type` tinyint(0) NULL DEFAULT NULL COMMENT '阶段类型 10构建 20发布', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `pipeline_id_idx`(`pipeline_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '应用流水线详情' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for application_pipeline_task +-- ---------------------------- +DROP TABLE IF EXISTS `application_pipeline_task`; +CREATE TABLE `application_pipeline_task` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `pipeline_id` bigint(0) NULL DEFAULT NULL COMMENT '流水线id', + `pipeline_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '流水线名称', + `profile_id` bigint(0) NULL DEFAULT NULL COMMENT '环境id', + `profile_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '环境名称', + `profile_tag` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '环境唯一标识', + `exec_title` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '执行标题', + `exec_description` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '执行描述', + `exec_status` int(0) NULL DEFAULT NULL COMMENT '执行状态 10待审核 20审核驳回 30待执行 35待调度 40执行中 50执行完成 60执行停止 70执行失败', + `timed_exec` tinyint(0) NULL DEFAULT NULL COMMENT '是否是定时执行 10普通执行 20定时执行', + `timed_exec_time` datetime(4) NULL DEFAULT NULL COMMENT '定时执行时间', + `create_user_id` bigint(0) NULL DEFAULT NULL COMMENT '创建人id', + `create_user_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人名称', + `audit_user_id` bigint(0) NULL DEFAULT NULL COMMENT '审核人id', + `audit_user_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '审核人名称', + `audit_time` datetime(4) NULL DEFAULT NULL COMMENT '审核时间', + `audit_reason` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '审核备注', + `exec_user_id` bigint(0) NULL DEFAULT NULL COMMENT '执行人id', + `exec_user_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '执行人名称', + `exec_start_time` datetime(4) NULL DEFAULT NULL COMMENT '执行开始时间', + `exec_end_time` datetime(4) NULL DEFAULT NULL COMMENT '执行结束时间', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `pipeline_id_idx`(`pipeline_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '应用流水线任务' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for application_pipeline_task_detail +-- ---------------------------- +DROP TABLE IF EXISTS `application_pipeline_task_detail`; +CREATE TABLE `application_pipeline_task_detail` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `pipeline_id` bigint(0) NULL DEFAULT NULL COMMENT '流水线id', + `pipeline_detail_id` bigint(0) NULL DEFAULT NULL COMMENT '流水线详情id', + `task_id` bigint(0) NULL DEFAULT NULL COMMENT '流水线任务id', + `rel_id` bigint(0) NULL DEFAULT NULL COMMENT '引用id', + `app_id` bigint(0) NULL DEFAULT NULL COMMENT '应用id', + `app_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '应用名称', + `app_tag` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '应用唯一标识', + `stage_type` tinyint(0) NULL DEFAULT NULL COMMENT '阶段类型 10构建 20发布', + `stage_config` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '阶段操作配置', + `exec_status` int(0) NULL DEFAULT NULL COMMENT '状态 10未开始 20进行中 30已完成 40执行失败 50已跳过 60已终止', + `exec_start_time` datetime(4) NULL DEFAULT NULL COMMENT '执行开始时间', + `exec_end_time` datetime(4) NULL DEFAULT NULL COMMENT '执行结束时间', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `rel_id_idx`(`pipeline_id`, `pipeline_detail_id`, `task_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '应用流水线任务详情' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for application_pipeline_task_log +-- ---------------------------- +DROP TABLE IF EXISTS `application_pipeline_task_log`; +CREATE TABLE `application_pipeline_task_log` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `task_id` bigint(0) NULL DEFAULT NULL COMMENT '流水线任务id', + `task_detail_id` bigint(0) NULL DEFAULT NULL COMMENT '流水线任务详情id', + `log_status` tinyint(0) NULL DEFAULT NULL COMMENT '日志状态 10创建 20执行 30成功 40失败 50停止 60跳过', + `stage_type` tinyint(0) NULL DEFAULT NULL COMMENT '阶段类型 10构建 20发布', + `log_info` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '日志详情', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `record_idx`(`task_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '应用流水线任务日志' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for application_profile +-- ---------------------------- +DROP TABLE IF EXISTS `application_profile`; +CREATE TABLE `application_profile` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `profile_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '环境名称', + `profile_tag` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '环境唯一标识', + `description` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '环境描述', + `release_audit` tinyint(0) NULL DEFAULT 2 COMMENT '发布是否需要审核 1需要 2无需', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '应用环境表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for application_release +-- ---------------------------- +DROP TABLE IF EXISTS `application_release`; +CREATE TABLE `application_release` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `release_title` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '发布标题', + `release_description` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '发布描述', + `build_id` bigint(0) NULL DEFAULT NULL COMMENT '构建id', + `build_seq` int(0) NULL DEFAULT NULL COMMENT '构建序列', + `app_id` bigint(0) NULL DEFAULT NULL COMMENT '应用id', + `app_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '应用名称', + `app_tag` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '应用唯一标识', + `profile_id` bigint(0) NULL DEFAULT NULL COMMENT '环境id', + `profile_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '环境名称', + `profile_tag` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '环境唯一标识', + `release_type` tinyint(0) NULL DEFAULT 10 COMMENT '发布类型 10正常发布 20回滚发布', + `release_status` tinyint(0) NULL DEFAULT NULL COMMENT '发布状态 10待审核 20审核驳回 30待发布 35待调度 40发布中 50发布完成 60发布停止 70发布失败', + `release_serialize` tinyint(0) NULL DEFAULT 10 COMMENT '发布序列 10串行 20并行', + `exception_handler` tinyint(0) NULL DEFAULT 10 COMMENT '异常处理 10跳过所有 20跳过错误', + `bundle_path` varchar(2048) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '构建产物文件', + `transfer_path` varchar(2048) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '产物传输路径', + `transfer_mode` char(8) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT 'sftp' COMMENT '产物传输方式 sftp scp', + `rollback_release_id` bigint(0) NULL DEFAULT NULL COMMENT '回滚发布id', + `timed_release` tinyint(0) NULL DEFAULT 10 COMMENT '是否是定时发布 10普通发布 20定时发布', + `timed_release_time` datetime(4) NULL DEFAULT NULL COMMENT '定时发布时间', + `create_user_id` bigint(0) NULL DEFAULT NULL COMMENT '创建人id', + `create_user_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人名称', + `audit_user_id` bigint(0) NULL DEFAULT NULL COMMENT '审核人id', + `audit_user_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '审核人名称', + `audit_time` datetime(4) NULL DEFAULT NULL COMMENT '审核时间', + `audit_reason` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '审核备注', + `release_start_time` datetime(4) NULL DEFAULT NULL COMMENT '发布开始时间', + `release_end_time` datetime(4) NULL DEFAULT NULL COMMENT '发布结束时间', + `release_user_id` bigint(0) NULL DEFAULT NULL COMMENT '发布人id', + `release_user_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '发布人名称', + `action_config` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '发布步骤json', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '发布单' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for application_release_machine +-- ---------------------------- +DROP TABLE IF EXISTS `application_release_machine`; +CREATE TABLE `application_release_machine` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `release_id` bigint(0) NULL DEFAULT NULL COMMENT '上线单id', + `machine_id` bigint(0) NULL DEFAULT NULL COMMENT '机器id', + `machine_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '机器名称', + `machine_tag` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '机器唯一标识', + `machine_host` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '机器主机', + `run_status` tinyint(0) NULL DEFAULT 10 COMMENT '状态 10未开始 20进行中 30已完成 40执行失败 50已跳过 60已取消', + `log_path` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '日志路径', + `start_time` datetime(4) NULL DEFAULT NULL COMMENT '开始时间', + `end_time` datetime(4) NULL DEFAULT NULL COMMENT '结束时间', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `release_idx`(`release_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '发布单机器表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for application_repository +-- ---------------------------- +DROP TABLE IF EXISTS `application_repository`; +CREATE TABLE `application_repository` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `repo_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '名称', + `repo_description` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述', + `repo_type` tinyint(0) NULL DEFAULT NULL COMMENT '类型 1git', + `repo_url` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'url', + `repo_username` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名', + `repo_password` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码', + `repo_private_token` varchar(2048) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'token', + `repo_status` int(0) NULL DEFAULT NULL COMMENT '状态 10未初始化 20初始化中 30正常 40失败', + `repo_auth_type` int(0) NULL DEFAULT 10 COMMENT '认证类型 10密码 20令牌', + `repo_token_type` int(0) NULL DEFAULT NULL COMMENT '令牌类型 10github 20gitee 30gitlab', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '应用版本仓库' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for command_exec +-- ---------------------------- +DROP TABLE IF EXISTS `command_exec`; +CREATE TABLE `command_exec` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `user_id` bigint(0) NULL DEFAULT NULL COMMENT '用户id', + `user_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名称', + `exec_type` int(0) NULL DEFAULT NULL COMMENT '执行类型 10批量执行', + `machine_id` bigint(0) NULL DEFAULT NULL COMMENT '机器id', + `machine_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '机器名称', + `machine_host` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '机器主机', + `machine_tag` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '机器唯一标识', + `exec_status` int(0) NULL DEFAULT NULL COMMENT '执行状态 10未开始 20执行中 30执行完成 40执行异常 50执行终止', + `exit_code` int(0) NULL DEFAULT NULL COMMENT '执行返回码', + `exec_command` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '命令', + `description` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述', + `log_path` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '日志目录', + `start_date` datetime(4) NULL DEFAULT NULL COMMENT '执行开始时间', + `end_date` datetime(4) NULL DEFAULT NULL COMMENT '执行结束时间', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `machine_idx`(`machine_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '命令执行表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for command_template +-- ---------------------------- +DROP TABLE IF EXISTS `command_template`; +CREATE TABLE `command_template` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `template_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '模板名称', + `template_value` varchar(2048) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '模板命令', + `description` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '命令描述', + `create_user_id` bigint(0) NULL DEFAULT NULL COMMENT '创建用户id', + `create_user_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建用户名', + `update_user_id` bigint(0) NULL DEFAULT NULL COMMENT '修改用户id', + `update_user_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '修改用户名', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '命令模板表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for file_tail_list +-- ---------------------------- +DROP TABLE IF EXISTS `file_tail_list`; +CREATE TABLE `file_tail_list` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `machine_id` bigint(0) NULL DEFAULT NULL COMMENT '机器id', + `alias_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '别名', + `file_path` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件路径', + `file_charset` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT 'UTF-8' COMMENT '文件编码', + `file_offset` int(0) NULL DEFAULT NULL COMMENT '文件偏移量', + `tail_command` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'tail 命令', + `tail_mode` char(8) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT 'tracker' COMMENT '宿主机文件追踪类型 tracker/tail', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '文件tail表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for file_transfer_log +-- ---------------------------- +DROP TABLE IF EXISTS `file_transfer_log`; +CREATE TABLE `file_transfer_log` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `user_id` bigint(0) NULL DEFAULT NULL COMMENT '用户id', + `user_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名', + `file_token` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件token', + `transfer_type` tinyint(0) NULL DEFAULT NULL COMMENT '传输类型 10上传 20下载 30传输 40打包', + `machine_id` bigint(0) NULL DEFAULT NULL COMMENT '机器id', + `remote_file` varchar(1024) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '远程文件', + `local_file` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '本机文件', + `current_size` bigint(0) NULL DEFAULT NULL COMMENT '当前传输大小', + `file_size` bigint(0) NULL DEFAULT NULL COMMENT '文件大小', + `now_progress` double(5, 2 +) NULL DEFAULT NULL COMMENT '当前进度', + `transfer_status` tinyint(0) NULL DEFAULT NULL COMMENT '传输状态 10未开始 20进行中 30已暂停 40已完成 50已取消 60传输异常', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP(4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP(4) ON UPDATE CURRENT_TIMESTAMP(4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `token_unidx`(`file_token`) USING BTREE, + INDEX `user_machine_idx`(`user_id`, `machine_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'sftp传输日志表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for history_value_snapshot +-- ---------------------------- +DROP TABLE IF EXISTS `history_value_snapshot`; +CREATE TABLE `history_value_snapshot` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `value_id` bigint(0) NULL DEFAULT NULL COMMENT '值id', + `value_type` tinyint(0) NULL DEFAULT NULL COMMENT '值类型 10机器环境变量 20应用环境变量', + `before_value` varchar(2048) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '原始值', + `after_value` varchar(2048) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '新值', + `operator_type` tinyint(0) NULL DEFAULT NULL COMMENT '操作类型 1新增 2修改 3删除', + `update_user_id` bigint(0) NULL DEFAULT NULL COMMENT '修改人id', + `update_user_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '修改人用户名', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `value_idx`(`value_id`, `value_type`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '历史值快照表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for machine_alarm_config +-- ---------------------------- +DROP TABLE IF EXISTS `machine_alarm_config`; +CREATE TABLE `machine_alarm_config` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `machine_id` bigint(0) NULL DEFAULT NULL COMMENT '机器id', + `alarm_type` int(0) NULL DEFAULT NULL COMMENT '报警类型 10: cpu使用率 20: 内存使用率', + `alarm_threshold` double NULL DEFAULT NULL COMMENT '报警阈值', + `trigger_threshold` int(0) NULL DEFAULT NULL COMMENT '触发报警阈值 次', + `notify_silence` int(0) NULL DEFAULT NULL COMMENT '报警通知沉默时间 分', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `machine_idx`(`machine_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '机器报警配置' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for machine_alarm_group +-- ---------------------------- +DROP TABLE IF EXISTS `machine_alarm_group`; +CREATE TABLE `machine_alarm_group` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `machine_id` bigint(0) NULL DEFAULT NULL COMMENT '机器id', + `group_id` bigint(0) NULL DEFAULT NULL COMMENT ' 报警组id', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `config_idx`(`machine_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '机器报警通知组' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for machine_alarm_history +-- ---------------------------- +DROP TABLE IF EXISTS `machine_alarm_history`; +CREATE TABLE `machine_alarm_history` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `machine_id` bigint(0) NULL DEFAULT NULL COMMENT '机器id', + `alarm_type` int(0) NULL DEFAULT NULL COMMENT '报警类型 10: cpu使用率 20: 内存使用率', + `alarm_value` double NULL DEFAULT NULL COMMENT '报警值', + `alarm_time` datetime(4) NULL DEFAULT NULL COMMENT '报警时间', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `machine_idx`(`machine_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '机器报警历史' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for machine_env +-- ---------------------------- +DROP TABLE IF EXISTS `machine_env`; +CREATE TABLE `machine_env` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `machine_id` bigint(0) NULL DEFAULT NULL COMMENT '机器id', + `attr_key` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'key', + `attr_value` varchar(2048) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'value', + `description` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `machine_key_index`(`machine_id`, `attr_key`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '机器环境变量' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for machine_group +-- ---------------------------- +DROP TABLE IF EXISTS `machine_group`; +CREATE TABLE `machine_group` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `parent_id` bigint(0) NULL DEFAULT NULL COMMENT '父id', + `group_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '组名称', + `sort` int(0) NULL DEFAULT 0 COMMENT '排序', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `parent_idx`(`parent_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '机器分组' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for machine_group_rel +-- ---------------------------- +DROP TABLE IF EXISTS `machine_group_rel`; +CREATE TABLE `machine_group_rel` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `group_id` bigint(0) NULL DEFAULT NULL COMMENT '组id', + `machine_id` bigint(0) NULL DEFAULT NULL COMMENT '机器id', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `group_idx`(`group_id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '机器分组关联表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for machine_info +-- ---------------------------- +DROP TABLE IF EXISTS `machine_info`; +CREATE TABLE `machine_info` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `proxy_id` bigint(0) NULL DEFAULT NULL COMMENT '代理id', + `key_id` bigint(0) NULL DEFAULT NULL COMMENT '密钥id', + `machine_host` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '主机ip', + `ssh_port` int(0) NULL DEFAULT 22 COMMENT 'ssh端口', + `machine_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '机器名称', + `machine_tag` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '机器唯一标识', + `description` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '机器描述', + `username` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '机器账号', + `password` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '机器密码', + `auth_type` tinyint(1) NULL DEFAULT NULL COMMENT '机器认证方式 1: 密码认证 2: 独立密钥', + `machine_status` tinyint(1) NULL DEFAULT 1 COMMENT '机器状态 1有效 2无效', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `host_idx`(`machine_host`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '机器信息表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Table structure for machine_monitor +-- ---------------------------- +DROP TABLE IF EXISTS `machine_monitor`; +CREATE TABLE `machine_monitor` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `machine_id` bigint(0) NULL DEFAULT NULL COMMENT '机器id', + `monitor_status` tinyint(0) NULL DEFAULT 1 COMMENT '监控状态 1未启动 2启动中 3运行中', + `monitor_url` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求 api url', + `access_token` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '请求 api accessToken', + `agent_version` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '插件版本', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '机器监控配置表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for machine_proxy +-- ---------------------------- +DROP TABLE IF EXISTS `machine_proxy`; +CREATE TABLE `machine_proxy` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `proxy_host` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '代理主机', + `proxy_port` int(0) NULL DEFAULT NULL COMMENT '代理端口', + `proxy_username` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '代理用户名', + `proxy_password` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '代理密码', + `proxy_type` int(0) NULL DEFAULT NULL COMMENT '代理类型 1http代理 2socket4代理 3socket5代理', + `description` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '机器代理' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for machine_secret_key +-- ---------------------------- +DROP TABLE IF EXISTS `machine_secret_key`; +CREATE TABLE `machine_secret_key` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `key_name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密钥名称', + `secret_key_path` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密钥文件本地路径', + `password` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密钥密码', + `description` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '机器ssh登录密钥' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for machine_terminal +-- ---------------------------- +DROP TABLE IF EXISTS `machine_terminal`; +CREATE TABLE `machine_terminal` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `machine_id` bigint(0) NOT NULL COMMENT '机器id', + `terminal_type` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT 'xterm' COMMENT '终端类型', + `background_color` char(7) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '背景色', + `font_color` char(7) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '字体颜色', + `font_size` int(0) NULL DEFAULT 14 COMMENT '字体大小', + `font_family` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '字体名称', + `enable_web_link` tinyint(0) NULL DEFAULT 2 COMMENT '是否开启url link 1开启 2关闭', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `machine_idx`(`machine_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '机器终端配置表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for machine_terminal_log +-- ---------------------------- +DROP TABLE IF EXISTS `machine_terminal_log`; +CREATE TABLE `machine_terminal_log` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `user_id` bigint(0) NULL DEFAULT NULL COMMENT '用户id', + `username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'username', + `machine_id` bigint(0) NULL DEFAULT NULL COMMENT '机器id', + `machine_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '机器名称', + `machine_host` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '机器host', + `machine_tag` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '机器唯一标识', + `access_token` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'token', + `connected_time` datetime(4) NULL DEFAULT NULL COMMENT '建立连接时间', + `disconnected_time` datetime(4) NULL DEFAULT NULL COMMENT '断开连接时间', + `close_code` int(0) NULL DEFAULT NULL COMMENT 'close code', + `screen_path` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '录屏文件路径', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '机器终端操作日志' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for scheduler_task +-- ---------------------------- +DROP TABLE IF EXISTS `scheduler_task`; +CREATE TABLE `scheduler_task` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `task_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '任务名称', + `expression` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'cron表达式', + `task_command` varchar(2048) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '执行命令', + `description` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '任务描述', + `enable_status` tinyint(0) NULL DEFAULT 2 COMMENT '启用状态 1启用 2停用', + `lately_status` tinyint(0) NULL DEFAULT NULL COMMENT '最近状态 10待调度 20调度中 30调度成功 40调度失败 50已终止', + `serialize_type` tinyint(0) NULL DEFAULT NULL COMMENT '调度序列 10串行 20并行', + `exception_handler` tinyint(0) NULL DEFAULT NULL COMMENT '异常处理 10跳过所有 20跳过错误', + `lately_schedule_time` datetime(0) NULL DEFAULT NULL COMMENT '上次调度时间', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '调度任务' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for scheduler_task_machine +-- ---------------------------- +DROP TABLE IF EXISTS `scheduler_task_machine`; +CREATE TABLE `scheduler_task_machine` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `task_id` bigint(0) NULL DEFAULT NULL COMMENT '任务id', + `machine_id` bigint(0) NULL DEFAULT NULL COMMENT '调度机器id', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `task_idx`(`task_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '调度任务机器' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for scheduler_task_machine_record +-- ---------------------------- +DROP TABLE IF EXISTS `scheduler_task_machine_record`; +CREATE TABLE `scheduler_task_machine_record` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `task_id` bigint(0) NULL DEFAULT NULL COMMENT '任务id', + `record_id` bigint(0) NULL DEFAULT NULL COMMENT '明细id', + `task_machine_id` bigint(0) NULL DEFAULT NULL COMMENT '任务机器id', + `machine_id` bigint(0) NULL DEFAULT NULL COMMENT '执行机器id', + `machine_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '机器名称', + `machine_host` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '机器主机', + `machine_tag` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '机器唯一标识', + `exec_command` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '执行命令', + `exec_status` int(0) NULL DEFAULT NULL COMMENT '执行状态 10待调度 20调度中 30调度成功 40调度失败 50跳过 60已停止', + `exit_code` int(0) NULL DEFAULT NULL COMMENT '退出码', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `log_path` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '日志路径', + `start_time` datetime(4) NULL DEFAULT NULL COMMENT '开始时间', + `end_time` datetime(4) NULL DEFAULT NULL COMMENT '结束时间', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `task_record_idx`(`task_id`, `record_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '调度任务执行明细机器详情' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for scheduler_task_record +-- ---------------------------- +DROP TABLE IF EXISTS `scheduler_task_record`; +CREATE TABLE `scheduler_task_record` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `task_id` bigint(0) NULL DEFAULT NULL COMMENT '任务id', + `task_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '任务名称', + `task_status` int(0) NULL DEFAULT NULL COMMENT '任务状态 10待调度 20调度中 30调度成功 40调度失败 50已停止', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `start_time` datetime(4) NULL DEFAULT NULL COMMENT '开始时间', + `end_time` datetime(4) NULL DEFAULT NULL COMMENT '结束时间', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `task_id_idx`(`task_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '调度任务执行日志' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for system_env +-- ---------------------------- +DROP TABLE IF EXISTS `system_env`; +CREATE TABLE `system_env` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `attr_key` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'key', + `attr_value` varchar(2048) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'value', + `system_env` tinyint(0) NULL DEFAULT 2 COMMENT '是否为系统变量 1是 2否', + `description` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `key_idx`(`attr_key`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统环境变量' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for user_event_log +-- ---------------------------- +DROP TABLE IF EXISTS `user_event_log`; +CREATE TABLE `user_event_log` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `user_id` bigint(0) NULL DEFAULT NULL COMMENT '用户id', + `username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名', + `event_classify` int(0) NULL DEFAULT NULL COMMENT '事件分类', + `event_type` int(0) NULL DEFAULT NULL COMMENT '事件类型', + `log_info` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '日志信息', + `params_json` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '日志参数', + `exec_result` tinyint(0) NULL DEFAULT NULL COMMENT '是否执行成功 1成功 2失败', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `user_event`(`user_id`, `event_classify`, `event_type`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户事件日志' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for user_info +-- ---------------------------- +DROP TABLE IF EXISTS `user_info`; +CREATE TABLE `user_info` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名', + `nickname` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '昵称', + `password` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码', + `salt` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '盐值', + `role_type` int(0) NULL DEFAULT NULL COMMENT '角色类型 10超级管理员 20开发 30运维', + `user_status` tinyint(0) NULL DEFAULT 1 COMMENT '用户状态 1启用 2禁用', + `lock_status` tinyint(0) NULL DEFAULT 1 COMMENT '锁定状态 1正常 2锁定', + `failed_login_count` int(0) NULL DEFAULT 0 COMMENT '登录失败次数', + `avatar_pic` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '头像路径', + `contact_phone` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '联系手机', + `contact_email` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '联系邮箱', + `last_login_time` datetime(4) NULL DEFAULT NULL COMMENT '最后登录时间', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `username_idx`(`username`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for web_side_message +-- ---------------------------- +DROP TABLE IF EXISTS `web_side_message`; +CREATE TABLE `web_side_message` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `message_classify` tinyint(0) NULL DEFAULT NULL COMMENT '消息分类', + `message_type` int(0) NULL DEFAULT NULL COMMENT '消息类型', + `read_status` tinyint(0) NULL DEFAULT 1 COMMENT '是否已读 1未读 2已读', + `to_user_id` bigint(0) NULL DEFAULT NULL COMMENT '收信人id', + `to_user_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '收信人名称', + `send_message` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '消息', + `rel_id` bigint(0) NULL DEFAULT NULL COMMENT '消息关联id', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `to_user_id_idx`(`to_user_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统站内信' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for webhook_config +-- ---------------------------- +DROP TABLE IF EXISTS `webhook_config`; +CREATE TABLE `webhook_config` +( + `id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id', + `webhook_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '名称', + `webhook_url` varchar(2048) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'url', + `webhook_type` int(0) NULL DEFAULT NULL COMMENT '类型 10: 钉钉机器人', + `webhook_config` varchar(2048) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '配置项 json', + `deleted` tinyint(0) NULL DEFAULT 1 COMMENT '是否删除 1未删除 2已删除', + `create_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) COMMENT '创建时间', + `update_time` datetime(4) NULL DEFAULT CURRENT_TIMESTAMP (4) ON UPDATE CURRENT_TIMESTAMP (4) COMMENT '更新时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'webhook 配置' ROW_FORMAT = Dynamic; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/sql/init-2-data.sql b/sql/init-2-data.sql new file mode 100644 index 0000000..aa36945 --- /dev/null +++ b/sql/init-2-data.sql @@ -0,0 +1,20 @@ +USE `youdu-ops`; + +# 默认应用环境 +INSERT INTO application_profile VALUES (1, '开发环境', 'dev', '开发环境', 1, 1, NOW(), NOW()); + +# 默认管理员用户 wxy/wxy@youdu.xin +INSERT INTO user_info VALUES (1, 'wxy', '管理员', 'b9cd085c3056717c3ec7ba7810fa81b6', 'DyBAFjVx3L2cIQbsrzl', 10, 1, 1, 0, '/avatar/1.png', '19999999999', 'wxy@youdu.xin', NULL, 1, NOW(), NOW()); + +# 默认机器分组 +INSERT INTO machine_group VALUES (1, -1, '默认分组', 1, 1, NOW(), NOW()); + +# 默认命令模板 +INSERT INTO command_template VALUES (1, 'echo 测试', 'echo \'123\'\necho \'1234\'', 'echo 模板', 1, 'wxy', 1, 'wxy', 1, NOW(), NOW()); +INSERT INTO command_template VALUES (2, 'for echo 0.05', 'for i in `seq 1 250`\ndo\n sleep 0.05\n echo $i\ndone', NULL, 1, 'wxy', 1, 'wxy', 1, NOW(), NOW()); +INSERT INTO command_template VALUES (3, 'for echo 1 c 10', 'for i in `seq 1 10`\ndo\n sleep 1\n echo $i\ndone', 'sleep 1 -c 10', 1, 'wxy', 1, 'wxy', 1, NOW(), NOW()); +INSERT INTO command_template VALUES (4, 'read value await', 'echo read\nread -p \"Enter value > \" value\necho \"value: $value\"', 'read await', 1, 'wxy', 1, 'wxy', 1, NOW(), NOW()); +INSERT INTO command_template VALUES (5, 'kill', 'PROGRESS=\nps -ef | grep $PROGRESS | grep -v grep | awk \'{print $2}\' | xargs kill -9 || echo $?', '通过进程名称执行kill命令', 1, 'wxy', 1, 'wxy', 1, NOW(), NOW()); +INSERT INTO command_template VALUES (6, '获取 pid', 'PROGRESS=\r\nPID=$(ps -ef | grep $PROGRESS | grep -v grep | awk \'{print $2}\')\r\nif [ ! -z $PID ]; then\r\n echo \'pid: \' $PID\r\nfi', '获取进程 pid', 1, 'wxy', 1, 'wxy', 1, NOW(), NOW()); +INSERT INTO command_template VALUES (7, '删除文件', 'DEL_PATH=\r\nif [ -f \"$DEL_PATH\" ]; then\r\n rm -f $DEL_PATH\r\n echo \'删除文件\' $DEL_PATH\r\nfi', NULL, 1, 'wxy', 1, 'wxy', 1, NOW(), NOW()); +INSERT INTO command_template VALUES (8, '删除文件夹', 'DEL_PATH=\r\nif [ -d \"$DEL_PATH\" ]; then\r\n rm -rf $DEL_PATH\r\n echo \'删除文件夹\' $DEL_PATH\r\nfi', NULL, 1, 'wxy', 1, 'wxy', 1, NOW(), NOW());