🔨 修改配置.
This commit is contained in:
3
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@@ -1,7 +1,7 @@
|
|||||||
name: 错误报告
|
name: 错误报告
|
||||||
description: File a bug report.
|
description: File a bug report.
|
||||||
title: "[错误报告]: "
|
title: "[错误报告]: "
|
||||||
labels: [""]
|
labels: [ "" ]
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
@@ -42,7 +42,6 @@ body:
|
|||||||
- 其他
|
- 其他
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: what-happened
|
id: what-happened
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
@@ -1,7 +1,7 @@
|
|||||||
name: 功能改进
|
name: 功能改进
|
||||||
description: 提出新功能建议 (请提交到需求收集帖)
|
description: 提出新功能建议 (请提交到需求收集帖)
|
||||||
title: "[功能建议]: "
|
title: "[功能建议]: "
|
||||||
labels: [""]
|
labels: [ "" ]
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
2
.github/workflows/docker-publish.yaml
vendored
2
.github/workflows/docker-publish.yaml
vendored
@@ -41,7 +41,7 @@ jobs:
|
|||||||
pnpm install
|
pnpm install
|
||||||
pnpm build
|
pnpm build
|
||||||
|
|
||||||
- name: 📦️ Download instant-agent
|
- name: 📦️ Download instance-agent
|
||||||
working-directory: ./docker/service
|
working-directory: ./docker/service
|
||||||
run: wget https://github.com/lijiahangmax/orion-visor-agent/releases/latest/download/instance-agent-release.tar.gz -O instance-agent-release.tar.gz
|
run: wget https://github.com/lijiahangmax/orion-visor-agent/releases/latest/download/instance-agent-release.tar.gz -O instance-agent-release.tar.gz
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
* **文件管理**:支持远程主机 SFTP 大文件的批量上传、下载和在线编辑等操作。
|
* **文件管理**:支持远程主机 SFTP 大文件的批量上传、下载和在线编辑等操作。
|
||||||
* **批量操作**:支持批量执行主机命令、多主机文件分发等功能。
|
* **批量操作**:支持批量执行主机命令、多主机文件分发等功能。
|
||||||
* **计划任务**:支持配置 cron 表达式,定时执行主机命令。
|
* **计划任务**:支持配置 cron 表达式,定时执行主机命令。
|
||||||
* **系统监控**:(开发中)。
|
* **系统监控**:支持对主机 CPU、内存、磁盘、网络等系统指标的监控和告警。
|
||||||
* **安全可靠**:动态配置权限,记录用户操作日志,提供简单的审计功能。
|
* **安全可靠**:动态配置权限,记录用户操作日志,提供简单的审计功能。
|
||||||
|
|
||||||
## 演示环境
|
## 演示环境
|
||||||
@@ -88,6 +88,7 @@ docker compose up -d
|
|||||||
* SpringBoot 2.7+
|
* SpringBoot 2.7+
|
||||||
* Mysql 8.0+
|
* Mysql 8.0+
|
||||||
* Redis 6.0+
|
* Redis 6.0+
|
||||||
|
* InfluxDB 2.7+
|
||||||
* Vue 3.5+
|
* Vue 3.5+
|
||||||
* Arco Design 2.56+
|
* Arco Design 2.56+
|
||||||
|
|
||||||
@@ -155,6 +156,10 @@ QQ群: 755242157
|
|||||||
|
|
||||||
本项目遵循 [Apache-2.0](https://github.com/dromara/orion-visor/blob/main/LICENSE) 开源许可证。
|
本项目遵循 [Apache-2.0](https://github.com/dromara/orion-visor/blob/main/LICENSE) 开源许可证。
|
||||||
|
|
||||||
|
## 贡献者
|
||||||
|
|
||||||
|
[](https://github.com/dromara/orion-visor, "Contributors")
|
||||||
|
|
||||||
## Gitee 最有价值的开源项目 GVP
|
## Gitee 最有价值的开源项目 GVP
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -24,7 +24,7 @@ RUN chmod +x /app/entrypoint.sh
|
|||||||
# 复制 jar 包
|
# 复制 jar 包
|
||||||
COPY ./service/orion-visor-launch.jar /app/app.jar
|
COPY ./service/orion-visor-launch.jar /app/app.jar
|
||||||
# 复制探针包
|
# 复制探针包
|
||||||
ADD ./service/instant-agent-release.tar.gz /app/instant-agent-release
|
ADD ./service/instance-agent-release.tar.gz /app/instance-agent-release
|
||||||
|
|
||||||
# 启动检测
|
# 启动检测
|
||||||
HEALTHCHECK --interval=15s --timeout=5s --retries=5 --start-period=10s \
|
HEALTHCHECK --interval=15s --timeout=5s --retries=5 --start-period=10s \
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
AGENT_RELEASE_DIR="/root/orion/orion-visor/instant-agent-release"
|
AGENT_RELEASE_DIR="/root/orion/orion-visor/instance-agent-release"
|
||||||
DEFAULT_AGENT_DIR="/app/instant-agent-release"
|
DEFAULT_AGENT_DIR="/app/instance-agent-release"
|
||||||
|
|
||||||
# 确保父目录存在
|
# 确保父目录存在
|
||||||
mkdir -p "$(dirname "$AGENT_RELEASE_DIR")"
|
mkdir -p "$(dirname "$AGENT_RELEASE_DIR")"
|
||||||
|
|||||||
@@ -37,15 +37,17 @@ public interface FileConst {
|
|||||||
|
|
||||||
String SCRIPT = "script";
|
String SCRIPT = "script";
|
||||||
|
|
||||||
String INSTANT_AGENT_PATH = "instant-agent";
|
String INSTANCE_AGENT_PATH = "instance-agent";
|
||||||
|
|
||||||
String INSTANT_AGENT_NAME = "instant_agent";
|
String INSTANCE_AGENT_NAME = "instance_agent";
|
||||||
|
|
||||||
String INSTANT_AGENT_RELEASE = "instant-agent-release";
|
String INSTANCE_AGENT_FILE_FORMAT = "instance_agent_{}_{}{}";
|
||||||
|
|
||||||
String INSTANT_AGENT_RELEASE_TEMP = "instant-agent-release-temp";
|
String INSTANCE_AGENT_RELEASE = "instance-agent-release";
|
||||||
|
|
||||||
String INSTANT_AGENT_RELEASE_TAR_GZ = "instant-agent-release.tar.gz";
|
String INSTANCE_AGENT_RELEASE_TEMP = "instance-agent-release-temp";
|
||||||
|
|
||||||
|
String INSTANCE_AGENT_RELEASE_TAR_GZ = "instance-agent-release.tar.gz";
|
||||||
|
|
||||||
String VERSION = ".version";
|
String VERSION = ".version";
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,9 @@ public class HostAgentApiImpl implements HostAgentApi {
|
|||||||
.filter(Strings::isNotBlank)
|
.filter(Strings::isNotBlank)
|
||||||
.map(Long::parseLong)
|
.map(Long::parseLong)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
if (logIdList.isEmpty()) {
|
||||||
|
return Lists.empty();
|
||||||
|
}
|
||||||
// 查询数据库
|
// 查询数据库
|
||||||
return hostAgentLogDAO.selectBatchIds(logIdList)
|
return hostAgentLogDAO.selectBatchIds(logIdList)
|
||||||
.stream()
|
.stream()
|
||||||
|
|||||||
@@ -72,12 +72,21 @@ public class HostVO implements Serializable {
|
|||||||
@Schema(description = "主机地址")
|
@Schema(description = "主机地址")
|
||||||
private String address;
|
private String address;
|
||||||
|
|
||||||
@Schema(description = "主机端口")
|
|
||||||
private Integer port;
|
|
||||||
|
|
||||||
@Schema(description = "主机状态")
|
@Schema(description = "主机状态")
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
|
@Schema(description = "agentKey")
|
||||||
|
private String agentKey;
|
||||||
|
|
||||||
|
@Schema(description = "探针版本")
|
||||||
|
private String agentVersion;
|
||||||
|
|
||||||
|
@Schema(description = "探针安装状态")
|
||||||
|
private Integer agentInstallStatus;
|
||||||
|
|
||||||
|
@Schema(description = "探针在线状态")
|
||||||
|
private Integer agentOnlineStatus;
|
||||||
|
|
||||||
@Schema(description = "描述")
|
@Schema(description = "描述")
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ public abstract class AbstractAgentInstaller implements AgentInstaller {
|
|||||||
this.params = params;
|
this.params = params;
|
||||||
this.logId = params.getLogId();
|
this.logId = params.getLogId();
|
||||||
this.startScriptName = Const.START + HostOsTypeEnum.of(params.getOsType()).getScriptSuffix();
|
this.startScriptName = Const.START + HostOsTypeEnum.of(params.getOsType()).getScriptSuffix();
|
||||||
this.uploadAgentName = FileConst.INSTANT_AGENT_NAME + HostOsTypeEnum.of(params.getOsType()).getBinarySuffix();
|
this.uploadAgentName = FileConst.INSTANCE_AGENT_NAME + HostOsTypeEnum.of(params.getOsType()).getBinarySuffix();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -154,7 +154,7 @@ public abstract class AbstractAgentInstaller implements AgentInstaller {
|
|||||||
protected String getAgentHomePath() {
|
protected String getAgentHomePath() {
|
||||||
return PathUtils.buildAppPath(HostOsTypeEnum.WINDOWS.name().equals(params.getOsType()),
|
return PathUtils.buildAppPath(HostOsTypeEnum.WINDOWS.name().equals(params.getOsType()),
|
||||||
sshConfig.getUsername(),
|
sshConfig.getUsername(),
|
||||||
FileConst.INSTANT_AGENT_PATH) + Const.SLASH;
|
FileConst.INSTANCE_AGENT_PATH) + Const.SLASH;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -75,8 +75,6 @@ import java.util.stream.Collectors;
|
|||||||
@Service
|
@Service
|
||||||
public class HostAgentServiceImpl implements HostAgentService {
|
public class HostAgentServiceImpl implements HostAgentService {
|
||||||
|
|
||||||
private static final String AGENT_FILE_FORMAT = "agent_{}_{}{}";
|
|
||||||
|
|
||||||
private String localVersion;
|
private String localVersion;
|
||||||
|
|
||||||
@Value("${orion.api.expose.token}")
|
@Value("${orion.api.expose.token}")
|
||||||
@@ -95,7 +93,7 @@ public class HostAgentServiceImpl implements HostAgentService {
|
|||||||
public void readLocalAgentVersion() {
|
public void readLocalAgentVersion() {
|
||||||
log.info("HostAgentService-readLocalAgentVersion start");
|
log.info("HostAgentService-readLocalAgentVersion start");
|
||||||
// 文件路径
|
// 文件路径
|
||||||
String path = PathUtils.getOrionPath(FileConst.INSTANT_AGENT_RELEASE + Const.SLASH + FileConst.VERSION);
|
String path = PathUtils.getOrionPath(FileConst.INSTANCE_AGENT_RELEASE + Const.SLASH + FileConst.VERSION);
|
||||||
log.info("HostAgentService-readLocalAgentVersion path: {}", path);
|
log.info("HostAgentService-readLocalAgentVersion path: {}", path);
|
||||||
try {
|
try {
|
||||||
if (!Files1.isFile(path)) {
|
if (!Files1.isFile(path)) {
|
||||||
@@ -191,9 +189,9 @@ public class HostAgentServiceImpl implements HostAgentService {
|
|||||||
Valid.notBlank(fileName, ErrorMessage.FILE_EXTENSION_TYPE);
|
Valid.notBlank(fileName, ErrorMessage.FILE_EXTENSION_TYPE);
|
||||||
Valid.isTrue(fileName.endsWith(Const.SUFFIX_TAR_GZ), ErrorMessage.FILE_EXTENSION_TYPE);
|
Valid.isTrue(fileName.endsWith(Const.SUFFIX_TAR_GZ), ErrorMessage.FILE_EXTENSION_TYPE);
|
||||||
// 保存文件
|
// 保存文件
|
||||||
String releaseDir = PathUtils.getOrionPath(FileConst.INSTANT_AGENT_RELEASE);
|
String releaseDir = PathUtils.getOrionPath(FileConst.INSTANCE_AGENT_RELEASE);
|
||||||
String releaseTempDir = PathUtils.getOrionPath(FileConst.INSTANT_AGENT_RELEASE_TEMP);
|
String releaseTempDir = PathUtils.getOrionPath(FileConst.INSTANCE_AGENT_RELEASE_TEMP);
|
||||||
File releaseTempFile = new File(releaseTempDir + Const.SLASH + FileConst.INSTANT_AGENT_RELEASE_TAR_GZ);
|
File releaseTempFile = new File(releaseTempDir + Const.SLASH + FileConst.INSTANCE_AGENT_RELEASE_TAR_GZ);
|
||||||
log.info("HostAgentService.installAgent start releaseTempDir: {}, releaseTempFile: {}", releaseTempDir, releaseTempFile.getAbsolutePath());
|
log.info("HostAgentService.installAgent start releaseTempDir: {}, releaseTempFile: {}", releaseTempDir, releaseTempFile.getAbsolutePath());
|
||||||
try {
|
try {
|
||||||
// 创建目录
|
// 创建目录
|
||||||
@@ -269,7 +267,7 @@ public class HostAgentServiceImpl implements HostAgentService {
|
|||||||
Valid.isTrue(supportSsh, ErrorMessage.PLEASE_CHECK_HOST_SSH, host.getName());
|
Valid.isTrue(supportSsh, ErrorMessage.PLEASE_CHECK_HOST_SSH, host.getName());
|
||||||
// 文件名称
|
// 文件名称
|
||||||
HostOsTypeEnum os = HostOsTypeEnum.of(host.getOsType());
|
HostOsTypeEnum os = HostOsTypeEnum.of(host.getOsType());
|
||||||
String agentFileName = Strings.format(AGENT_FILE_FORMAT,
|
String agentFileName = Strings.format(FileConst.INSTANCE_AGENT_FILE_FORMAT,
|
||||||
os.name().toLowerCase(),
|
os.name().toLowerCase(),
|
||||||
host.getArchType().toLowerCase(),
|
host.getArchType().toLowerCase(),
|
||||||
os.getBinarySuffix());
|
os.getBinarySuffix());
|
||||||
@@ -278,9 +276,9 @@ public class HostAgentServiceImpl implements HostAgentService {
|
|||||||
.hostId(host.getId())
|
.hostId(host.getId())
|
||||||
.osType(host.getOsType())
|
.osType(host.getOsType())
|
||||||
.agentKey(host.getAgentKey())
|
.agentKey(host.getAgentKey())
|
||||||
.agentFilePath(PathUtils.getOrionPath(FileConst.INSTANT_AGENT_RELEASE + Const.SLASH + agentFileName))
|
.agentFilePath(PathUtils.getOrionPath(FileConst.INSTANCE_AGENT_RELEASE + Const.SLASH + agentFileName))
|
||||||
.configFilePath(PathUtils.getOrionPath(FileConst.INSTANT_AGENT_RELEASE + Const.SLASH + FileConst.CONFIG_YAML))
|
.configFilePath(PathUtils.getOrionPath(FileConst.INSTANCE_AGENT_RELEASE + Const.SLASH + FileConst.CONFIG_YAML))
|
||||||
.startScriptPath(PathUtils.getOrionPath(FileConst.INSTANT_AGENT_RELEASE + Const.SLASH + Const.START + os.getScriptSuffix()))
|
.startScriptPath(PathUtils.getOrionPath(FileConst.INSTANCE_AGENT_RELEASE + Const.SLASH + Const.START + os.getScriptSuffix()))
|
||||||
.build();
|
.build();
|
||||||
taskParams.add(params);
|
taskParams.add(params);
|
||||||
// 添加待检查文件
|
// 添加待检查文件
|
||||||
|
|||||||
@@ -287,14 +287,14 @@
|
|||||||
@click="setInstallSuccess(record.installLog)">
|
@click="setInstallSuccess(record.installLog)">
|
||||||
<span class="more-doption normal">安装成功</span>
|
<span class="more-doption normal">安装成功</span>
|
||||||
</a-doption>
|
</a-doption>
|
||||||
<!-- 报警开关 -->
|
<!-- 告警开关 -->
|
||||||
<a-doption v-if="record.id"
|
<a-doption v-if="record.id"
|
||||||
v-permission="['monitor:monitor-host:update', 'monitor:monitor-host:update-switch']"
|
v-permission="['monitor:monitor-host:update', 'monitor:monitor-host:update-switch']"
|
||||||
type="text"
|
type="text"
|
||||||
size="mini"
|
size="mini"
|
||||||
@click="toggleAlarmSwitch(record)">
|
@click="toggleAlarmSwitch(record)">
|
||||||
<span class="more-doption normal">
|
<span class="more-doption normal">
|
||||||
{{ toggleDictValue(AlarmSwitchKey, record.alarmSwitch, 'label') + '报警' }}
|
{{ toggleDictValue(AlarmSwitchKey, record.alarmSwitch, 'label') + '告警' }}
|
||||||
</span>
|
</span>
|
||||||
</a-doption>
|
</a-doption>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -317,14 +317,14 @@
|
|||||||
@click="setInstallSuccess(record.installLog)">
|
@click="setInstallSuccess(record.installLog)">
|
||||||
<span class="more-doption normal">安装成功</span>
|
<span class="more-doption normal">安装成功</span>
|
||||||
</a-doption>
|
</a-doption>
|
||||||
<!-- 报警开关 -->
|
<!-- 告警开关 -->
|
||||||
<a-doption v-if="record.id"
|
<a-doption v-if="record.id"
|
||||||
v-permission="['monitor:monitor-host:update', 'monitor:monitor-host:update-switch']"
|
v-permission="['monitor:monitor-host:update', 'monitor:monitor-host:update-switch']"
|
||||||
type="text"
|
type="text"
|
||||||
size="mini"
|
size="mini"
|
||||||
@click="toggleAlarmSwitch(record)">
|
@click="toggleAlarmSwitch(record)">
|
||||||
<span class="more-doption normal">
|
<span class="more-doption normal">
|
||||||
{{ toggleDictValue(AlarmSwitchKey, record.alarmSwitch, 'label') + '报警' }}
|
{{ toggleDictValue(AlarmSwitchKey, record.alarmSwitch, 'label') + '告警' }}
|
||||||
</span>
|
</span>
|
||||||
</a-doption>
|
</a-doption>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -107,13 +107,13 @@ export default function useMonitorHostList(options: UseMonitorHostListOptions) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 更新报警开关
|
// 更新告警开关
|
||||||
const toggleAlarmSwitch = async (record: MonitorHostQueryResponse) => {
|
const toggleAlarmSwitch = async (record: MonitorHostQueryResponse) => {
|
||||||
const dict = toggleDict(AlarmSwitchKey, record.alarmSwitch);
|
const dict = toggleDict(AlarmSwitchKey, record.alarmSwitch);
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: `${dict.label}确认`,
|
title: `${dict.label}确认`,
|
||||||
titleAlign: 'start',
|
titleAlign: 'start',
|
||||||
content: `确定要${dict.label}报警功能吗?`,
|
content: `确定要${dict.label}告警功能吗?`,
|
||||||
okText: '确定',
|
okText: '确定',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -664,6 +664,6 @@ INSERT INTO `system_menu` VALUES (287, 283, '删除监控指标', 'monitor:monit
|
|||||||
INSERT INTO `system_menu` VALUES (288, 282, '主机监控', NULL, 2, 10, 1, 1, 1, 0, 'IconComputer', NULL, 'monitorHost', '2025-08-23 17:02:02', '2025-08-24 22:47:03', 'admin', 'admin', 0);
|
INSERT INTO `system_menu` VALUES (288, 282, '主机监控', NULL, 2, 10, 1, 1, 1, 0, 'IconComputer', NULL, 'monitorHost', '2025-08-23 17:02:02', '2025-08-24 22:47:03', 'admin', 'admin', 0);
|
||||||
INSERT INTO `system_menu` VALUES (289, 288, '查询监控主机', 'monitor:monitor-host:query', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2025-08-23 17:02:02', '2025-08-23 17:02:02', 'admin', 'admin', 0);
|
INSERT INTO `system_menu` VALUES (289, 288, '查询监控主机', 'monitor:monitor-host:query', 3, 10, 1, 1, 1, 0, NULL, NULL, NULL, '2025-08-23 17:02:02', '2025-08-23 17:02:02', 'admin', 'admin', 0);
|
||||||
INSERT INTO `system_menu` VALUES (290, 288, '修改监控主机', 'monitor:monitor-host:update', 3, 20, 1, 1, 1, 0, NULL, NULL, NULL, '2025-08-23 17:02:02', '2025-08-23 17:03:56', 'admin', 'admin', 0);
|
INSERT INTO `system_menu` VALUES (290, 288, '修改监控主机', 'monitor:monitor-host:update', 3, 20, 1, 1, 1, 0, NULL, NULL, NULL, '2025-08-23 17:02:02', '2025-08-23 17:03:56', 'admin', 'admin', 0);
|
||||||
INSERT INTO `system_menu` VALUES (292, 288, '修改报警开关', 'monitor:monitor-host:update-switch', 3, 30, 1, 1, 1, 0, NULL, NULL, NULL, '2025-08-23 17:02:02', '2025-08-23 17:04:31', 'admin', 'admin', 0);
|
INSERT INTO `system_menu` VALUES (292, 288, '修改告警开关', 'monitor:monitor-host:update-switch', 3, 30, 1, 1, 1, 0, NULL, NULL, NULL, '2025-08-23 17:02:02', '2025-08-23 17:04:31', 'admin', 'admin', 0);
|
||||||
INSERT INTO `system_menu` VALUES (293, 64, '安装探针', 'asset:host:install-agent', 3, 110, 1, 1, 1, 0, NULL, NULL, NULL, '2025-08-31 20:18:14', '2025-08-31 20:18:14', 'admin', 'admin', 0);
|
INSERT INTO `system_menu` VALUES (293, 64, '安装探针', 'asset:host:install-agent', 3, 110, 1, 1, 1, 0, NULL, NULL, NULL, '2025-08-31 20:18:14', '2025-08-31 20:18:14', 'admin', 'admin', 0);
|
||||||
INSERT INTO `system_menu` VALUES (294, 282, '主机监控详情', NULL, 2, 20, 0, 1, 1, 0, 'IconComputer', '', 'monitorDetail', '2025-09-03 23:03:20', '2025-09-03 23:03:55', 'admin', 'admin', 0);
|
INSERT INTO `system_menu` VALUES (294, 282, '主机监控详情', NULL, 2, 20, 0, 1, 1, 0, 'IconComputer', '', 'monitorDetail', '2025-09-03 23:03:20', '2025-09-03 23:03:55', 'admin', 'admin', 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user