update url list format
This commit is contained in:
+6
-5
@@ -83,7 +83,7 @@ func createDefaultURLList() {
|
|||||||
_, err := os.Stat(urlFileList)
|
_, err := os.Stat(urlFileList)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
log.Printf("%s: Using Default url list: %s", time.Now(), urlFileList)
|
log.Printf("%s: Using Default url list: %s", time.Now(), urlFileList)
|
||||||
writeLines(urlFileList, defaultURLs)
|
writeURLListCSV(urlFileList, defaultURLs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,13 +100,14 @@ func fetchAndMergeSources() {
|
|||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
builder.Write(blockData)
|
builder.Write(blockData)
|
||||||
|
|
||||||
urls, _ := readLines(urlFileList)
|
items, _ := readURLListCSV(urlFileList)
|
||||||
for _, url := range urls {
|
for _, item := range items {
|
||||||
if strings.HasPrefix(url, "#") {
|
if !item.Enabled {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(item.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Printf("%s: Failed to fetch %s: %v", time.Now(), item.Name, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
body, _ := io.ReadAll(resp.Body)
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ type Config struct {
|
|||||||
MergedListTmp string `json:"merged_list_tmp"`
|
MergedListTmp string `json:"merged_list_tmp"`
|
||||||
|
|
||||||
// Default blocklist URLs
|
// Default blocklist URLs
|
||||||
DefaultURLs []string `json:"default_urls"`
|
DefaultURLs []URLListItem `json:"default_urls"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -64,25 +64,25 @@ func getDefaultConfig() Config {
|
|||||||
ProcessName: "coredns",
|
ProcessName: "coredns",
|
||||||
TmpFile: "/sdcard1/combined-blocklist.txt",
|
TmpFile: "/sdcard1/combined-blocklist.txt",
|
||||||
LastUpdateFile: "/sdcard1/last_update.txt",
|
LastUpdateFile: "/sdcard1/last_update.txt",
|
||||||
URLFileList: "/sdcard1/urllist.txt",
|
URLFileList: "/sdcard1/urllist.csv",
|
||||||
BlocklistFile: "/run/utm/domain_list/domainlist_0.list",
|
BlocklistFile: "/run/utm/domain_list/domainlist_0.list",
|
||||||
RemoveFile: "/run/utm/domain_list/domainlist_1.list",
|
RemoveFile: "/run/utm/domain_list/domainlist_1.list",
|
||||||
MergedListTmp: "/tmp/mergedlist.txt",
|
MergedListTmp: "/tmp/mergedlist.txt",
|
||||||
DefaultURLs: []string{
|
DefaultURLs: []URLListItem{
|
||||||
"https://adguardteam.github.io/HostlistsRegistry/assets/filter_27.txt",
|
{Name: "AdGuard DNS filter", Enabled: true, Group: "Default", URL: "https://adguardteam.github.io/HostlistsRegistry/assets/filter_27.txt"},
|
||||||
"https://adguardteam.github.io/HostlistsRegistry/assets/filter_49.txt",
|
{Name: "AdGuard Russian filter", Enabled: true, Group: "Default", URL: "https://adguardteam.github.io/HostlistsRegistry/assets/filter_49.txt"},
|
||||||
"https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt",
|
{Name: "AdGuard Base filter", Enabled: true, Group: "Default", URL: "https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt"},
|
||||||
"https://adguardteam.github.io/HostlistsRegistry/assets/filter_42.txt",
|
{Name: "AdGuard Tracking Protection", Enabled: true, Group: "Default", URL: "https://adguardteam.github.io/HostlistsRegistry/assets/filter_42.txt"},
|
||||||
"https://adguardteam.github.io/HostlistsRegistry/assets/filter_18.txt",
|
{Name: "AdGuard Social Media filter", Enabled: true, Group: "Default", URL: "https://adguardteam.github.io/HostlistsRegistry/assets/filter_18.txt"},
|
||||||
"https://adguardteam.github.io/HostlistsRegistry/assets/filter_23.txt",
|
{Name: "AdGuard Annoyance filter", Enabled: true, Group: "Default", URL: "https://adguardteam.github.io/HostlistsRegistry/assets/filter_23.txt"},
|
||||||
"https://adguardteam.github.io/HostlistsRegistry/assets/filter_11.txt",
|
{Name: "AdGuard Mobile Ads filter", Enabled: true, Group: "Default", URL: "https://adguardteam.github.io/HostlistsRegistry/assets/filter_11.txt"},
|
||||||
"https://adguardteam.github.io/HostlistsRegistry/assets/filter_9.txt",
|
{Name: "AdGuard Search Ads filter", Enabled: true, Group: "Default", URL: "https://adguardteam.github.io/HostlistsRegistry/assets/filter_9.txt"},
|
||||||
"https://adguardteam.github.io/HostlistsRegistry/assets/filter_50.txt",
|
{Name: "AdGuard Adult filter", Enabled: true, Group: "Default", URL: "https://adguardteam.github.io/HostlistsRegistry/assets/filter_50.txt"},
|
||||||
"https://raw.githubusercontent.com/DandelionSprout/adfilt/master/Alternate%20versions%20Anti-Malware%20List/AntiMalwareHosts.txt",
|
{Name: "AntiMalware Hosts", Enabled: true, Group: "Default", URL: "https://raw.githubusercontent.com/DandelionSprout/adfilt/master/Alternate%20versions%20Anti-Malware%20List/AntiMalwareHosts.txt"},
|
||||||
"https://osint.digitalside.it/Threat-Intel/lists/latestdomains.txt",
|
{Name: "DigitalSide Threat Intel", Enabled: true, Group: "Default", URL: "https://osint.digitalside.it/Threat-Intel/lists/latestdomains.txt"},
|
||||||
"https://v.firebog.net/hosts/Prigent-Crypto.txt",
|
{Name: "Firebog Crypto", Enabled: true, Group: "Default", URL: "https://v.firebog.net/hosts/Prigent-Crypto.txt"},
|
||||||
"https://phishing.army/download/phishing_army_blocklist_extended.txt",
|
{Name: "Phishing Army", Enabled: true, Group: "Default", URL: "https://phishing.army/download/phishing_army_blocklist_extended.txt"},
|
||||||
"https://v.firebog.net/hosts/static/w3kbl.txt",
|
{Name: "W3kbl", Enabled: true, Group: "Default", URL: "https://v.firebog.net/hosts/static/w3kbl.txt"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+80
-43
@@ -26,52 +26,87 @@ func dashboardHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func urlListsHandler(w http.ResponseWriter, r *http.Request) {
|
func urlListsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method == "POST" {
|
|
||||||
action := r.FormValue("action")
|
|
||||||
url := sanitizeInput(r.FormValue("url"))
|
|
||||||
indexStr := r.FormValue("index")
|
|
||||||
|
|
||||||
configMu.RLock()
|
configMu.RLock()
|
||||||
urlFileList := config.URLFileList
|
urlFileList := config.URLFileList
|
||||||
defaultURLs := config.DefaultURLs
|
defaultURLs := config.DefaultURLs
|
||||||
configMu.RUnlock()
|
configMu.RUnlock()
|
||||||
|
|
||||||
urls, err := readLines(urlFileList)
|
if r.Method == "POST" {
|
||||||
|
action := r.FormValue("action")
|
||||||
|
|
||||||
|
items, err := readURLListCSV(urlFileList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
renderTemplate(w, r, "urllists.html", struct {
|
renderTemplate(w, r, "urllists.html", struct {
|
||||||
URLs []string
|
Items []URLListItem
|
||||||
Notification string
|
Notification string
|
||||||
NotificationType string
|
NotificationType string
|
||||||
}{
|
}{
|
||||||
URLs: urls,
|
Items: items,
|
||||||
Notification: "Error reading URL list",
|
Notification: "Error reading URL list",
|
||||||
NotificationType: "error",
|
NotificationType: "error",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
errorMsg := ""
|
var errorMsg string
|
||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case "add":
|
case "add":
|
||||||
if !isValidURL(url) {
|
name := sanitizeInput(r.FormValue("name"))
|
||||||
|
urlStr := sanitizeInput(r.FormValue("url"))
|
||||||
|
group := sanitizeInput(r.FormValue("group"))
|
||||||
|
note := sanitizeInput(r.FormValue("note"))
|
||||||
|
|
||||||
|
if name == "" {
|
||||||
|
errorMsg = "Name is required"
|
||||||
|
} else if !isValidURL(urlStr) {
|
||||||
errorMsg = "Invalid URL format. Must start with http:// or https://"
|
errorMsg = "Invalid URL format. Must start with http:// or https://"
|
||||||
} else {
|
} else {
|
||||||
urls = append(urls, url)
|
items = append(items, URLListItem{
|
||||||
|
Name: name,
|
||||||
|
Enabled: true,
|
||||||
|
Group: group,
|
||||||
|
URL: urlStr,
|
||||||
|
Note: note,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
case "remove":
|
case "remove":
|
||||||
|
indexStr := r.FormValue("index")
|
||||||
index, _ := strconv.Atoi(indexStr)
|
index, _ := strconv.Atoi(indexStr)
|
||||||
if index >= 0 && index < len(urls) {
|
if index >= 0 && index < len(items) {
|
||||||
urls = append(urls[:index], urls[index+1:]...)
|
items = append(items[:index], items[index+1:]...)
|
||||||
} else {
|
} else {
|
||||||
errorMsg = "Invalid index"
|
errorMsg = "Invalid index"
|
||||||
}
|
}
|
||||||
|
|
||||||
case "toggle":
|
case "toggle":
|
||||||
|
indexStr := r.FormValue("index")
|
||||||
index, _ := strconv.Atoi(indexStr)
|
index, _ := strconv.Atoi(indexStr)
|
||||||
if index >= 0 && index < len(urls) {
|
if index >= 0 && index < len(items) {
|
||||||
if strings.HasPrefix(urls[index], "#") {
|
items[index].Enabled = !items[index].Enabled
|
||||||
urls[index] = strings.TrimPrefix(urls[index], "#")
|
|
||||||
} else {
|
} else {
|
||||||
urls[index] = "#" + urls[index]
|
errorMsg = "Invalid index"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "edit":
|
||||||
|
indexStr := r.FormValue("index")
|
||||||
|
name := sanitizeInput(r.FormValue("name"))
|
||||||
|
urlStr := sanitizeInput(r.FormValue("url"))
|
||||||
|
group := sanitizeInput(r.FormValue("group"))
|
||||||
|
note := sanitizeInput(r.FormValue("note"))
|
||||||
|
|
||||||
|
index, _ := strconv.Atoi(indexStr)
|
||||||
|
if index >= 0 && index < len(items) {
|
||||||
|
if name == "" {
|
||||||
|
errorMsg = "Name is required"
|
||||||
|
} else if !isValidURL(urlStr) {
|
||||||
|
errorMsg = "Invalid URL format. Must start with http:// or https://"
|
||||||
|
} else {
|
||||||
|
items[index].Name = name
|
||||||
|
items[index].URL = urlStr
|
||||||
|
items[index].Group = group
|
||||||
|
items[index].Note = note
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errorMsg = "Invalid index"
|
errorMsg = "Invalid index"
|
||||||
@@ -80,54 +115,56 @@ func urlListsHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if errorMsg != "" {
|
if errorMsg != "" {
|
||||||
renderTemplate(w, r, "urllists.html", struct {
|
renderTemplate(w, r, "urllists.html", struct {
|
||||||
URLs []string
|
Items []URLListItem
|
||||||
Notification string
|
Notification string
|
||||||
NotificationType string
|
NotificationType string
|
||||||
}{
|
}{
|
||||||
URLs: urls,
|
Items: items,
|
||||||
Notification: errorMsg,
|
Notification: errorMsg,
|
||||||
NotificationType: "error",
|
NotificationType: "error",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
writeLines(urlFileList, urls)
|
err = writeURLListCSV(urlFileList, items)
|
||||||
|
if err != nil {
|
||||||
// Show success notification
|
|
||||||
if len(urls) == 0 {
|
|
||||||
writeLines(urlFileList, defaultURLs)
|
|
||||||
urls = defaultURLs
|
|
||||||
}
|
|
||||||
|
|
||||||
renderTemplate(w, r, "urllists.html", struct {
|
renderTemplate(w, r, "urllists.html", struct {
|
||||||
URLs []string
|
Items []URLListItem
|
||||||
Notification string
|
Notification string
|
||||||
NotificationType string
|
NotificationType string
|
||||||
}{
|
}{
|
||||||
URLs: urls,
|
Items: items,
|
||||||
|
Notification: "Error saving URL list",
|
||||||
|
NotificationType: "error",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTemplate(w, r, "urllists.html", struct {
|
||||||
|
Items []URLListItem
|
||||||
|
Notification string
|
||||||
|
NotificationType string
|
||||||
|
}{
|
||||||
|
Items: items,
|
||||||
Notification: "URL list updated successfully",
|
Notification: "URL list updated successfully",
|
||||||
NotificationType: "success",
|
NotificationType: "success",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
configMu.RLock()
|
items, err := readURLListCSV(urlFileList)
|
||||||
urlFileList := config.URLFileList
|
if err != nil || len(items) == 0 {
|
||||||
defaultURLs := config.DefaultURLs
|
// Use default blocklists
|
||||||
configMu.RUnlock()
|
defaultItems := defaultURLs
|
||||||
|
writeURLListCSV(urlFileList, defaultItems)
|
||||||
urls, _ := readLines(urlFileList)
|
items = defaultItems
|
||||||
if len(urls) == 0 {
|
|
||||||
writeLines(urlFileList, defaultURLs)
|
|
||||||
urls = defaultURLs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data := struct {
|
renderTemplate(w, r, "urllists.html", struct {
|
||||||
URLs []string
|
Items []URLListItem
|
||||||
}{
|
}{
|
||||||
URLs: urls,
|
Items: items,
|
||||||
}
|
})
|
||||||
renderTemplate(w, r, "urllists.html", data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func domainsHandler(w http.ResponseWriter, r *http.Request) {
|
func domainsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
@@ -6,4 +6,6 @@ go build -o unifi-blocklist-app main.go
|
|||||||
|
|
||||||
tailwindcss -o static/tailwind.css --minify
|
tailwindcss -o static/tailwind.css --minify
|
||||||
|
|
||||||
|
tailwindcss -o static/tailwind.css --minify --watch
|
||||||
|
|
||||||
git add . && git commit -m "update" && git push
|
git add . && git commit -m "update" && git push
|
||||||
+1
-1
File diff suppressed because one or more lines are too long
+137
-16
@@ -1,34 +1,155 @@
|
|||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<div class="max-w-4xl mx-auto">
|
<div class="max-w-6xl mx-auto">
|
||||||
<h1 class="text-3xl font-bold mb-6">URL Lists</h1>
|
<h1 class="text-3xl font-bold mb-6">URL Blocklists</h1>
|
||||||
<form method="POST" class="mb-6 bg-gray-800 p-4 rounded">
|
|
||||||
|
<!-- Add New URL Form -->
|
||||||
|
<div class="bg-gray-800 p-6 rounded mb-6">
|
||||||
|
<h2 class="text-xl font-bold mb-4">Add New Blocklist</h2>
|
||||||
|
<form method="POST" class="space-y-4">
|
||||||
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
||||||
<input type="hidden" name="action" value="add">
|
<input type="hidden" name="action" value="add">
|
||||||
<div class="flex gap-2">
|
|
||||||
<input type="text" name="url" placeholder="Add URL (e.g., https://example.com/blocklist.txt)" class="flex-1 p-2 bg-gray-700 rounded text-white placeholder-gray-400">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white font-bold px-4 py-2 rounded transition">Add</button>
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-300 mb-1">Name *</label>
|
||||||
|
<input type="text" name="name" placeholder="e.g., AdGuard Default" class="w-full p-2 bg-gray-700 rounded text-white placeholder-gray-400" required>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-300 mb-1">Group</label>
|
||||||
|
<input type="text" name="group" placeholder="e.g., Adblock, Malware" class="w-full p-2 bg-gray-700 rounded text-white placeholder-gray-400">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-300 mb-1">URL *</label>
|
||||||
|
<input type="text" name="url" placeholder="https://example.com/blocklist.txt" class="w-full p-2 bg-gray-700 rounded text-white placeholder-gray-400" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-300 mb-1">Note</label>
|
||||||
|
<textarea name="note" placeholder="Optional notes about this blocklist" class="w-full p-2 bg-gray-700 rounded text-white placeholder-gray-400" rows="2"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white font-bold px-4 py-2 rounded transition">Add Blocklist</button>
|
||||||
</form>
|
</form>
|
||||||
<ul class="space-y-2">
|
</div>
|
||||||
{{ range $i, $url := .PageData.URLs }}
|
|
||||||
<li class="flex justify-between items-center bg-gray-800 p-3 rounded">
|
<!-- Blocklists Table -->
|
||||||
<span class="text-sm text-gray-300 break-all">{{ $url }}</span>
|
<div class="bg-gray-800 rounded overflow-hidden">
|
||||||
<div class="flex gap-2 ml-4">
|
<table class="w-full">
|
||||||
|
<thead class="bg-gray-700">
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-3 text-left text-sm font-bold text-gray-300">Status</th>
|
||||||
|
<th class="px-4 py-3 text-left text-sm font-bold text-gray-300">Name</th>
|
||||||
|
<th class="px-4 py-3 text-left text-sm font-bold text-gray-300">Group</th>
|
||||||
|
<th class="px-4 py-3 text-left text-sm font-bold text-gray-300">URL</th>
|
||||||
|
<th class="px-4 py-3 text-left text-sm font-bold text-gray-300">Note</th>
|
||||||
|
<th class="px-4 py-3 text-left text-sm font-bold text-gray-300">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{ range $i, $item := .PageData.Items }}
|
||||||
|
<tr class="border-t border-gray-700 hover:bg-gray-750 transition">
|
||||||
|
<td class="px-4 py-3 text-center">
|
||||||
<form method="POST" class="inline">
|
<form method="POST" class="inline">
|
||||||
<input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
|
<input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
|
||||||
<input type="hidden" name="action" value="toggle">
|
<input type="hidden" name="action" value="toggle">
|
||||||
<input type="hidden" name="index" value="{{ $i }}">
|
<input type="hidden" name="index" value="{{ $i }}">
|
||||||
<button type="submit" class="bg-yellow-600 hover:bg-yellow-700 text-white font-bold px-3 py-1 rounded text-sm transition">Toggle</button>
|
<button type="submit" class="px-3 py-1 rounded text-sm font-bold transition {{ if $item.Enabled }}bg-green-600 hover:bg-green-700 text-white{{ else }}bg-gray-600 hover:bg-gray-700 text-gray-200{{ end }}">
|
||||||
|
{{ if $item.Enabled }}Enabled{{ else }}Disabled{{ end }}
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-300">{{ $item.Name }}</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-400">{{ if $item.Group }}{{ $item.Group }}{{ else }}<span class="text-gray-600">-</span>{{ end }}</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-400 max-w-xs truncate" title="{{ $item.URL }}">{{ $item.URL }}</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-gray-400 max-w-xs truncate" title="{{ $item.Note }}">{{ if $item.Note }}{{ $item.Note }}{{ else }}<span class="text-gray-600">-</span>{{ end }}</td>
|
||||||
|
<td class="px-4 py-3 text-sm space-x-2">
|
||||||
|
<button onclick="openEditModal('{{ $i }}', '{{ $item.Name }}', '{{ $item.URL }}', '{{ $item.Group }}', '{{ $item.Note }}')" class="bg-blue-600 hover:bg-blue-700 text-white font-bold px-2 py-1 rounded transition">
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
<form method="POST" class="inline">
|
<form method="POST" class="inline">
|
||||||
<input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
|
<input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
|
||||||
<input type="hidden" name="action" value="remove">
|
<input type="hidden" name="action" value="remove">
|
||||||
<input type="hidden" name="index" value="{{ $i }}">
|
<input type="hidden" name="index" value="{{ $i }}">
|
||||||
<button type="submit" class="bg-red-600 hover:bg-red-700 text-white font-bold px-3 py-1 rounded text-sm transition">Remove</button>
|
<button type="submit" onclick="return confirm('Are you sure you want to delete this blocklist?')" class="bg-red-600 hover:bg-red-700 text-white font-bold px-2 py-1 rounded transition">
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ if not .PageData.Items }}
|
||||||
|
<div class="text-center text-gray-400 py-8">
|
||||||
|
No blocklists configured. Add one to get started.
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Edit Modal -->
|
||||||
|
<div id="editModal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
|
<div class="bg-gray-800 rounded-lg shadow-lg max-w-md w-full mx-4 p-6">
|
||||||
|
<h2 class="text-xl font-bold mb-4">Edit Blocklist</h2>
|
||||||
|
<form method="POST" class="space-y-4">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
||||||
|
<input type="hidden" name="action" value="edit">
|
||||||
|
<input type="hidden" name="index" id="modalIndex" value="">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-300 mb-1">Name *</label>
|
||||||
|
<input type="text" name="name" id="modalName" placeholder="Name" class="w-full p-2 bg-gray-700 rounded text-white placeholder-gray-400" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-300 mb-1">Group</label>
|
||||||
|
<input type="text" name="group" id="modalGroup" placeholder="Group" class="w-full p-2 bg-gray-700 rounded text-white placeholder-gray-400">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-300 mb-1">URL *</label>
|
||||||
|
<input type="text" name="url" id="modalURL" placeholder="URL" class="w-full p-2 bg-gray-700 rounded text-white placeholder-gray-400" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-300 mb-1">Note</label>
|
||||||
|
<textarea name="note" id="modalNote" placeholder="Note" class="w-full p-2 bg-gray-700 rounded text-white placeholder-gray-400" rows="2"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-2 justify-end">
|
||||||
|
<button type="button" onclick="closeEditModal()" class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded font-medium transition">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button type="submit" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded font-medium transition">
|
||||||
|
Save Changes
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
|
||||||
{{ end }}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function openEditModal(index, name, url, group, note) {
|
||||||
|
document.getElementById('modalIndex').value = index;
|
||||||
|
document.getElementById('modalName').value = name;
|
||||||
|
document.getElementById('modalURL').value = url;
|
||||||
|
document.getElementById('modalGroup').value = group;
|
||||||
|
document.getElementById('modalNote').value = note;
|
||||||
|
document.getElementById('editModal').classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeEditModal() {
|
||||||
|
document.getElementById('editModal').classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close modal when clicking outside
|
||||||
|
document.getElementById('editModal').addEventListener('click', function(event) {
|
||||||
|
if (event.target === this) {
|
||||||
|
closeEditModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
Name,Enabled,Group,URL,Note
|
||||||
|
AdGuard DNS filter,false,Default,https://adguardteam.github.io/HostlistsRegistry/assets/filter_27.txt,
|
||||||
|
AdGuard Russian filter,true,Default,https://adguardteam.github.io/HostlistsRegistry/assets/filter_49.txt,
|
||||||
|
AdGuard Base filter,false,Default,https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt,
|
||||||
|
AdGuard Tracking Protection,true,Default,https://adguardteam.github.io/HostlistsRegistry/assets/filter_42.txt,
|
||||||
|
AdGuard Social Media filter,true,Default,https://adguardteam.github.io/HostlistsRegistry/assets/filter_18.txt,
|
||||||
|
AdGuard Annoyance filter,true,Default,https://adguardteam.github.io/HostlistsRegistry/assets/filter_23.txt,
|
||||||
|
AdGuard Mobile Ads filter,true,Default,https://adguardteam.github.io/HostlistsRegistry/assets/filter_11.txt,
|
||||||
|
AdGuard Search Ads filter,false,Default,https://adguardteam.github.io/HostlistsRegistry/assets/filter_9.txt,
|
||||||
|
AdGuard Adult filter,true,Default,https://adguardteam.github.io/HostlistsRegistry/assets/filter_50.txt,
|
||||||
|
AntiMalware Hosts,true,Default,https://raw.githubusercontent.com/DandelionSprout/adfilt/master/Alternate%20versions%20Anti-Malware%20List/AntiMalwareHosts.txt,
|
||||||
|
DigitalSide Threat Intel,true,Default,https://osint.digitalside.it/Threat-Intel/lists/latestdomains.txt,
|
||||||
|
Firebog Crypto,true,Default,https://v.firebog.net/hosts/Prigent-Crypto.txt,
|
||||||
|
Phishing Army,true,Default,https://phishing.army/download/phishing_army_blocklist_extended.txt,
|
||||||
|
W3kbl,true,Default,https://v.firebog.net/hosts/static/w3kbl.txt,
|
||||||
|
@@ -1,13 +0,0 @@
|
|||||||
https://adguardteam.github.io/HostlistsRegistry/assets/filter_27.txt
|
|
||||||
https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txt
|
|
||||||
https://adguardteam.github.io/HostlistsRegistry/assets/filter_42.txt
|
|
||||||
https://adguardteam.github.io/HostlistsRegistry/assets/filter_18.txt
|
|
||||||
https://adguardteam.github.io/HostlistsRegistry/assets/filter_23.txt
|
|
||||||
https://adguardteam.github.io/HostlistsRegistry/assets/filter_11.txt
|
|
||||||
https://adguardteam.github.io/HostlistsRegistry/assets/filter_9.txt
|
|
||||||
https://adguardteam.github.io/HostlistsRegistry/assets/filter_50.txt
|
|
||||||
https://raw.githubusercontent.com/DandelionSprout/adfilt/master/Alternate%20versions%20Anti-Malware%20List/AntiMalwareHosts.txt
|
|
||||||
https://osint.digitalside.it/Threat-Intel/lists/latestdomains.txt
|
|
||||||
https://v.firebog.net/hosts/Prigent-Crypto.txt
|
|
||||||
https://phishing.army/download/phishing_army_blocklist_extended.txt
|
|
||||||
https://v.firebog.net/hosts/static/w3kbl.txt
|
|
||||||
@@ -159,6 +159,91 @@ func isValidDomain(d string) bool {
|
|||||||
return re.MatchString(d)
|
return re.MatchString(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// URLListItem represents a single URL entry in the CSV
|
||||||
|
type URLListItem struct {
|
||||||
|
Name string
|
||||||
|
Enabled bool
|
||||||
|
Group string
|
||||||
|
URL string
|
||||||
|
Note string
|
||||||
|
}
|
||||||
|
|
||||||
|
// readURLListCSV reads the URL list from a CSV file
|
||||||
|
func readURLListCSV(file string) ([]URLListItem, error) {
|
||||||
|
data, err := os.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(string(data), "\n")
|
||||||
|
var items []URLListItem
|
||||||
|
|
||||||
|
for i, line := range lines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line == "" || (i == 0 && strings.HasPrefix(line, "Name,")) {
|
||||||
|
// Skip empty lines and header
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.SplitN(line, ",", 5)
|
||||||
|
if len(parts) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
item := URLListItem{
|
||||||
|
Name: strings.TrimSpace(parts[0]),
|
||||||
|
Enabled: strings.TrimSpace(parts[1]) == "true",
|
||||||
|
Group: "",
|
||||||
|
URL: "",
|
||||||
|
Note: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parts) > 2 {
|
||||||
|
item.Group = strings.TrimSpace(parts[2])
|
||||||
|
}
|
||||||
|
if len(parts) > 3 {
|
||||||
|
item.URL = strings.TrimSpace(parts[3])
|
||||||
|
}
|
||||||
|
if len(parts) > 4 {
|
||||||
|
item.Note = strings.TrimSpace(parts[4])
|
||||||
|
}
|
||||||
|
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeURLListCSV writes the URL list to a CSV file
|
||||||
|
func writeURLListCSV(file string, items []URLListItem) error {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString("Name,Enabled,Group,URL,Note\n")
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
enabled := "false"
|
||||||
|
if item.Enabled {
|
||||||
|
enabled = "true"
|
||||||
|
}
|
||||||
|
// Escape quotes and wrap in quotes if needed
|
||||||
|
name := escapeCSVField(item.Name)
|
||||||
|
group := escapeCSVField(item.Group)
|
||||||
|
url := escapeCSVField(item.URL)
|
||||||
|
note := escapeCSVField(item.Note)
|
||||||
|
|
||||||
|
sb.WriteString(name + "," + enabled + "," + group + "," + url + "," + note + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.WriteFile(file, []byte(sb.String()), 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// escapeCSVField properly escapes a CSV field
|
||||||
|
func escapeCSVField(field string) string {
|
||||||
|
if strings.ContainsAny(field, ",\"\n") {
|
||||||
|
return "\"" + strings.ReplaceAll(field, "\"", "\"\"") + "\""
|
||||||
|
}
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
|
||||||
func generateSecret() string {
|
func generateSecret() string {
|
||||||
// This function is kept for backward compatibility but is not currently used
|
// This function is kept for backward compatibility but is not currently used
|
||||||
// since we're using pquerna/otp for TOTP generation
|
// since we're using pquerna/otp for TOTP generation
|
||||||
|
|||||||
Reference in New Issue
Block a user