beta1
This commit is contained in:
@@ -14,7 +14,7 @@
|
|||||||
"remoteRules": [
|
"remoteRules": [
|
||||||
"https://example.com/rules.txt"
|
"https://example.com/rules.txt"
|
||||||
],
|
],
|
||||||
"updateInterval": 3600,
|
"updateInterval": 60,
|
||||||
"hostsFile": "data/hosts.txt"
|
"hostsFile": "data/hosts.txt"
|
||||||
},
|
},
|
||||||
"log": {
|
"log": {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ type ShieldConfig struct {
|
|||||||
CustomBlockIP string `json:"customBlockIP"` // 自定义屏蔽IP,当BlockMethod为"customIP"时使用
|
CustomBlockIP string `json:"customBlockIP"` // 自定义屏蔽IP,当BlockMethod为"customIP"时使用
|
||||||
StatsFile string `json:"statsFile"` // 计数数据持久化文件
|
StatsFile string `json:"statsFile"` // 计数数据持久化文件
|
||||||
StatsSaveInterval int `json:"statsSaveInterval"` // 计数数据保存间隔(秒)
|
StatsSaveInterval int `json:"statsSaveInterval"` // 计数数据保存间隔(秒)
|
||||||
|
RemoteRulesCacheDir string `json:"remoteRulesCacheDir"` // 远程规则缓存目录
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogConfig 日志配置
|
// LogConfig 日志配置
|
||||||
@@ -94,6 +95,9 @@ func LoadConfig(path string) (*Config, error) {
|
|||||||
if config.Shield.StatsSaveInterval == 0 {
|
if config.Shield.StatsSaveInterval == 0 {
|
||||||
config.Shield.StatsSaveInterval = 300 // 默认5分钟保存一次
|
config.Shield.StatsSaveInterval = 300 // 默认5分钟保存一次
|
||||||
}
|
}
|
||||||
|
if config.Shield.RemoteRulesCacheDir == "" {
|
||||||
|
config.Shield.RemoteRulesCacheDir = "./data/remote_rules" // 默认远程规则缓存目录
|
||||||
|
}
|
||||||
if config.Log.Level == "" {
|
if config.Log.Level == "" {
|
||||||
config.Log.Level = "info"
|
config.Log.Level = "info"
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"blockedDomainsCount": {},
|
||||||
|
"resolvedDomainsCount": {},
|
||||||
|
"lastSaved": "2025-11-23T19:30:41.791142096+08:00"
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"stats": {
|
"stats": {
|
||||||
"Queries": 33,
|
"Queries": 34,
|
||||||
"Blocked": 24,
|
"Blocked": 24,
|
||||||
"Allowed": 19,
|
"Allowed": 20,
|
||||||
"Errors": 2,
|
"Errors": 2,
|
||||||
"LastQuery": "2025-11-23T19:06:10.694259822+08:00"
|
"LastQuery": "2025-11-23T19:28:57.615407202+08:00"
|
||||||
},
|
},
|
||||||
"blockedDomains": {
|
"blockedDomains": {
|
||||||
"ad.qq.com": {
|
"ad.qq.com": {
|
||||||
@@ -29,6 +29,11 @@
|
|||||||
"Count": 8,
|
"Count": 8,
|
||||||
"LastSeen": "2025-11-23T19:05:54.356216418+08:00"
|
"LastSeen": "2025-11-23T19:05:54.356216418+08:00"
|
||||||
},
|
},
|
||||||
|
"apd-pcdnwxlogin.teg.tencent-cloud.net": {
|
||||||
|
"Domain": "apd-pcdnwxlogin.teg.tencent-cloud.net",
|
||||||
|
"Count": 1,
|
||||||
|
"LastSeen": "2025-11-23T19:28:57.621471084+08:00"
|
||||||
|
},
|
||||||
"apd-pcdnwxstat.teg.tencent-cloud.net": {
|
"apd-pcdnwxstat.teg.tencent-cloud.net": {
|
||||||
"Domain": "apd-pcdnwxstat.teg.tencent-cloud.net",
|
"Domain": "apd-pcdnwxstat.teg.tencent-cloud.net",
|
||||||
"Count": 1,
|
"Count": 1,
|
||||||
@@ -38,5 +43,5 @@
|
|||||||
"hourlyStats": {
|
"hourlyStats": {
|
||||||
"2025-11-23-19": 12
|
"2025-11-23-19": 12
|
||||||
},
|
},
|
||||||
"lastSaved": "2025-11-23T19:06:21.194988376+08:00"
|
"lastSaved": "2025-11-23T19:30:24.828634638+08:00"
|
||||||
}
|
}
|
||||||
BIN
dns-server
BIN
dns-server
Binary file not shown.
3246
dns-server.log
3246
dns-server.log
File diff suppressed because it is too large
Load Diff
@@ -373,11 +373,13 @@ func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
case http.MethodGet:
|
||||||
// 返回当前配置(只返回必要的部分)
|
// 返回当前配置(包括远程规则相关配置)
|
||||||
config := map[string]interface{}{
|
config := map[string]interface{}{
|
||||||
"shield": map[string]string{
|
"shield": map[string]interface{}{
|
||||||
"blockMethod": s.globalConfig.Shield.BlockMethod,
|
"blockMethod": s.globalConfig.Shield.BlockMethod,
|
||||||
"customBlockIP": s.globalConfig.Shield.CustomBlockIP,
|
"customBlockIP": s.globalConfig.Shield.CustomBlockIP,
|
||||||
|
"remoteRules": s.globalConfig.Shield.RemoteRules,
|
||||||
|
"updateInterval": s.globalConfig.Shield.UpdateInterval,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
json.NewEncoder(w).Encode(config)
|
json.NewEncoder(w).Encode(config)
|
||||||
@@ -388,6 +390,8 @@ func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
Shield struct {
|
Shield struct {
|
||||||
BlockMethod string `json:"blockMethod"`
|
BlockMethod string `json:"blockMethod"`
|
||||||
CustomBlockIP string `json:"customBlockIP"`
|
CustomBlockIP string `json:"customBlockIP"`
|
||||||
|
RemoteRules []string `json:"remoteRules"`
|
||||||
|
UpdateInterval int `json:"updateInterval"`
|
||||||
} `json:"shield"`
|
} `json:"shield"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,6 +435,23 @@ func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
s.globalConfig.Shield.CustomBlockIP = req.Shield.CustomBlockIP
|
s.globalConfig.Shield.CustomBlockIP = req.Shield.CustomBlockIP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新远程规则列表
|
||||||
|
if req.Shield.RemoteRules != nil {
|
||||||
|
s.globalConfig.Shield.RemoteRules = req.Shield.RemoteRules
|
||||||
|
// 重新加载规则
|
||||||
|
if err := s.shieldManager.LoadRules(); err != nil {
|
||||||
|
logger.Error("重新加载规则失败", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新更新间隔
|
||||||
|
if req.Shield.UpdateInterval > 0 {
|
||||||
|
s.globalConfig.Shield.UpdateInterval = req.Shield.UpdateInterval
|
||||||
|
// 重新启动自动更新
|
||||||
|
s.shieldManager.StopAutoUpdate()
|
||||||
|
s.shieldManager.StartAutoUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
// 返回成功响应
|
// 返回成功响应
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
"success": true,
|
"success": true,
|
||||||
|
|||||||
@@ -141,15 +141,65 @@ func (m *ShieldManager) loadRemoteRules() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getCacheFilePath 根据URL生成缓存文件路径
|
||||||
|
func (m *ShieldManager) getCacheFilePath(url string) string {
|
||||||
|
// 使用URL的哈希值作为文件名,避免文件名冲突
|
||||||
|
hash := fmt.Sprintf("%x", url)
|
||||||
|
// 简单处理,移除特殊字符,确保文件名合法
|
||||||
|
hash = strings.ReplaceAll(hash, "/", "_")
|
||||||
|
hash = strings.ReplaceAll(hash, "\\", "_")
|
||||||
|
return filepath.Join(m.config.RemoteRulesCacheDir, hash+".rules")
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldUpdateCache 检查缓存是否需要更新
|
||||||
|
func (m *ShieldManager) shouldUpdateCache(cacheFile string) bool {
|
||||||
|
// 检查文件是否存在
|
||||||
|
if _, err := os.Stat(cacheFile); os.IsNotExist(err) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查文件修改时间
|
||||||
|
fileInfo, err := os.Stat(cacheFile)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果缓存文件超过更新间隔时间,则需要更新
|
||||||
|
return time.Since(fileInfo.ModTime()) > time.Duration(m.config.UpdateInterval)*time.Second
|
||||||
|
}
|
||||||
|
|
||||||
// fetchRemoteRules 从远程URL获取规则
|
// fetchRemoteRules 从远程URL获取规则
|
||||||
func (m *ShieldManager) fetchRemoteRules(url string) error {
|
func (m *ShieldManager) fetchRemoteRules(url string) error {
|
||||||
|
// 获取缓存文件路径
|
||||||
|
cacheFile := m.getCacheFilePath(url)
|
||||||
|
|
||||||
|
// 尝试从缓存加载
|
||||||
|
hasLoadedFromCache := false
|
||||||
|
if !m.shouldUpdateCache(cacheFile) {
|
||||||
|
if err := m.loadCachedRules(cacheFile); err == nil {
|
||||||
|
logger.Info("从缓存加载远程规则", "url", url)
|
||||||
|
hasLoadedFromCache = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从远程获取规则
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// 如果从远程获取失败,但已经从缓存加载成功,则返回nil
|
||||||
|
if hasLoadedFromCache {
|
||||||
|
logger.Warn("远程规则更新失败,使用缓存版本", "url", url, "error", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
// 如果状态码不正确,但已经从缓存加载成功,则返回nil
|
||||||
|
if hasLoadedFromCache {
|
||||||
|
logger.Warn("远程规则更新失败,使用缓存版本", "url", url, "statusCode", resp.StatusCode)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return fmt.Errorf("远程服务器返回错误状态码: %d", resp.StatusCode)
|
return fmt.Errorf("远程服务器返回错误状态码: %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,6 +208,38 @@ func (m *ShieldManager) fetchRemoteRules(url string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 保存规则到缓存
|
||||||
|
if err := m.saveRemoteRulesToCache(cacheFile, body); err != nil {
|
||||||
|
logger.Warn("保存远程规则缓存失败", "url", url, "error", err)
|
||||||
|
// 继续处理,不返回错误
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析并加载规则
|
||||||
|
lines := strings.Split(string(body), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m.parseRule(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadCachedRules 从缓存文件加载规则
|
||||||
|
func (m *ShieldManager) loadCachedRules(filePath string) error {
|
||||||
|
file, err := os.Open(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
lines := strings.Split(string(body), "\n")
|
lines := strings.Split(string(body), "\n")
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
line = strings.TrimSpace(line)
|
line = strings.TrimSpace(line)
|
||||||
@@ -170,6 +252,17 @@ func (m *ShieldManager) fetchRemoteRules(url string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// saveRemoteRulesToCache 保存远程规则到缓存文件
|
||||||
|
func (m *ShieldManager) saveRemoteRulesToCache(filePath string, data []byte) error {
|
||||||
|
// 确保缓存目录存在
|
||||||
|
if err := os.MkdirAll(m.config.RemoteRulesCacheDir, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入文件
|
||||||
|
return ioutil.WriteFile(filePath, data, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
// loadHosts 加载hosts文件
|
// loadHosts 加载hosts文件
|
||||||
func (m *ShieldManager) loadHosts() error {
|
func (m *ShieldManager) loadHosts() error {
|
||||||
if m.config.HostsFile == "" {
|
if m.config.HostsFile == "" {
|
||||||
|
|||||||
@@ -312,16 +312,29 @@
|
|||||||
background-color: #c0392b;
|
background-color: #c0392b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline {
|
.btn-outline { background-color: transparent; color: var(--primary-color); border: 1px solid var(--primary-color); }
|
||||||
background-color: transparent;
|
|
||||||
color: var(--primary-color);
|
|
||||||
border: 1px solid var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-outline:hover {
|
.btn-outline:hover { background-color: var(--primary-color); color: white; }
|
||||||
background-color: var(--primary-color);
|
|
||||||
color: white;
|
.btn-secondary { background-color: var(--secondary-color); color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; }
|
||||||
}
|
|
||||||
|
.btn-secondary:hover { background-color: #1a252f; transform: translateY(-1px); box-shadow: var(--shadow-md); }
|
||||||
|
|
||||||
|
.btn-sm { padding: 4px 8px; font-size: 0.875rem; }
|
||||||
|
|
||||||
|
.form-label { display: block; margin-bottom: 0.5rem; font-weight: 500; }
|
||||||
|
|
||||||
|
.form-text { display: block; margin-top: 0.25rem; font-size: 0.875rem; color: var(--gray-600); }
|
||||||
|
|
||||||
|
.mb-3 { margin-bottom: 1rem; }
|
||||||
|
|
||||||
|
.mt-3 { margin-top: 1rem; }
|
||||||
|
|
||||||
|
.rule-items { display: flex; flex-direction: column; gap: 0.5rem; }
|
||||||
|
|
||||||
|
.rule-item { display: flex; align-items: center; justify-content: space-between; padding: 0.75rem; background-color: var(--gray-50); border: 1px solid var(--gray-200); border-radius: 4px; }
|
||||||
|
|
||||||
|
.rule-text { flex: 1; word-break: break-all; margin-right: 0.5rem; }
|
||||||
|
|
||||||
/* 悬浮通知样式 */
|
/* 悬浮通知样式 */
|
||||||
.notification {
|
.notification {
|
||||||
@@ -800,6 +813,39 @@
|
|||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title"><i class="fas fa-globe"></i> 远程规则管理</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="update-interval" class="form-label">更新间隔 (秒)</label>
|
||||||
|
<input type="number" id="update-interval" min="60" max="86400" placeholder="3600">
|
||||||
|
<small class="form-text">远程规则自动更新的时间间隔,建议至少60秒</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="remote-rule-url" placeholder="输入远程规则URL">
|
||||||
|
<button id="add-remote-rule" class="btn-secondary">
|
||||||
|
<i class="fas fa-plus"></i> 添加规则
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="list-container" id="remote-rules-list">
|
||||||
|
<div class="empty-state">
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
<p>加载中...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="save-remote-settings" class="btn-primary mt-3">
|
||||||
|
<i class="fas fa-save"></i> 保存远程设置
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title"><i class="fas fa-plus-circle"></i> 添加屏蔽规则</h3>
|
<h3 class="card-title"><i class="fas fa-plus-circle"></i> 添加屏蔽规则</h3>
|
||||||
@@ -1719,6 +1765,13 @@ function loadRules() {
|
|||||||
|
|
||||||
// 加载当前屏蔽设置
|
// 加载当前屏蔽设置
|
||||||
loadBlockSettings();
|
loadBlockSettings();
|
||||||
|
|
||||||
|
// 远程规则相关事件监听
|
||||||
|
document.getElementById('add-remote-rule').addEventListener('click', addRemoteRule);
|
||||||
|
document.getElementById('save-remote-settings').addEventListener('click', saveRemoteSettings);
|
||||||
|
|
||||||
|
// 加载远程规则设置
|
||||||
|
loadRemoteSettings();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 加载当前屏蔽设置
|
// 加载当前屏蔽设置
|
||||||
@@ -1782,6 +1835,169 @@ function loadRules() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载远程规则设置
|
||||||
|
function loadRemoteSettings() {
|
||||||
|
fetch('/api/config', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.shield) {
|
||||||
|
// 设置更新间隔
|
||||||
|
document.getElementById('update-interval').value = data.shield.updateInterval || 3600;
|
||||||
|
|
||||||
|
// 加载远程规则列表
|
||||||
|
renderRemoteRulesList(data.shield.remoteRules || []);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('加载远程规则设置失败:', error);
|
||||||
|
showNotification('danger', '加载远程规则设置失败');
|
||||||
|
renderRemoteRulesList([]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染远程规则列表
|
||||||
|
function renderRemoteRulesList(rules) {
|
||||||
|
const listContainer = document.getElementById('remote-rules-list');
|
||||||
|
|
||||||
|
if (rules.length === 0) {
|
||||||
|
listContainer.innerHTML = `
|
||||||
|
<div class="empty-state">
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
<p>暂无远程规则</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = '<div class="list-container">';
|
||||||
|
rules.forEach((rule, index) => {
|
||||||
|
html += `
|
||||||
|
<div class="list-item">
|
||||||
|
<div class="list-content">
|
||||||
|
<div class="list-title">远程规则 ${index + 1}</div>
|
||||||
|
<div class="list-description">${rule}</div>
|
||||||
|
</div>
|
||||||
|
<div class="list-actions">
|
||||||
|
<span class="badge badge-primary">远程</span>
|
||||||
|
<button class="btn-danger btn-sm delete-rule" data-index="${index}">
|
||||||
|
<i class="fas fa-trash-alt"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
listContainer.innerHTML = html;
|
||||||
|
|
||||||
|
// 添加删除按钮事件监听
|
||||||
|
document.querySelectorAll('.delete-rule').forEach(btn => {
|
||||||
|
btn.addEventListener('click', function() {
|
||||||
|
const index = parseInt(this.getAttribute('data-index'));
|
||||||
|
deleteRemoteRule(index);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加远程规则
|
||||||
|
function addRemoteRule() {
|
||||||
|
const urlInput = document.getElementById('remote-rule-url');
|
||||||
|
const url = urlInput.value.trim();
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
showNotification('warning', '请输入有效的URL');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单的URL验证
|
||||||
|
try {
|
||||||
|
new URL(url);
|
||||||
|
} catch (e) {
|
||||||
|
showNotification('warning', '请输入有效的URL格式');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前列表中的规则
|
||||||
|
const ruleItems = document.querySelectorAll('.list-item');
|
||||||
|
const rules = Array.from(ruleItems).map(item =>
|
||||||
|
item.querySelector('.list-description').textContent
|
||||||
|
);
|
||||||
|
|
||||||
|
// 检查是否已存在
|
||||||
|
if (rules.includes(url)) {
|
||||||
|
showNotification('warning', '该规则已存在');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加新规则
|
||||||
|
rules.push(url);
|
||||||
|
renderRemoteRulesList(rules);
|
||||||
|
urlInput.value = '';
|
||||||
|
|
||||||
|
showNotification('success', '规则已添加');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除远程规则
|
||||||
|
function deleteRemoteRule(index) {
|
||||||
|
const ruleItems = document.querySelectorAll('.list-item');
|
||||||
|
const rules = Array.from(ruleItems).map(item =>
|
||||||
|
item.querySelector('.list-description').textContent
|
||||||
|
);
|
||||||
|
|
||||||
|
// 移除指定索引的规则
|
||||||
|
rules.splice(index, 1);
|
||||||
|
renderRemoteRulesList(rules);
|
||||||
|
|
||||||
|
showNotification('success', '规则已删除');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存远程规则设置
|
||||||
|
function saveRemoteSettings() {
|
||||||
|
const updateInterval = parseInt(document.getElementById('update-interval').value);
|
||||||
|
|
||||||
|
// 验证更新间隔
|
||||||
|
if (isNaN(updateInterval) || updateInterval < 60) {
|
||||||
|
showNotification('warning', '更新间隔必须大于等于60秒');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前列表中的规则
|
||||||
|
const ruleItems = document.querySelectorAll('.list-item');
|
||||||
|
const remoteRules = Array.from(ruleItems).map(item =>
|
||||||
|
item.querySelector('.list-description').textContent
|
||||||
|
);
|
||||||
|
|
||||||
|
fetch('/api/config', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
shield: {
|
||||||
|
remoteRules: remoteRules,
|
||||||
|
updateInterval: updateInterval
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
showNotification('success', '远程规则设置已保存');
|
||||||
|
} else {
|
||||||
|
showNotification('danger', '保存失败: ' + (data.error || '未知错误'));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('保存远程规则设置失败:', error);
|
||||||
|
showNotification('danger', '保存远程规则设置失败: ' + error.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 显示悬浮通知
|
// 显示悬浮通知
|
||||||
function showNotification(type, message) {
|
function showNotification(type, message) {
|
||||||
// 创建通知元素
|
// 创建通知元素
|
||||||
|
|||||||
Reference in New Issue
Block a user