Updated sessions,notifications
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
go.mod
|
go.mod
|
||||||
go.sum
|
go.sum
|
||||||
config.json*
|
config.json*
|
||||||
unifi-blocklist-app
|
/unifi-blocklist-app
|
||||||
@@ -70,14 +70,26 @@ func loginHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
defer configMu.RUnlock()
|
defer configMu.RUnlock()
|
||||||
|
|
||||||
if username != config.Username || bcrypt.CompareHashAndPassword([]byte(config.HashedPass), []byte(password)) != nil {
|
if username != config.Username || bcrypt.CompareHashAndPassword([]byte(config.HashedPass), []byte(password)) != nil {
|
||||||
http.Error(w, "Invalid credentials", http.StatusUnauthorized)
|
renderTemplate(w, r, "login.html", struct {
|
||||||
|
Notification string
|
||||||
|
NotificationType string
|
||||||
|
}{
|
||||||
|
Notification: "Invalid username or password",
|
||||||
|
NotificationType: "error",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.MFASecret != "" {
|
if config.MFASecret != "" {
|
||||||
valid := totp.Validate(mfaCode, config.MFASecret)
|
valid := totp.Validate(mfaCode, config.MFASecret)
|
||||||
if !valid {
|
if !valid {
|
||||||
http.Error(w, "Invalid MFA code", http.StatusUnauthorized)
|
renderTemplate(w, r, "login.html", struct {
|
||||||
|
Notification string
|
||||||
|
NotificationType string
|
||||||
|
}{
|
||||||
|
Notification: "Invalid MFA code",
|
||||||
|
NotificationType: "error",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+89
-7
@@ -33,26 +33,39 @@ func urlListsHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
configMu.RLock()
|
configMu.RLock()
|
||||||
urlFileList := config.URLFileList
|
urlFileList := config.URLFileList
|
||||||
|
defaultURLs := config.DefaultURLs
|
||||||
configMu.RUnlock()
|
configMu.RUnlock()
|
||||||
|
|
||||||
urls, err := readLines(urlFileList)
|
urls, err := readLines(urlFileList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Error reading URL list", http.StatusInternalServerError)
|
renderTemplate(w, r, "urllists.html", struct {
|
||||||
|
URLs []string
|
||||||
|
Notification string
|
||||||
|
NotificationType string
|
||||||
|
}{
|
||||||
|
URLs: urls,
|
||||||
|
Notification: "Error reading URL list",
|
||||||
|
NotificationType: "error",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
errorMsg := ""
|
||||||
switch action {
|
switch action {
|
||||||
case "add":
|
case "add":
|
||||||
if isValidURL(url) {
|
if !isValidURL(url) {
|
||||||
|
errorMsg = "Invalid URL format. Must start with http:// or https://"
|
||||||
|
} else {
|
||||||
urls = append(urls, url)
|
urls = append(urls, url)
|
||||||
}
|
}
|
||||||
case "remove":
|
case "remove":
|
||||||
index, _ := strconv.Atoi(indexStr)
|
index, _ := strconv.Atoi(indexStr)
|
||||||
if index >= 0 && index < len(urls) {
|
if index >= 0 && index < len(urls) {
|
||||||
urls = append(urls[:index], urls[index+1:]...)
|
urls = append(urls[:index], urls[index+1:]...)
|
||||||
|
} else {
|
||||||
|
errorMsg = "Invalid index"
|
||||||
}
|
}
|
||||||
case "toggle":
|
case "toggle":
|
||||||
// Assume enabled/disabled by commenting out with #
|
|
||||||
index, _ := strconv.Atoi(indexStr)
|
index, _ := strconv.Atoi(indexStr)
|
||||||
if index >= 0 && index < len(urls) {
|
if index >= 0 && index < len(urls) {
|
||||||
if strings.HasPrefix(urls[index], "#") {
|
if strings.HasPrefix(urls[index], "#") {
|
||||||
@@ -60,11 +73,41 @@ func urlListsHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
} else {
|
} else {
|
||||||
urls[index] = "#" + urls[index]
|
urls[index] = "#" + urls[index]
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
errorMsg = "Invalid index"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if errorMsg != "" {
|
||||||
|
renderTemplate(w, r, "urllists.html", struct {
|
||||||
|
URLs []string
|
||||||
|
Notification string
|
||||||
|
NotificationType string
|
||||||
|
}{
|
||||||
|
URLs: urls,
|
||||||
|
Notification: errorMsg,
|
||||||
|
NotificationType: "error",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
writeLines(urlFileList, urls)
|
writeLines(urlFileList, urls)
|
||||||
http.Redirect(w, r, "/urllists", http.StatusSeeOther)
|
|
||||||
|
// Show success notification
|
||||||
|
if len(urls) == 0 {
|
||||||
|
writeLines(urlFileList, defaultURLs)
|
||||||
|
urls = defaultURLs
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTemplate(w, r, "urllists.html", struct {
|
||||||
|
URLs []string
|
||||||
|
Notification string
|
||||||
|
NotificationType string
|
||||||
|
}{
|
||||||
|
URLs: urls,
|
||||||
|
Notification: "URL list updated successfully",
|
||||||
|
NotificationType: "success",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,13 +141,26 @@ func domainsHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
domains, err := readLines(blocklistFile)
|
domains, err := readLines(blocklistFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Error reading domains", http.StatusInternalServerError)
|
renderTemplate(w, r, "domains.html", struct {
|
||||||
|
Domains []string
|
||||||
|
Query string
|
||||||
|
Notification string
|
||||||
|
NotificationType string
|
||||||
|
}{
|
||||||
|
Domains: domains,
|
||||||
|
Query: "",
|
||||||
|
Notification: "Error reading domains",
|
||||||
|
NotificationType: "error",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
errorMsg := ""
|
||||||
switch action {
|
switch action {
|
||||||
case "add":
|
case "add":
|
||||||
if isValidDomain(domain) {
|
if !isValidDomain(domain) {
|
||||||
|
errorMsg = "Invalid domain format"
|
||||||
|
} else {
|
||||||
domains = append(domains, domain)
|
domains = append(domains, domain)
|
||||||
sort.Strings(domains)
|
sort.Strings(domains)
|
||||||
}
|
}
|
||||||
@@ -117,8 +173,34 @@ func domainsHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if errorMsg != "" {
|
||||||
|
renderTemplate(w, r, "domains.html", struct {
|
||||||
|
Domains []string
|
||||||
|
Query string
|
||||||
|
Notification string
|
||||||
|
NotificationType string
|
||||||
|
}{
|
||||||
|
Domains: domains,
|
||||||
|
Query: "",
|
||||||
|
Notification: errorMsg,
|
||||||
|
NotificationType: "error",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
writeLines(blocklistFile, domains)
|
writeLines(blocklistFile, domains)
|
||||||
http.Redirect(w, r, "/domains", http.StatusSeeOther)
|
|
||||||
|
renderTemplate(w, r, "domains.html", struct {
|
||||||
|
Domains []string
|
||||||
|
Query string
|
||||||
|
Notification string
|
||||||
|
NotificationType string
|
||||||
|
}{
|
||||||
|
Domains: domains,
|
||||||
|
Query: "",
|
||||||
|
Notification: "Domain updated successfully",
|
||||||
|
NotificationType: "success",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
go build -o unifi-blocklist-app main.go 2>&1 && echo "✓ Build successful"
|
go build -o unifi-blocklist-app main.go 2>&1 && echo "✓ Build successful"
|
||||||
|
|
||||||
|
|
||||||
go build -o unifi-blocklist-app main.go
|
go build -o unifi-blocklist-app main.go
|
||||||
|
|
||||||
|
|
||||||
|
tailwindcss -o static/tailwind.css --minify
|
||||||
@@ -1 +0,0 @@
|
|||||||
tailwindcss -o static/tailwind.css --minify
|
|
||||||
+1
-1
File diff suppressed because one or more lines are too long
+2
-1
@@ -3,10 +3,11 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{{ with .PageData }}{{ .Title }}{{ end }} - {{ .AppName }}</title>
|
<title>{{ .AppName }}</title>
|
||||||
<link href="/static/tailwind.css" rel="stylesheet">
|
<link href="/static/tailwind.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-900 text-gray-100 min-h-screen flex flex-col">
|
<body class="bg-gray-900 text-gray-100 min-h-screen flex flex-col">
|
||||||
|
{{ template "notifications" . }}
|
||||||
<header class="bg-gray-800 p-4">
|
<header class="bg-gray-800 p-4">
|
||||||
<nav class="flex justify-between">
|
<nav class="flex justify-between">
|
||||||
<a href="/" class="text-xl font-bold">{{ .AppName }}</a>
|
<a href="/" class="text-xl font-bold">{{ .AppName }}</a>
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<h1 class="text-2xl mb-4">Dashboard</h1>
|
<div class="max-w-4xl mx-auto">
|
||||||
<p>Welcome to Unifi Custom Blocklist Manager.</p>
|
<h1 class="text-3xl font-bold mb-4">Dashboard</h1>
|
||||||
<form method="POST" action="/apply">
|
<p class="text-gray-300 mb-6">Welcome to {{ .AppName }}. Use the menu above to manage blocklists and domains.</p>
|
||||||
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
<form method="POST" action="/apply">
|
||||||
<button type="submit" class="bg-green-600 p-2 rounded">Apply Changes</button>
|
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
||||||
</form>
|
<button type="submit" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded transition">Apply Changes</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
+30
-26
@@ -1,28 +1,32 @@
|
|||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<h1 class="text-2xl mb-4">Domains</h1>
|
<div class="max-w-4xl mx-auto">
|
||||||
<form method="GET" class="mb-4">
|
<h1 class="text-3xl font-bold mb-6">Domains</h1>
|
||||||
<input type="text" name="query" placeholder="Search (use * for wildcard)" value="{{ with .PageData }}{{ .Query }}{{ end }}" class="p-2 bg-gray-700 rounded">
|
<form method="GET" class="mb-6 bg-gray-800 p-4 rounded">
|
||||||
<button type="submit" class="bg-blue-600 p-2 rounded">Search</button>
|
<div class="flex gap-2">
|
||||||
</form>
|
<input type="text" name="query" placeholder="Search domains (use * for wildcard)" value="{{ .PageData.Query }}" class="flex-1 p-2 bg-gray-700 rounded text-white placeholder-gray-400">
|
||||||
<form method="POST" class="mb-4">
|
<button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white font-bold px-4 py-2 rounded transition">Search</button>
|
||||||
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
</div>
|
||||||
<input type="hidden" name="action" value="add">
|
</form>
|
||||||
<input type="text" name="domain" placeholder="Add Domain" class="p-2 bg-gray-700 rounded">
|
<form method="POST" class="mb-6 bg-gray-800 p-4 rounded">
|
||||||
<button type="submit" class="bg-green-600 p-2 rounded">Add</button>
|
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
||||||
</form>
|
<input type="hidden" name="action" value="add">
|
||||||
<ul class="space-y-2">
|
<div class="flex gap-2">
|
||||||
{{ with .PageData }}
|
<input type="text" name="domain" placeholder="Add domain (e.g., ads.example.com)" class="flex-1 p-2 bg-gray-700 rounded text-white placeholder-gray-400">
|
||||||
{{ range .Domains }}
|
<button type="submit" class="bg-green-600 hover:bg-green-700 text-white font-bold px-4 py-2 rounded transition">Add</button>
|
||||||
<li class="flex justify-between bg-gray-800 p-2 rounded">
|
</div>
|
||||||
{{ . }}
|
</form>
|
||||||
<form method="POST" class="inline">
|
<ul class="space-y-2">
|
||||||
<input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
|
{{ range .PageData.Domains }}
|
||||||
<input type="hidden" name="action" value="remove">
|
<li class="flex justify-between items-center bg-gray-800 p-3 rounded">
|
||||||
<input type="hidden" name="domain" value="{{ . }}">
|
<span class="text-sm text-gray-300 break-all">{{ . }}</span>
|
||||||
<button type="submit" class="bg-red-600 p-1 rounded">Remove</button>
|
<form method="POST" class="inline ml-4">
|
||||||
</form>
|
<input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
|
||||||
</li>
|
<input type="hidden" name="action" value="remove">
|
||||||
{{ end }}
|
<input type="hidden" name="domain" value="{{ . }}">
|
||||||
{{ end }}
|
<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>
|
||||||
</ul>
|
</form>
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
+10
-7
@@ -1,9 +1,12 @@
|
|||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<form method="POST" class="max-w-md mx-auto bg-gray-800 p-6 rounded">
|
<div class="min-h-screen flex items-center justify-center">
|
||||||
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
<form method="POST" class="max-w-md w-full mx-4 bg-gray-800 p-6 rounded">
|
||||||
<input type="text" name="username" placeholder="Username" class="w-full mb-4 p-2 bg-gray-700 rounded" required>
|
<h2 class="text-2xl font-bold mb-6 text-center">Login</h2>
|
||||||
<input type="password" name="password" placeholder="Password" class="w-full mb-4 p-2 bg-gray-700 rounded" required>
|
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
||||||
<input type="text" name="mfa_code" placeholder="MFA Code (if enabled)" class="w-full mb-4 p-2 bg-gray-700 rounded">
|
<input type="text" name="username" placeholder="Username" class="w-full mb-4 p-2 bg-gray-700 rounded text-white placeholder-gray-400" required>
|
||||||
<button type="submit" class="w-full bg-blue-600 p-2 rounded">Login</button>
|
<input type="password" name="password" placeholder="Password" class="w-full mb-4 p-2 bg-gray-700 rounded text-white placeholder-gray-400" required>
|
||||||
</form>
|
<input type="text" name="mfa_code" placeholder="MFA Code (if enabled)" class="w-full mb-6 p-2 bg-gray-700 rounded text-white placeholder-gray-400">
|
||||||
|
<button type="submit" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold p-2 rounded transition">Login</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{{ define "content" }}
|
||||||
|
<div class="max-w-4xl mx-auto">
|
||||||
|
<h1 class="text-3xl font-bold mb-6">Logs</h1>
|
||||||
|
<div class="bg-gray-800 p-6 rounded">
|
||||||
|
<p class="text-gray-300">Application logs will be displayed here. Currently, logs are output to the console when the application is running.</p>
|
||||||
|
<p class="text-gray-400 text-sm mt-4">Check the terminal output where you started the application for detailed activity logs.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
@@ -1,6 +1,23 @@
|
|||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<h1 class="text-2xl mb-4">MFA Setup</h1>
|
<div class="max-w-2xl mx-auto">
|
||||||
<p>MFA Secret: {{ with .PageData }}{{ .MFASecret }}{{ end }}</p>
|
<h1 class="text-3xl font-bold mb-6">MFA Setup</h1>
|
||||||
<p>OTP URL: {{ with .PageData }}{{ .OTPURL }}{{ end }}</p>
|
<div class="bg-gray-800 p-6 rounded space-y-4">
|
||||||
<p>Use a QR code generator with the OTP URL or enter the secret in your authenticator app.</p>
|
<div>
|
||||||
|
<p class="text-sm text-gray-400 mb-2"><strong>MFA Secret:</strong></p>
|
||||||
|
<p class="bg-gray-900 p-3 rounded font-mono text-sm text-green-400 break-all">{{ .PageData.MFASecret }}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm text-gray-400 mb-2"><strong>OTP URL:</strong></p>
|
||||||
|
<p class="bg-gray-900 p-3 rounded font-mono text-xs text-green-400 break-all">{{ .PageData.OTPURL }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="mt-6 p-4 bg-gray-700 rounded">
|
||||||
|
<p class="text-sm text-gray-300"><strong>Instructions:</strong></p>
|
||||||
|
<ul class="text-sm text-gray-400 mt-2 space-y-1">
|
||||||
|
<li>• Use a QR code scanner with the OTP URL above</li>
|
||||||
|
<li>• Or manually enter the MFA Secret in your authenticator app</li>
|
||||||
|
<li>• Supported apps: Google Authenticator, Authy, Microsoft Authenticator, etc.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
{{ define "notifications" }}
|
||||||
|
{{ if .Notification }}
|
||||||
|
<div id="notification-container" class="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 {{ if eq .NotificationType "error" }}border-l-4 border-red-500{{ else if eq .NotificationType "success" }}border-l-4 border-green-500{{ else }}border-l-4 border-blue-500{{ end }}">
|
||||||
|
<div class="p-6">
|
||||||
|
<div class="flex items-start">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
{{ if eq .NotificationType "error" }}
|
||||||
|
<svg class="h-6 w-6 text-red-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4v2m0 0a9 9 0 11-18 0 9 9 0 0118 0zm0-14a9 9 0 00-9 9m0 0a9 9 0 1018 0 9 9 0 00-18 0z" />
|
||||||
|
</svg>
|
||||||
|
{{ else if eq .NotificationType "success" }}
|
||||||
|
<svg class="h-6 w-6 text-green-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
{{ else }}
|
||||||
|
<svg class="h-6 w-6 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
<div class="ml-3 flex-1">
|
||||||
|
<h3 class="text-lg font-medium {{ if eq .NotificationType "error" }}text-red-400{{ else if eq .NotificationType "success" }}text-green-400{{ else }}text-blue-400{{ end }}">
|
||||||
|
{{ if eq .NotificationType "error" }}Error{{ else if eq .NotificationType "success" }}Success{{ else }}Information{{ end }}
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2 text-sm text-gray-300">
|
||||||
|
{{ .Notification }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button onclick="closeNotification()" class="ml-3 text-gray-400 hover:text-gray-200">
|
||||||
|
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-700 px-6 py-4 flex justify-end gap-3">
|
||||||
|
{{ if eq .NotificationType "error" }}
|
||||||
|
<button onclick="closeNotification()" class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded font-medium transition">
|
||||||
|
Dismiss
|
||||||
|
</button>
|
||||||
|
{{ else if eq .NotificationType "success" }}
|
||||||
|
<button onclick="closeNotification()" class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded font-medium transition">
|
||||||
|
OK
|
||||||
|
</button>
|
||||||
|
{{ else }}
|
||||||
|
<button onclick="closeNotification()" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded font-medium transition">
|
||||||
|
OK
|
||||||
|
</button>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function closeNotification() {
|
||||||
|
var container = document.getElementById('notification-container');
|
||||||
|
if (container) {
|
||||||
|
container.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
var type = '{{ .NotificationType }}';
|
||||||
|
if (type === 'success') {
|
||||||
|
setTimeout(closeNotification, 5000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
var type = '{{ .NotificationType }}';
|
||||||
|
if (type === 'success') {
|
||||||
|
setTimeout(closeNotification, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
+27
-14
@@ -1,17 +1,30 @@
|
|||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<h1 class="text-2xl mb-4">Profile</h1>
|
<div class="max-w-2xl mx-auto">
|
||||||
{{ with .PageData }}
|
<h1 class="text-3xl font-bold mb-6">Profile</h1>
|
||||||
<div class="bg-gray-800 p-4 rounded mb-4">
|
<div class="bg-gray-800 p-6 rounded">
|
||||||
<p class="mb-2"><strong>Username:</strong> admin</p>
|
<p class="mb-4">
|
||||||
<p class="mb-2">
|
<strong class="text-white">Username:</strong>
|
||||||
<strong>MFA Status:</strong>
|
<span class="text-gray-300">admin</span>
|
||||||
{{ if .MFAEnabled }}
|
</p>
|
||||||
<span class="text-green-400">Enabled</span>
|
<p class="mb-6">
|
||||||
{{ else }}
|
<strong class="text-white">MFA Status:</strong>
|
||||||
<span class="text-yellow-400">Disabled</span>
|
{{ if .PageData.MFAEnabled }}
|
||||||
{{ end }}
|
<span class="text-green-400 font-bold">Enabled</span>
|
||||||
</p>
|
{{ else }}
|
||||||
<p class="text-sm text-gray-400 mt-4">To manage MFA or change password, use the command-line interface with the executable flags.</p>
|
<span class="text-yellow-400 font-bold">Disabled</span>
|
||||||
|
{{ end }}
|
||||||
|
</p>
|
||||||
|
<div class="bg-gray-700 p-4 rounded mt-4">
|
||||||
|
<p class="text-sm text-gray-300">
|
||||||
|
<strong>To manage MFA or change password:</strong>
|
||||||
|
</p>
|
||||||
|
<p class="text-sm text-gray-400 mt-2">Use the command-line interface with the executable flags:</p>
|
||||||
|
<ul class="text-sm text-gray-400 mt-2 ml-4 space-y-1">
|
||||||
|
<li>• <code class="bg-gray-900 px-2 py-1 rounded">./unifi-blocklist-app -pw "NewPassword"</code> - Change password</li>
|
||||||
|
<li>• <code class="bg-gray-900 px-2 py-1 rounded">./unifi-blocklist-app -mfa on</code> - Enable MFA</li>
|
||||||
|
<li>• <code class="bg-gray-900 px-2 py-1 rounded">./unifi-blocklist-app -mfa off</code> - Disable MFA</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
+31
-29
@@ -1,32 +1,34 @@
|
|||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<h1 class="text-2xl mb-4">URL Lists</h1>
|
<div class="max-w-4xl mx-auto">
|
||||||
<form method="POST" class="mb-4">
|
<h1 class="text-3xl font-bold mb-6">URL Lists</h1>
|
||||||
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
<form method="POST" class="mb-6 bg-gray-800 p-4 rounded">
|
||||||
<input type="hidden" name="action" value="add">
|
<input type="hidden" name="csrf_token" value="{{ .CSRFToken }}">
|
||||||
<input type="text" name="url" placeholder="Add URL" class="p-2 bg-gray-700 rounded">
|
<input type="hidden" name="action" value="add">
|
||||||
<button type="submit" class="bg-blue-600 p-2 rounded">Add</button>
|
<div class="flex gap-2">
|
||||||
</form>
|
<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">
|
||||||
<ul class="space-y-2">
|
<button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white font-bold px-4 py-2 rounded transition">Add</button>
|
||||||
{{ with .PageData }}
|
|
||||||
{{ range $i, $url := .URLs }}
|
|
||||||
<li class="flex justify-between bg-gray-800 p-2 rounded">
|
|
||||||
{{ $url }}
|
|
||||||
<div>
|
|
||||||
<form method="POST" class="inline">
|
|
||||||
<input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
|
|
||||||
<input type="hidden" name="action" value="toggle">
|
|
||||||
<input type="hidden" name="index" value="{{ $i }}">
|
|
||||||
<button type="submit" class="bg-yellow-600 p-1 rounded">Toggle</button>
|
|
||||||
</form>
|
|
||||||
<form method="POST" class="inline">
|
|
||||||
<input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
|
|
||||||
<input type="hidden" name="action" value="remove">
|
|
||||||
<input type="hidden" name="index" value="{{ $i }}">
|
|
||||||
<button type="submit" class="bg-red-600 p-1 rounded">Remove</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</form>
|
||||||
{{ end }}
|
<ul class="space-y-2">
|
||||||
{{ end }}
|
{{ range $i, $url := .PageData.URLs }}
|
||||||
</ul>
|
<li class="flex justify-between items-center bg-gray-800 p-3 rounded">
|
||||||
|
<span class="text-sm text-gray-300 break-all">{{ $url }}</span>
|
||||||
|
<div class="flex gap-2 ml-4">
|
||||||
|
<form method="POST" class="inline">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
|
||||||
|
<input type="hidden" name="action" value="toggle">
|
||||||
|
<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>
|
||||||
|
</form>
|
||||||
|
<form method="POST" class="inline">
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ $.CSRFToken }}">
|
||||||
|
<input type="hidden" name="action" value="remove">
|
||||||
|
<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>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
https://adguardteam.github.io/HostlistsRegistry/assets/filter_27.txt
|
#https://adguardteam.github.io/HostlistsRegistry/assets/filter_27.txt
|
||||||
https://adguardteam.github.io/HostlistsRegistry/assets/filter_49.txt
|
#https://adguardteam.github.io/HostlistsRegistry/assets/filter_49.txt
|
||||||
https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.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_42.txt
|
||||||
https://adguardteam.github.io/HostlistsRegistry/assets/filter_18.txt
|
https://adguardteam.github.io/HostlistsRegistry/assets/filter_18.txt
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -17,10 +18,12 @@ import (
|
|||||||
var content embed.FS
|
var content embed.FS
|
||||||
|
|
||||||
type TemplateData struct {
|
type TemplateData struct {
|
||||||
CSRFToken string
|
CSRFToken string
|
||||||
PageData interface{}
|
PageData interface{}
|
||||||
Authenticated bool
|
Authenticated bool
|
||||||
AppName string
|
AppName string
|
||||||
|
Notification string
|
||||||
|
NotificationType string
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderTemplate(w http.ResponseWriter, r *http.Request, tmpl string, pageData interface{}) {
|
func renderTemplate(w http.ResponseWriter, r *http.Request, tmpl string, pageData interface{}) {
|
||||||
@@ -42,9 +45,25 @@ func renderTemplate(w http.ResponseWriter, r *http.Request, tmpl string, pageDat
|
|||||||
AppName: appName,
|
AppName: appName,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract notification and type from pageData if it has those fields
|
||||||
|
if pageData != nil {
|
||||||
|
v := reflect.ValueOf(pageData)
|
||||||
|
if v.Kind() == reflect.Struct {
|
||||||
|
notifField := v.FieldByName("Notification")
|
||||||
|
typeField := v.FieldByName("NotificationType")
|
||||||
|
|
||||||
|
if notifField.IsValid() && notifField.Kind() == reflect.String {
|
||||||
|
td.Notification = notifField.String()
|
||||||
|
}
|
||||||
|
if typeField.IsValid() && typeField.Kind() == reflect.String {
|
||||||
|
td.NotificationType = typeField.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
templatesFS, _ := fs.Sub(content, "templates")
|
templatesFS, _ := fs.Sub(content, "templates")
|
||||||
// Parse base.html and the specific page template together
|
// Parse base.html, notifications.html and the specific page template together
|
||||||
files := []string{"base.html", tmpl}
|
files := []string{"base.html", "notifications.html", tmpl}
|
||||||
t, err := template.ParseFS(templatesFS, files...)
|
t, err := template.ParseFS(templatesFS, files...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|||||||
Reference in New Issue
Block a user