feat: 默认脚本编辑弹窗+后端读写default脚本接口
This commit is contained in:
@@ -65,6 +65,34 @@ public class ScriptController {
|
|||||||
return ApiResponse.success(Map.of("version", v));
|
return ApiResponse.success(Map.of("version", v));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 获取站点类型默认脚本(管理员) */
|
||||||
|
@GetMapping("/api/v1/admin/scripts/default/{siteType}")
|
||||||
|
public ApiResponse<Map<String, String>> getDefaultScript(@PathVariable String siteType, HttpServletRequest request) {
|
||||||
|
checkAdmin(request);
|
||||||
|
String script = GroovyScriptEngine.loadDefaultByType(siteType);
|
||||||
|
return ApiResponse.success(Map.of("siteType", siteType, "script", script != null ? script : ""));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 保存站点类型默认脚本(管理员) */
|
||||||
|
@PutMapping("/api/v1/admin/scripts/default/{siteType}")
|
||||||
|
public ApiResponse<Void> saveDefaultScript(
|
||||||
|
@PathVariable String siteType,
|
||||||
|
@RequestBody Map<String, String> payload,
|
||||||
|
HttpServletRequest request) {
|
||||||
|
checkAdmin(request);
|
||||||
|
String script = payload.get("script");
|
||||||
|
if (script == null || script.isBlank()) return ApiResponse.error(400, "script is required");
|
||||||
|
GroovyScriptEngine.saveDefaultScript(siteType, script);
|
||||||
|
return ApiResponse.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkAdmin(HttpServletRequest request) {
|
||||||
|
var level = request.getAttribute("trustLevel");
|
||||||
|
if (level == null || !"ADMIN".equals(level.toString())) {
|
||||||
|
throw new IllegalArgumentException("Admin permission required");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 上传 Groovy 脚本(PSK 签名认证) */
|
/** 上传 Groovy 脚本(PSK 签名认证) */
|
||||||
@PutMapping("/api/v1/scripts/{siteId}")
|
@PutMapping("/api/v1/scripts/{siteId}")
|
||||||
public ApiResponse<?> uploadScript(
|
public ApiResponse<?> uploadScript(
|
||||||
|
|||||||
@@ -43,6 +43,23 @@ public class GroovyScriptEngine {
|
|||||||
return null; // placeholder, 由 AdapterController 实现具体逻辑
|
return null; // placeholder, 由 AdapterController 实现具体逻辑
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存站点类型默认脚本到 classpath
|
||||||
|
*/
|
||||||
|
public static void saveDefaultScript(String siteType, String script) {
|
||||||
|
if (siteType == null || script == null) return;
|
||||||
|
String key = siteType.toLowerCase();
|
||||||
|
DEFAULT_SCRIPTS.put(key, script);
|
||||||
|
try {
|
||||||
|
var path = java.nio.file.Paths.get(
|
||||||
|
GroovyScriptEngine.class.getClassLoader().getResource("scripts/default-" + key + ".groovy").toURI());
|
||||||
|
java.nio.file.Files.writeString(path, script, java.nio.charset.StandardCharsets.UTF_8);
|
||||||
|
log.info("Default script saved: default-{}.groovy", key);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to save default script: {}", key, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从 classpath 加载默认脚本文件
|
* 从 classpath 加载默认脚本文件
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ const routes = [
|
|||||||
path: 'access-results/check/:siteId',
|
path: 'access-results/check/:siteId',
|
||||||
name: 'AccessResultCheck',
|
name: 'AccessResultCheck',
|
||||||
component: () => import('../views/AccessResultCheck.vue'),
|
component: () => import('../views/AccessResultCheck.vue'),
|
||||||
meta: { title: '适配校验详情' }
|
meta: { title: '脚本编辑' }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'site-pages',
|
path: 'site-pages',
|
||||||
|
|||||||
@@ -1,7 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h2>脚本管理</h2>
|
<h2>脚本管理</h2>
|
||||||
<el-card style="margin-top: 20px">
|
|
||||||
|
<!-- 默认模板 -->
|
||||||
|
<el-card style="margin-top:12px">
|
||||||
|
<template #header><span style="font-weight:500">站点类型默认模板</span></template>
|
||||||
|
<div style="display:flex;gap:12px;flex-wrap:wrap">
|
||||||
|
<div v-for="dt in defaultTypes" :key="dt" style="border:1px solid #ebeef5;border-radius:4px;padding:10px 14px;min-width:180px">
|
||||||
|
<div style="font-weight:500;margin-bottom:6px"><el-tag size="small">{{ dt }}</el-tag></div>
|
||||||
|
<div style="display:flex;gap:4px">
|
||||||
|
<el-button size="small" @click="editDefaultScript(dt)">编辑</el-button>
|
||||||
|
<el-button size="small" @click="viewDefaultScript(dt)">查看</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-dialog v-model="defaultEditorVisible" :title="'默认脚本: '+editingDefaultType" width="800px" destroy-on-close>
|
||||||
|
<div style="height:500px;border-radius:4px;overflow:hidden">
|
||||||
|
<CodeEditor v-model="defaultScriptContent" language="groovy" />
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="defaultEditorVisible=false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="saveDefaultScript" :loading="defaultSaving">保存</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog v-model="defaultViewVisible" :title="'默认脚本: '+viewingDefaultType" width="800px">
|
||||||
|
<pre style="background:#1e1e1e;color:#d4d4d4;padding:12px;border-radius:4px;max-height:500px;overflow:auto;font-size:12px;white-space:pre-wrap">{{ viewingDefaultScript }}</pre>
|
||||||
|
</el-dialog>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card style="margin-top: 12px">
|
||||||
<div style="margin-bottom: 12px;display:flex;gap:8px">
|
<div style="margin-bottom: 12px;display:flex;gap:8px">
|
||||||
<el-button @click="fetchAll" :icon="Refresh">刷新</el-button>
|
<el-button @click="fetchAll" :icon="Refresh">刷新</el-button>
|
||||||
<el-button @click="exportScripts" :loading="exporting">导出脚本</el-button>
|
<el-button @click="exportScripts" :loading="exporting">导出脚本</el-button>
|
||||||
@@ -72,6 +101,39 @@ import { Refresh } from '@element-plus/icons-vue'
|
|||||||
|
|
||||||
const loading = ref(false), exporting = ref(false), importing = ref(false)
|
const loading = ref(false), exporting = ref(false), importing = ref(false)
|
||||||
const sites = ref([])
|
const sites = ref([])
|
||||||
|
const defaultTypes = ['nexusphp','mtorrent','gazelle','unit3d']
|
||||||
|
const defaultEditorVisible = ref(false), defaultViewVisible = ref(false)
|
||||||
|
const editingDefaultType = ref(''), viewingDefaultType = ref(''), viewingDefaultScript = ref('')
|
||||||
|
const defaultScriptContent = ref(''), defaultSaving = ref(false)
|
||||||
|
|
||||||
|
const editDefaultScript = async (type) => {
|
||||||
|
editingDefaultType.value = type
|
||||||
|
defaultScriptContent.value = ''
|
||||||
|
defaultEditorVisible.value = true
|
||||||
|
try {
|
||||||
|
const r = await api.raw.get('/admin/scripts/default/' + type)
|
||||||
|
defaultScriptContent.value = (r.data||r||{}).script || (r||{}).script || ''
|
||||||
|
} catch { ElMessage.error('加载默认脚本失败') }
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewDefaultScript = async (type) => {
|
||||||
|
viewingDefaultType.value = type
|
||||||
|
viewingDefaultScript.value = '加载中...'
|
||||||
|
defaultViewVisible.value = true
|
||||||
|
try {
|
||||||
|
const r = await api.raw.get('/admin/scripts/default/' + type)
|
||||||
|
viewingDefaultScript.value = (r.data||r||{}).script || (r||{}).script || '(空)'
|
||||||
|
} catch { viewingDefaultScript.value = '加载失败' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveDefaultScript = async () => {
|
||||||
|
defaultSaving.value = true
|
||||||
|
try {
|
||||||
|
await api.raw.put('/admin/scripts/default/' + editingDefaultType.value, { script: defaultScriptContent.value })
|
||||||
|
ElMessage.success('已保存'); defaultEditorVisible.value = false
|
||||||
|
} catch { ElMessage.error('保存失败') }
|
||||||
|
finally { defaultSaving.value = false }
|
||||||
|
}
|
||||||
|
|
||||||
const exportScripts = async () => {
|
const exportScripts = async () => {
|
||||||
exporting.value = true
|
exporting.value = true
|
||||||
|
|||||||
Reference in New Issue
Block a user