revamped CSS - using Tailwind now, update layout and added home page

This commit is contained in:
nahakubuilde
2025-07-19 12:58:22 +01:00
parent 20cfcd1829
commit 173569365b
17 changed files with 4318 additions and 8950 deletions

File diff suppressed because it is too large Load Diff

Binary file not shown.

42
landingpage/handler.go Normal file
View File

@@ -0,0 +1,42 @@
package landingpage
import (
"html/template"
"io/fs"
"net/http"
)
type Handler struct {
template *template.Template
}
func NewHandler(embeddedFS fs.FS) (*Handler, error) {
tmpl, err := template.ParseFS(embeddedFS, "web/base.html", "web/landing_page.html")
if err != nil {
return nil, err
}
return &Handler{
template: tmpl,
}, nil
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Only handle root path
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
data := struct {
CurrentPage string
}{
CurrentPage: "home",
}
w.Header().Set("Content-Type", "text/html")
if err := h.template.ExecuteTemplate(w, "base.html", data); err != nil {
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
}

26
main.go
View File

@@ -16,6 +16,7 @@ import (
"syscall"
"time"
"headeranalyzer/landingpage"
"headeranalyzer/parser"
"headeranalyzer/passwordgenerator"
"headeranalyzer/pwpusher"
@@ -129,6 +130,10 @@ func main() {
passwordgenerator.InitWordList()
// Create handlers with separate template sets
landingHandler, err := landingpage.NewHandler(embeddedFiles)
if err != nil {
log.Fatalf("Failed to initialize landing page handler: %v", err)
}
indexHandler := parser.NewHandler(embeddedFiles)
dnsHandler := resolver.NewHandler(embeddedFiles)
passwordHandler := passwordgenerator.NewHandler(embeddedFiles)
@@ -158,17 +163,30 @@ func main() {
w.Write(data)
})
http.Handle("/", indexHandler)
// Serve CSS file with correct MIME type
http.HandleFunc("/style.css", func(w http.ResponseWriter, r *http.Request) {
data, err := fs.ReadFile(staticFS, "style.css")
if err != nil {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "text/css")
w.Write(data)
})
http.Handle("/", landingHandler)
http.Handle("/analyze", indexHandler)
http.Handle("/dns", dnsHandler)
http.HandleFunc("/api/dns", resolver.DNSAPIHandler)
http.Handle("/password", passwordHandler)
http.Handle("/pwgenerator", passwordHandler)
http.HandleFunc("/api/password", passwordgenerator.PasswordAPIHandler)
http.HandleFunc("/api/pwgenerator", passwordgenerator.PasswordAPIHandler)
http.HandleFunc("/api/password/info", passwordgenerator.PasswordInfoAPIHandler)
http.HandleFunc("/api/pwgenerator/info", passwordgenerator.PasswordInfoAPIHandler)
// Register PWPusher routes
pwPusher.RegisterRoutesWithDefault()

View File

@@ -3,6 +3,7 @@ package passwordgenerator
import (
"encoding/json"
"net/http"
"strings"
"headeranalyzer/security"
)
@@ -23,6 +24,7 @@ func PasswordAPIHandler(w http.ResponseWriter, r *http.Request) {
IncludeLower bool `json:"includeLower"`
NumberCount int `json:"numberCount"`
SpecialChars string `json:"specialChars"`
MinSpecialChars int `json:"minSpecialChars"`
NoConsecutive bool `json:"noConsecutive"`
WordCount int `json:"wordCount"`
NumberPosition string `json:"numberPosition"`
@@ -49,6 +51,12 @@ func PasswordAPIHandler(w http.ResponseWriter, r *http.Request) {
return
}
if requestData.MinSpecialChars < 0 || requestData.MinSpecialChars > 20 {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Minimum special characters count must be between 0 and 20"))
return
}
if requestData.WordCount < 2 || requestData.WordCount > 10 {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Word count must be between 2 and 10"))
@@ -72,8 +80,17 @@ func PasswordAPIHandler(w http.ResponseWriter, r *http.Request) {
requestData.NumberPosition = "end" // Default
}
// Sanitize special characters to prevent potential issues
requestData.SpecialChars = validator.SanitizeHTML(requestData.SpecialChars)
// Validate special characters - only allow specific safe characters
if len(requestData.SpecialChars) > 0 {
allowedSpecialChars := "!@#$%&*-_=+."
for _, char := range requestData.SpecialChars {
if !strings.ContainsRune(allowedSpecialChars, char) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Special characters must only contain: !@#$%&*-_=+."))
return
}
}
}
// Convert to internal Config format
config := Config{
@@ -82,6 +99,7 @@ func PasswordAPIHandler(w http.ResponseWriter, r *http.Request) {
IncludeLower: requestData.IncludeLower,
NumberCount: requestData.NumberCount,
SpecialChars: requestData.SpecialChars,
MinSpecialChars: requestData.MinSpecialChars,
NoConsecutive: requestData.NoConsecutive,
UsePassphrase: requestData.Type == "passphrase",
WordCount: requestData.WordCount,

View File

@@ -18,6 +18,7 @@ type Config struct {
IncludeLower bool `json:"includeLower"`
NumberCount int `json:"numberCount"`
SpecialChars string `json:"specialChars"`
MinSpecialChars int `json:"minSpecialChars"`
NoConsecutive bool `json:"noConsecutive"`
UsePassphrase bool `json:"usePassphrase"`
WordCount int `json:"wordCount"`
@@ -232,8 +233,9 @@ func DefaultConfig() Config {
IncludeUpper: true,
IncludeLower: true,
NumberCount: 1,
SpecialChars: "!@#$%^&*-_=+",
NoConsecutive: false,
SpecialChars: "!@#$%&*-_=+.",
MinSpecialChars: 3,
NoConsecutive: true,
UsePassphrase: true, // Default to passphrase
WordCount: 3,
NumberPosition: "end",
@@ -279,6 +281,18 @@ func generateRandomPassword(config Config) (string, error) {
return "", fmt.Errorf("no character types selected")
}
// Validate that minimum special characters doesn't exceed password length
totalRequired := config.NumberCount + config.MinSpecialChars
if config.IncludeLower {
totalRequired++
}
if config.IncludeUpper {
totalRequired++
}
if totalRequired > config.Length {
return "", fmt.Errorf("password length too short for required character counts")
}
password := make([]byte, config.Length)
// Ensure at least one character from each required set
@@ -304,10 +318,50 @@ func generateRandomPassword(config Config) (string, error) {
}
}
// Then place at least one from each other required character set
// Place required special characters
specialCharsPlaced := 0
if config.MinSpecialChars > 0 && len(config.SpecialChars) > 0 {
attempts := 0
maxAttempts := config.Length * 10 // Prevent infinite loops
for specialCharsPlaced < config.MinSpecialChars && attempts < maxAttempts {
pos, err := rand.Int(rand.Reader, big.NewInt(int64(config.Length)))
if err != nil {
return "", err
}
posInt := int(pos.Int64())
if !usedPositions[posInt] {
char, err := rand.Int(rand.Reader, big.NewInt(int64(len(config.SpecialChars))))
if err != nil {
return "", err
}
password[posInt] = config.SpecialChars[char.Int64()]
usedPositions[posInt] = true
specialCharsPlaced++
}
attempts++
}
// If we couldn't place enough special characters due to bad luck, force placement
if specialCharsPlaced < config.MinSpecialChars {
for i := 0; i < config.Length && specialCharsPlaced < config.MinSpecialChars; i++ {
if !usedPositions[i] {
char, err := rand.Int(rand.Reader, big.NewInt(int64(len(config.SpecialChars))))
if err != nil {
return "", err
}
password[i] = config.SpecialChars[char.Int64()]
usedPositions[i] = true
specialCharsPlaced++
}
}
}
}
// Then place at least one from each other required character set (excluding numbers and special chars already handled)
for _, reqSet := range required {
if reqSet == "0123456789" {
continue // Already handled numbers
if reqSet == "0123456789" || reqSet == config.SpecialChars {
continue // Already handled numbers and special chars
}
placed := false
@@ -344,11 +398,60 @@ func generateRandomPassword(config Config) (string, error) {
}
// Handle no consecutive characters requirement
finalPassword := string(password)
if config.NoConsecutive {
return ensureNoConsecutive(string(password), charset)
finalPassword, err := ensureNoConsecutive(string(password), charset)
if err != nil {
return "", err
}
return string(password), nil
// After ensureNoConsecutive, we need to verify minimum requirements are still met
// and fix them if necessary
if config.MinSpecialChars > 0 && len(config.SpecialChars) > 0 {
// Count current special characters
currentSpecialCount := 0
for _, char := range finalPassword {
if strings.ContainsRune(config.SpecialChars, char) {
currentSpecialCount++
}
}
// If we don't have enough special characters, replace some non-special characters
if currentSpecialCount < config.MinSpecialChars {
passwordRunes := []rune(finalPassword)
needed := config.MinSpecialChars - currentSpecialCount
for i := 0; i < len(passwordRunes) && needed > 0; i++ {
// Find non-special characters that can be replaced
if !strings.ContainsRune(config.SpecialChars, passwordRunes[i]) {
// Make sure this replacement won't create consecutive characters
specialChar, err := rand.Int(rand.Reader, big.NewInt(int64(len(config.SpecialChars))))
if err != nil {
return "", err
}
newChar := rune(config.SpecialChars[specialChar.Int64()])
// Check if this would create consecutive characters
wouldCreateConsecutive := false
if i > 0 && passwordRunes[i-1] == newChar {
wouldCreateConsecutive = true
}
if i < len(passwordRunes)-1 && passwordRunes[i+1] == newChar {
wouldCreateConsecutive = true
}
if !wouldCreateConsecutive {
passwordRunes[i] = newChar
needed--
}
}
}
finalPassword = string(passwordRunes)
}
}
return finalPassword, nil
}
return finalPassword, nil
}
func generatePassphrase(config Config) (string, error) {

View File

@@ -25,12 +25,12 @@ type PasswordConfig struct {
IncludeLower bool
NumberCount int
SpecialChars string
MinSpecialChars int
NoConsecutive bool
WordCount int
UseNumbers bool
UseSpecial bool
NumberPosition string
SavePasswords bool
}
func NewHandler(embeddedFiles embed.FS) *Handler {
@@ -59,7 +59,7 @@ func NewHandler(embeddedFiles embed.FS) *Handler {
return 0
}
},
}).ParseFS(embeddedFiles, "web/base.html", "web/password.html"))
}).ParseFS(embeddedFiles, "web/base.html", "web/pwgenerator.html"))
return &Handler{
templates: tmpl,
@@ -72,7 +72,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Generate CSRF token
csrfToken, err := h.csrf.GenerateToken()
if err != nil {
http.Redirect(w, r, "/password?error="+url.QueryEscape("Security token generation failed"), http.StatusSeeOther)
http.Redirect(w, r, "/pwgenerator?error="+url.QueryEscape("Security token generation failed"), http.StatusSeeOther)
return
}
@@ -82,14 +82,14 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Length: getIntParam(r, "length", 12),
IncludeUpper: getBoolParam(r, "includeUpper", true),
IncludeLower: getBoolParam(r, "includeLower", true),
NumberCount: getIntParam(r, "numberCount", 1),
SpecialChars: getStringParam(r, "specialChars", "!@#$%^&*-_=+"),
NoConsecutive: getBoolParam(r, "noConsecutive", false),
NumberCount: getIntParam(r, "numberCount", 2),
SpecialChars: getStringParam(r, "specialChars", "!@#$%&*-_=+."),
MinSpecialChars: getIntParam(r, "minSpecialChars", 3),
NoConsecutive: getBoolParam(r, "noConsecutive", true),
WordCount: getIntParam(r, "wordCount", 3),
UseNumbers: getBoolParam(r, "useNumbers", true),
UseSpecial: getBoolParam(r, "useSpecial", false),
NumberPosition: getStringParam(r, "numberPosition", "end"),
SavePasswords: getBoolParam(r, "savePasswords", false),
}
data := struct {
@@ -101,7 +101,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Config: config,
CSRFToken: csrfToken,
}
h.templates.ExecuteTemplate(w, "password.html", data)
h.templates.ExecuteTemplate(w, "pwgenerator.html", data)
}
// Helper functions to parse URL parameters

View File

@@ -198,8 +198,6 @@ func initDatabase() (*sql.DB, error) {
}
func loadPushTemplates(embeddedFS fs.FS) (*template.Template, error) {
log.Printf("Loading PWPusher push templates...")
templates := template.New("").Funcs(template.FuncMap{
"formatTime": func(t time.Time) string {
return t.Format("2006-01-02 15:04:05")
@@ -214,14 +212,10 @@ func loadPushTemplates(embeddedFS fs.FS) (*template.Template, error) {
if err != nil {
return nil, fmt.Errorf("failed to parse push templates: %v", err)
}
log.Printf("PWPusher push templates loaded successfully")
return templates, nil
}
func loadViewTemplates(embeddedFS fs.FS) (*template.Template, error) {
log.Printf("Loading PWPusher view templates...")
templates := template.New("").Funcs(template.FuncMap{
"formatTime": func(t time.Time) string {
return t.Format("2006-01-02 15:04:05")
@@ -257,8 +251,6 @@ func loadViewTemplates(embeddedFS fs.FS) (*template.Template, error) {
if err != nil {
return nil, fmt.Errorf("failed to parse view templates: %v", err)
}
log.Printf("PWPusher view templates loaded successfully")
return templates, nil
}
@@ -442,9 +434,6 @@ func (p *PWPusher) recordFailedAttempt(clientIP string) {
}
}
// Log for debugging
log.Printf("Failed attempt recorded for IP %s: Count=%d, BlockedUntil=%v",
clientIP, p.failedAttempts[clientIP].Count, p.failedAttempts[clientIP].BlockedUntil)
}
func (p *PWPusher) resetFailedAttempts(clientIP string) {
@@ -468,7 +457,6 @@ func (p *PWPusher) getBlockedUntil(clientIP string) time.Time {
// HTTP Handlers
func (p *PWPusher) IndexHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("PWPusher IndexHandler called: %s %s", r.Method, r.URL.Path)
if r.Method == http.MethodPost {
p.handleCreatePush(w, r)
@@ -478,7 +466,6 @@ func (p *PWPusher) IndexHandler(w http.ResponseWriter, r *http.Request) {
// For GET requests, render the form with CurrentPage
csrfToken, err := p.generateCSRFToken()
if err != nil {
log.Printf("Error generating CSRF token: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
@@ -495,7 +482,6 @@ func (p *PWPusher) IndexHandler(w http.ResponseWriter, r *http.Request) {
Success: false,
CSRFToken: csrfToken,
}
log.Printf("Rendering pwpush.html template with data: %+v", data)
p.renderTemplate(w, "pwpush.html", data)
}
@@ -522,7 +508,8 @@ func (p *PWPusher) handleCreatePush(w http.ResponseWriter, r *http.Request) {
// Validate CSRF token for form submissions
csrfToken := r.FormValue("csrf_token")
if !p.validateCSRFToken(csrfToken) {
http.Error(w, "Invalid CSRF token", http.StatusForbidden)
w.Header().Set("Location", "/pwpusher?error="+url.QueryEscape("Invalid CSRF token"))
http.Redirect(w, r, "/pwpusher?error="+url.QueryEscape("Invalid CSRF token"), http.StatusSeeOther)
return
}
@@ -549,23 +536,30 @@ func (p *PWPusher) handleCreatePush(w http.ResponseWriter, r *http.Request) {
// Comprehensive input validation
sanitizedText, err := p.validator.ValidateAndSanitizeText(req.Text, 100000)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
w.Header().Set("Location", "/pwpusher?error="+url.QueryEscape(err.Error()))
http.Redirect(w, r, "/pwpusher?error="+url.QueryEscape(err.Error()), http.StatusSeeOther)
return
}
req.Text = sanitizedText
// Only validate password if one is provided (passwords are optional)
if req.Password != "" {
if err := p.validator.ValidatePassword(req.Password); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
w.Header().Set("Location", "/pwpusher?error="+url.QueryEscape(err.Error()))
http.Redirect(w, r, "/pwpusher?error="+url.QueryEscape(err.Error()), http.StatusSeeOther)
return
}
}
if err := p.validator.ValidateIntRange(req.ExpiryDays, MinExpiryDays, MaxExpiryDays, "expiry days"); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
w.Header().Set("Location", "/pwpusher?error="+url.QueryEscape(err.Error()))
http.Redirect(w, r, "/pwpusher?error="+url.QueryEscape(err.Error()), http.StatusSeeOther)
return
}
if err := p.validator.ValidateIntRange(req.MaxViews, MinViews, MaxViews, "max views"); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
w.Header().Set("Location", "/pwpusher?error="+url.QueryEscape(err.Error()))
http.Redirect(w, r, "/pwpusher?error="+url.QueryEscape(err.Error()), http.StatusSeeOther)
return
}
@@ -575,7 +569,6 @@ func (p *PWPusher) handleCreatePush(w http.ResponseWriter, r *http.Request) {
// Encrypt text with double encryption
encryptedText, err := p.encryptWithKey(req.Text, additionalKey)
if err != nil {
log.Printf("Encryption error: %v", err)
http.Error(w, "Failed to encrypt text", http.StatusInternalServerError)
return
}
@@ -591,7 +584,6 @@ func (p *PWPusher) handleCreatePush(w http.ResponseWriter, r *http.Request) {
if req.Password != "" {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
if err != nil {
log.Printf("Failed to hash password: %v", err)
http.Error(w, "Failed to process password", http.StatusInternalServerError)
return
}
@@ -687,8 +679,6 @@ func (p *PWPusher) ViewHandler(w http.ResponseWriter, r *http.Request) {
return
}
log.Printf("ViewHandler: Extracted ID '%s' from URL '%s'", id, r.URL.Path)
// Handle POST requests (reveal actions and password verification)
if r.Method == http.MethodPost {
r.ParseForm()
@@ -827,7 +817,6 @@ func (p *PWPusher) ViewHandler(w http.ResponseWriter, r *http.Request) {
}
// Password is correct - reset failed attempts and show content directly
log.Printf("Correct password for IP %s, resetting attempts", clientIP)
p.resetFailedAttempts(clientIP)
// Decrypt text with the encryption key
@@ -1190,8 +1179,6 @@ func (p *PWPusher) setHistoryCookies(w http.ResponseWriter, history []map[string
func (p *PWPusher) renderTemplate(w http.ResponseWriter, templateName string, data interface{}) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
log.Printf("Attempting to render template: %s", templateName)
// Auto-add CSRF token for ViewData structs
if viewData, ok := data.(*ViewData); ok && viewData.CSRFToken == "" {
if err := p.addCSRFToken(viewData); err != nil {
@@ -1220,15 +1207,11 @@ func (p *PWPusher) renderTemplate(w http.ResponseWriter, templateName string, da
// Check if template exists
if templates.Lookup(templateName) == nil {
log.Printf("Template %s not found, using basic page", templateName)
p.renderBasicPage(w, templateName, data)
return
}
log.Printf("Template %s found, executing with data: %+v", templateName, data)
// Execute the specific template directly
if err := templates.ExecuteTemplate(w, templateName, data); err != nil {
log.Printf("Template error: %v", err)
// Fallback to basic HTML
w.Write([]byte(`<!DOCTYPE html><html><head><title>PWPusher</title></head><body>
<h1>PWPusher - Template Error</h1>

View File

@@ -1,108 +1,240 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" class="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{block "title" .}}HeaderAnalyzer{{end}}</title>
<link rel="stylesheet" href="/static/style.css">
<title>{{block "title" .}}{{end}}</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="/style.css">
<link rel="icon" href="/favicon.ico" type="image/x-icon">
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
colors: {
dark: {
bg: '#1e1e1e',
surface: '#2d2d2d',
border: '#404040',
text: '#e0e0e0',
muted: '#999999'
}
}
}
}
}
</script>
<style>
/* Popup notification styles */
.popup-notification {
position: fixed;
top: 20px;
right: 20px;
z-index: 10000;
background: #f44336;
color: white;
padding: 15px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
max-width: 400px;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
transform: translateX(100%);
transition: transform 0.3s ease-in-out;
/* Custom animations and styles */
@keyframes slideInRight {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.popup-notification.show {
transform: translateX(0);
@keyframes slideOutRight {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(100%); opacity: 0; }
}
.popup-notification.error {
background: #f44336;
.animate-slide-in-right {
animation: slideInRight 0.3s ease-out forwards;
}
.popup-notification.warning {
background: #ff9800;
.animate-slide-out-right {
animation: slideOutRight 0.3s ease-in forwards;
}
.popup-notification.success {
background: #4caf50;
/* Backdrop blur for popup */
.backdrop-blur-popup {
backdrop-filter: blur(8px);
}
.popup-notification.info {
background: #2196f3;
}
.popup-notification .close-btn {
float: right;
background: none;
border: none;
color: white;
font-size: 18px;
font-weight: bold;
cursor: pointer;
margin-left: 10px;
padding: 0;
line-height: 1;
}
.popup-notification .close-btn:hover {
opacity: 0.7;
/* Floating button styles */
.floating-nav {
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
</style>
{{block "head" .}}{{end}}
</head>
<body>
<nav>
<a href="/" {{if eq .CurrentPage "home"}}class="active"{{end}}>Analyze New Header</a>
<a href="/dns" {{if eq .CurrentPage "dns"}}class="active"{{end}}>DNS Tools</a>
<a href="/password" {{if eq .CurrentPage "password"}}class="active"{{end}}>Password Generator</a>
<a href="/pwpush" {{if eq .CurrentPage "pwpush"}}class="active"{{end}}>Password Pusher</a>
</nav>
<body class="bg-dark-bg text-dark-text min-h-screen">
<!-- Floating Navigation -->
<div class="fixed top-4 right-4 z-50 floating-nav">
<div class="flex bg-dark-surface border border-dark-border rounded-full overflow-hidden">
<!-- Home Button -->
<a href="/" class="flex items-center justify-center w-12 h-12 bg-blue-600 hover:bg-blue-700 transition-colors duration-200 text-white">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"/>
</svg>
</a>
<main>
<!-- Menu Button -->
<button id="menuButton" class="flex items-center justify-center w-12 h-12 bg-dark-surface hover:bg-gray-700 transition-colors duration-200 text-dark-text">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
</svg>
</button>
</div>
</div>
<!-- Navigation Popup -->
<div id="navigationPopup" class="fixed inset-0 z-40 hidden">
<!-- Backdrop -->
<div class="absolute inset-0 bg-black bg-opacity-50 backdrop-blur-popup" onclick="closeNavigationPopup()"></div>
<!-- Popup Content -->
<div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-dark-surface border border-dark-border rounded-xl p-6 max-w-md w-full mx-4 shadow-2xl">
<div class="flex items-center justify-between mb-6">
<h2 class="text-xl font-bold text-dark-text">Navigation</h2>
<button onclick="closeNavigationPopup()" class="text-dark-muted hover:text-dark-text transition-colors">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<!-- IT Tools Category -->
<div class="mb-6">
<h3 class="text-sm font-semibold text-blue-400 uppercase tracking-wide mb-3">IT Tools</h3>
<div class="space-y-2">
<a href="/analyze" class="flex items-center p-3 rounded-lg hover:bg-dark-bg transition-colors duration-200 group">
<div class="flex items-center justify-center w-10 h-10 bg-green-600 rounded-lg mr-3 group-hover:bg-green-700 transition-colors">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z"/>
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z"/>
</svg>
</div>
<div>
<div class="text-dark-text font-medium">Email Header Analyzer</div>
<div class="text-dark-muted text-sm">Analyze email headers for security</div>
</div>
</a>
<a href="/dns" class="flex items-center p-3 rounded-lg hover:bg-dark-bg transition-colors duration-200 group">
<div class="flex items-center justify-center w-10 h-10 bg-purple-600 rounded-lg mr-3 group-hover:bg-purple-700 transition-colors">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M3 3a1 1 0 000 2v8a2 2 0 002 2h2.586l-1.293 1.293a1 1 0 101.414 1.414L10 15.414l2.293 2.293a1 1 0 001.414-1.414L12.414 15H15a2 2 0 002-2V5a1 1 0 100-2H3zm11.707 4.707a1 1 0 00-1.414-1.414L10 9.586 8.707 8.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
</svg>
</div>
<div>
<div class="text-dark-text font-medium">DNS Tools</div>
<div class="text-dark-muted text-sm">DNS lookups and network diagnostics</div>
</div>
</a>
</div>
</div>
<!-- Online Security Category -->
<div>
<h3 class="text-sm font-semibold text-orange-400 uppercase tracking-wide mb-3">Online Security</h3>
<div class="space-y-2">
<a href="/pwgenerator" class="flex items-center p-3 rounded-lg hover:bg-dark-bg transition-colors duration-200 group">
<div class="flex items-center justify-center w-10 h-10 bg-yellow-600 rounded-lg mr-3 group-hover:bg-yellow-700 transition-colors">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 8a6 6 0 01-7.743 5.743L10 14l-0.257-.257A6 6 0 1118 8zm-1.5 0a4.5 4.5 0 11-9 0 4.5 4.5 0 019 0zM10 7a1 1 0 100 2 1 1 0 000-2z" clip-rule="evenodd"/>
</svg>
</div>
<div>
<div class="text-dark-text font-medium">Password Generator</div>
<div class="text-dark-muted text-sm">Generate secure passwords</div>
</div>
</a>
<a href="/pwpush" class="flex items-center p-3 rounded-lg hover:bg-dark-bg transition-colors duration-200 group">
<div class="flex items-center justify-center w-10 h-10 bg-red-600 rounded-lg mr-3 group-hover:bg-red-700 transition-colors">
<svg class="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"/>
</svg>
</div>
<div>
<div class="text-dark-text font-medium">Password Pusher</div>
<div class="text-dark-muted text-sm">Share sensitive text securely</div>
</div>
</a>
</div>
</div>
</div>
</div>
<main class="container mx-auto px-4 py-6 pt-8">
{{block "content" .}}
<div class="container">
<h1>HeaderAnalyzer</h1>
<p>Welcome to HeaderAnalyzer - your tool for email header analysis, DNS tools, and password generation.</p>
<div class="text-center">
<h1 class="text-4xl font-bold mb-4">HeaderAnalyzer</h1>
<p class="text-dark-muted text-lg">Welcome to HeaderAnalyzer - your comprehensive toolkit for IT and security tools.</p>
</div>
{{end}}
</main>
<!-- Popup notification container -->
<div id="popup-container"></div>
<div id="popup-container" class="fixed top-4 left-4 z-50 space-y-2"></div>
<script>
// Popup notification system
// Navigation popup functionality
function openNavigationPopup() {
const popup = document.getElementById('navigationPopup');
popup.classList.remove('hidden');
}
function closeNavigationPopup() {
const popup = document.getElementById('navigationPopup');
popup.classList.add('hidden');
}
// Menu button click handler
document.addEventListener('DOMContentLoaded', function() {
const menuButton = document.getElementById('menuButton');
menuButton.addEventListener('click', openNavigationPopup);
});
// Popup notification system with Tailwind classes
function showPopup(message, type = 'error', duration = 5000) {
const container = document.getElementById('popup-container');
const popup = document.createElement('div');
popup.className = `popup-notification ${type}`;
let bgClass = 'bg-red-600';
let iconSvg = `<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
</svg>`;
switch(type) {
case 'success':
bgClass = 'bg-green-600';
iconSvg = `<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
</svg>`;
break;
case 'warning':
bgClass = 'bg-yellow-600';
iconSvg = `<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
</svg>`;
break;
case 'info':
bgClass = 'bg-blue-600';
iconSvg = `<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
</svg>`;
break;
}
popup.className = `${bgClass} text-white p-4 rounded-lg shadow-lg max-w-sm w-full animate-slide-in-right flex items-start space-x-3`;
popup.innerHTML = `
<button class="close-btn" onclick="this.parentElement.remove()">&times;</button>
${message}
<div class="flex-shrink-0">${iconSvg}</div>
<div class="flex-grow">${message}</div>
<button class="flex-shrink-0 text-white hover:text-gray-200 transition-colors" onclick="this.parentElement.remove()">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<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>
`;
container.appendChild(popup);
// Trigger animation
setTimeout(() => popup.classList.add('show'), 10);
// Auto-remove after duration
if (duration > 0) {
setTimeout(() => {
popup.classList.remove('show');
popup.classList.remove('animate-slide-in-right');
popup.classList.add('animate-slide-out-right');
setTimeout(() => popup.remove(), 300);
}, duration);
}
@@ -118,7 +250,6 @@
if (error) {
showPopup(decodeURIComponent(error), 'error');
// Clean URL
urlParams.delete('error');
window.history.replaceState({}, '', `${window.location.pathname}${urlParams.toString() ? '?' + urlParams.toString() : ''}`);
}

View File

@@ -2,25 +2,21 @@
{{define "title"}}DNS Tools - HeaderAnalyzer{{end}}
{{define "head"}}
<style>
.dns-tools-container { max-width: 900px; margin: 0 auto; }
.dns-query-form { display: flex; gap: 10px; margin-bottom: 10px; }
.dns-query-form input, .dns-query-form select { padding: 7px; border-radius: 4px; border: 1px solid #444; background: #232323; color: #e0e0e0; }
.dns-query-form button { padding: 7px 16px; }
.dns-results { margin-top: 10px; }
.dns-result-block { background: #232323; border-radius: 6px; margin-bottom: 12px; padding: 12px; box-shadow: 0 2px 4px rgba(0,0,0,0.12); }
.dns-result-block pre { white-space: pre-wrap; word-break: break-word; font-size: 1em; }
.save-btns { margin-bottom: 10px; }
</style>
{{end}}
{{define "content"}}
<div class="dns-tools-container">
<h1>DNS Tools</h1>
<form class="dns-query-form" id="dnsForm" onsubmit="return false;">
<input type="text" id="dnsInput" placeholder="Enter domain or IP" required>
<select id="dnsType">
<div class="container mx-auto px-4 py-8 max-w-6xl">
<div class="bg-gray-800 rounded-xl p-8 border border-gray-700 mb-8">
<a href="/dns" class="inline-block">
<h1 class="text-2xl md:text-3xl font-bold text-gray-100 hover:text-blue-400 transition-colors cursor-pointer mb-4">
🌐 DNS Tools
</h1>
</a>
<form class="flex flex-wrap gap-3 items-end mb-6" id="dnsForm" onsubmit="return false;">
<div class="flex-1 min-w-64">
<input type="text" id="dnsInput" placeholder="Enter domain or IP" required
class="w-full px-4 py-3 bg-gray-900 border border-gray-600 rounded-lg text-gray-100 placeholder-gray-400 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 focus:outline-none">
</div>
<div>
<select id="dnsType" class="px-4 py-3 bg-gray-900 border border-gray-600 rounded-lg text-gray-100 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 focus:outline-none">
<option value="A">A</option>
<option value="AAAA">AAAA</option>
<option value="MX">MX</option>
@@ -34,27 +30,141 @@
<option value="DMARC">DMARC</option>
<option value="WHOIS">WHOIS</option>
</select>
<input type="text" id="dnsServer" placeholder="Custom DNS server (optional)" style="width:180px;" autocomplete="off">
<button type="submit">Query</button>
</form>
<div class="save-btns">
<button onclick="saveResults('csv')">Save as CSV</button>
<button onclick="saveResults('txt')">Save as TXT</button>
</div>
<div class="dns-results" id="dnsResults"></div>
<div class="min-w-48">
<input type="text" id="dnsServer" placeholder="Custom DNS server (optional)"
class="w-full px-4 py-3 bg-gray-900 border border-gray-600 rounded-lg text-gray-100 placeholder-gray-400 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 focus:outline-none">
</div>
<button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white font-medium px-6 py-3 rounded-lg transition-colors flex items-center space-x-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
<span>Query</span>
</button>
</form>
<div class="flex gap-3">
<button onclick="saveResults('csv')" class="bg-green-600 hover:bg-green-700 text-white font-medium px-4 py-2 rounded-lg transition-colors flex items-center space-x-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
<span>Save as CSV</span>
</button>
<button onclick="saveResults('txt')" class="bg-green-600 hover:bg-green-700 text-white font-medium px-4 py-2 rounded-lg transition-colors flex items-center space-x-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
<span>Save as TXT</span>
</button>
</div>
</div>
<div class="space-y-4" id="dnsResults"></div>
</div>
<style>
.popup-notification {
background-color: rgb(31 41 55);
border: 1px solid rgb(75 85 99);
color: rgb(243 244 246);
padding: 1rem;
border-radius: 0.5rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
max-width: 24rem;
position: relative;
transform: translateX(100%);
opacity: 0;
transition: all 0.3s ease;
}
.popup-notification.show {
transform: translateX(0);
opacity: 1;
}
.popup-notification.error {
border-color: rgb(239 68 68);
background-color: rgba(127 29 29, 0.5);
}
.popup-notification.success {
border-color: rgb(34 197 94);
background-color: rgba(20 83 45, 0.5);
}
.popup-notification.warning {
border-color: rgb(234 179 8);
background-color: rgba(133 77 14, 0.5);
}
.popup-notification .close-btn {
position: absolute;
top: 0.5rem;
right: 0.5rem;
color: rgb(156 163 175);
width: 1.5rem;
height: 1.5rem;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.125rem;
line-height: 1;
cursor: pointer;
border: none;
background: none;
}
.popup-notification .close-btn:hover {
color: rgb(229 231 235);
}
</style>
<!-- Popup notification container -->
<div id="popup-container" class="fixed top-4 right-4 z-50 space-y-2"></div>
{{end}}
{{define "scripts"}}
<script>
let results = [];
// Popup notification system
function showPopup(message, type = 'error', duration = 5000) {
const container = document.getElementById('popup-container');
const popup = document.createElement('div');
popup.className = `popup-notification ${type}`;
popup.innerHTML = `
<button class="close-btn" onclick="this.parentElement.remove()">&times;</button>
<div class="flex items-start gap-2">
<div class="flex-shrink-0 mt-0.5">
${type === 'error' ? '❌' : type === 'success' ? '✅' : type === 'warning' ? '⚠️' : ''}
</div>
<div class="flex-1">${message}</div>
</div>
`;
container.appendChild(popup);
// Trigger animation
setTimeout(() => popup.classList.add('show'), 10);
// Auto-remove after duration
if (duration > 0) {
setTimeout(() => {
popup.classList.remove('show');
setTimeout(() => popup.remove(), 300);
}, duration);
}
}
document.getElementById('dnsForm').addEventListener('submit', function() {
const query = document.getElementById('dnsInput').value.trim();
const type = document.getElementById('dnsType').value;
const server = document.getElementById('dnsServer').value.trim();
if (!query) return;
if (!query) {
showPopup('Please enter a domain or IP address to query', 'warning');
return;
}
let url = `/api/dns?query=${encodeURIComponent(query)}&type=${encodeURIComponent(type)}`;
if (server) {
@@ -69,11 +179,31 @@ document.getElementById('dnsForm').addEventListener('submit', function() {
if (server) {
url += `&server=${encodeURIComponent(server)}`;
}
} else {
showPopup('DKIM selector is required for DKIM queries', 'warning');
return;
}
}
// Show loading state
const submitBtn = document.querySelector('button[type="submit"]');
const originalText = submitBtn.innerHTML;
submitBtn.innerHTML = `
<svg class="animate-spin w-4 h-4" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="m4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Querying...
`;
submitBtn.disabled = true;
fetch(url)
.then(response => response.text())
.then(response => {
if (!response.ok) {
throw new Error(`Server error: ${response.status}`);
}
return response.text();
})
.then(data => {
const timestamp = new Date().toLocaleString();
const result = {
@@ -86,35 +216,45 @@ document.getElementById('dnsForm').addEventListener('submit', function() {
results.push(result);
const resultDiv = document.createElement('div');
resultDiv.className = 'dns-result-block';
resultDiv.className = 'bg-gray-800 border border-gray-700 rounded-lg p-6 mb-4';
resultDiv.innerHTML = `
<h3>${type} query for ${query}</h3>
<p><small>Time: ${timestamp} | Server: ${server || 'Default'}</small></p>
<pre>${data}</pre>
<h3 class="text-blue-400 font-semibold mb-2">${type} query for ${query}</h3>
<p class="text-gray-400 text-sm mb-3">Time: ${timestamp} | Server: ${server || 'Default'}</p>
<pre class="bg-gray-900 text-gray-100 p-3 rounded-lg overflow-x-auto text-sm border border-gray-700">${data}</pre>
`;
document.getElementById('dnsResults').insertBefore(resultDiv, document.getElementById('dnsResults').firstChild);
showPopup(`Successfully queried ${type} record for ${query}`, 'success', 3000);
})
.catch(error => {
console.error('Error:', error);
console.error('DNS Query Error:', error);
const resultDiv = document.createElement('div');
resultDiv.className = 'dns-result-block';
resultDiv.className = 'bg-gray-800 border border-red-500/50 rounded-lg p-6 mb-4';
resultDiv.innerHTML = `
<h3>Error querying ${query}</h3>
<pre>Error: ${error.message}</pre>
<h3 class="text-red-400 font-semibold mb-2">❌ Error querying ${query}</h3>
<pre class="bg-gray-900 text-red-300 p-3 rounded-lg text-sm border border-gray-700">Error: ${error.message}</pre>
`;
document.getElementById('dnsResults').insertBefore(resultDiv, document.getElementById('dnsResults').firstChild);
showPopup(`Failed to query ${query}: ${error.message}`, 'error');
})
.finally(() => {
// Restore button
submitBtn.innerHTML = originalText;
submitBtn.disabled = false;
});
});
function saveResults(format) {
if (results.length === 0) {
alert('No results to save');
showPopup('No results to save. Please run some DNS queries first.', 'warning');
return;
}
let content = '';
let filename = '';
try {
if (format === 'csv') {
content = 'Timestamp,Query,Type,Server,Result\n';
results.forEach(r => {
@@ -139,6 +279,11 @@ function saveResults(format) {
a.download = filename;
a.click();
URL.revokeObjectURL(url);
showPopup(`Successfully exported ${results.length} results as ${format.toUpperCase()}`, 'success', 3000);
} catch (error) {
showPopup(`Failed to export results: ${error.message}`, 'error');
}
}
</script>
{{end}}

224
web/dns.html.old Normal file
View File

@@ -0,0 +1,224 @@
{{template "base.html" .}}
{{define "title"}}DNS Tools - HeaderAnalyzer{{end}}
{{define "content"}}
<div class="dns-tools-container">
<div class="bg-dark-bg rounded-xl p-8 border border-dark-border">
<h1 class="text-3xl font-bold text-dark-text mb-2">🌐 DNS Tools</h1>
<p class="text-dark-muted mb-6">Comprehensive DNS lookup and analysis tools for domains and IP addresses</p>
<form class="dns-query-form" id="dnsForm" onsubmit="return false;">
<input type="text" id="dnsInput" placeholder="Enter domain or IP" required
class="flex-1 min-w-48 px-4 py-3 bg-dark-bg border border-dark-border rounded-lg text-dark-text placeholder-dark-muted focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20">
<select id="dnsType" class="px-4 py-3 bg-dark-bg border border-dark-border rounded-lg text-dark-text focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20">
<option value="A">A</option>
<option value="AAAA">AAAA</option>
<option value="MX">MX</option>
<option value="TXT">TXT</option>
<option value="NS">NS</option>
<option value="CNAME">CNAME</option>
<option value="PTR">PTR</option>
<option value="SOA">SOA</option>
<option value="SPF">SPF</option>
<option value="DKIM">DKIM</option>
<option value="DMARC">DMARC</option>
<option value="WHOIS">WHOIS</option>
</select>
<input type="text" id="dnsServer" placeholder="Custom DNS server (optional)"
class="w-48 px-4 py-3 bg-dark-bg border border-dark-border rounded-lg text-dark-text placeholder-dark-muted focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20">
<button type="submit" class="btn btn-primary">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
Query
</button>
</form>
<div class="save-btns">
<button onclick="saveResults('csv')" class="btn btn-success">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
Save as CSV
</button>
<button onclick="saveResults('txt')" class="btn btn-success">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg>
Save as TXT
</button>
</div>
</div>
<div class="dns-results" id="dnsResults"></div>
</div>
<!-- Popup notification container -->
<div id="popup-container" class="fixed top-4 right-4 z-50 space-y-2"></div>
{{end}}
{{define "scripts"}}
<script>
let results = [];
// Popup notification system
function showPopup(message, type = 'error', duration = 5000) {
const container = document.getElementById('popup-container');
const popup = document.createElement('div');
popup.className = `popup-notification ${type}`;
popup.innerHTML = `
<button class="close-btn" onclick="this.parentElement.remove()">&times;</button>
<div class="flex items-start gap-2">
<div class="flex-shrink-0 mt-0.5">
${type === 'error' ? '❌' : type === 'success' ? '✅' : type === 'warning' ? '⚠️' : ''}
</div>
<div class="flex-1">${message}</div>
</div>
`;
container.appendChild(popup);
// Trigger animation
setTimeout(() => popup.classList.add('show'), 10);
// Auto-remove after duration
if (duration > 0) {
setTimeout(() => {
popup.classList.remove('show');
setTimeout(() => popup.remove(), 300);
}, duration);
}
}
document.getElementById('dnsForm').addEventListener('submit', function() {
const query = document.getElementById('dnsInput').value.trim();
const type = document.getElementById('dnsType').value;
const server = document.getElementById('dnsServer').value.trim();
if (!query) {
showPopup('Please enter a domain or IP address to query', 'warning');
return;
}
let url = `/api/dns?query=${encodeURIComponent(query)}&type=${encodeURIComponent(type)}`;
if (server) {
url += `&server=${encodeURIComponent(server)}`;
}
// Add selector field for DKIM queries
if (type === 'DKIM' && !query.includes(':')) {
const selector = prompt('Enter DKIM selector (e.g., "selector1", "default"):');
if (selector) {
url = `/api/dns?query=${encodeURIComponent(query + ':' + selector)}&type=${encodeURIComponent(type)}`;
if (server) {
url += `&server=${encodeURIComponent(server)}`;
}
} else {
showPopup('DKIM selector is required for DKIM queries', 'warning');
return;
}
}
// Show loading state
const submitBtn = document.querySelector('button[type="submit"]');
const originalText = submitBtn.innerHTML;
submitBtn.innerHTML = `
<svg class="animate-spin w-4 h-4" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="m4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Querying...
`;
submitBtn.disabled = true;
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(`Server error: ${response.status}`);
}
return response.text();
})
.then(data => {
const timestamp = new Date().toLocaleString();
const result = {
timestamp: timestamp,
query: query,
type: type,
server: server || 'Default',
result: data
};
results.push(result);
const resultDiv = document.createElement('div');
resultDiv.className = 'dns-result-block';
resultDiv.innerHTML = `
<h3 class="text-blue-400 font-semibold mb-2">${type} query for ${query}</h3>
<p class="text-dark-muted text-sm mb-3">Time: ${timestamp} | Server: ${server || 'Default'}</p>
<pre class="bg-gray-900 text-dark-text p-3 rounded-lg overflow-x-auto text-sm">${data}</pre>
`;
document.getElementById('dnsResults').insertBefore(resultDiv, document.getElementById('dnsResults').firstChild);
showPopup(`Successfully queried ${type} record for ${query}`, 'success', 3000);
})
.catch(error => {
console.error('DNS Query Error:', error);
const resultDiv = document.createElement('div');
resultDiv.className = 'dns-result-block border-red-500/50';
resultDiv.innerHTML = `
<h3 class="text-red-400 font-semibold mb-2">❌ Error querying ${query}</h3>
<pre class="bg-gray-900 text-red-300 p-3 rounded-lg text-sm">Error: ${error.message}</pre>
`;
document.getElementById('dnsResults').insertBefore(resultDiv, document.getElementById('dnsResults').firstChild);
showPopup(`Failed to query ${query}: ${error.message}`, 'error');
})
.finally(() => {
// Restore button
submitBtn.innerHTML = originalText;
submitBtn.disabled = false;
});
});
function saveResults(format) {
if (results.length === 0) {
showPopup('No results to save. Please run some DNS queries first.', 'warning');
return;
}
let content = '';
let filename = '';
try {
if (format === 'csv') {
content = 'Timestamp,Query,Type,Server,Result\n';
results.forEach(r => {
const escapedResult = '"' + r.result.replace(/"/g, '""') + '"';
content += `"${r.timestamp}","${r.query}","${r.type}","${r.server}",${escapedResult}\n`;
});
filename = 'dns-results.csv';
} else if (format === 'txt') {
results.forEach(r => {
content += `=== ${r.type} query for ${r.query} ===\n`;
content += `Time: ${r.timestamp}\n`;
content += `Server: ${r.server}\n`;
content += `Result:\n${r.result}\n\n`;
});
filename = 'dns-results.txt';
}
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
showPopup(`Successfully exported ${results.length} results as ${format.toUpperCase()}`, 'success', 3000);
} catch (error) {
showPopup(`Failed to export results: ${error.message}`, 'error');
}
}
</script>
{{end}}

View File

@@ -9,312 +9,547 @@
{{end}}
{{define "content"}}
<div class="container">
<h1>Email Header Analyzer</h1>
<div class="container mx-auto px-4 py-8 max-w-6xl">
<div class="text-center mb-8">
<a href="/analyze" class="inline-block">
<h1 class="text-2xl md:text-3xl font-bold text-gray-100 hover:text-blue-400 transition-colors cursor-pointer mb-4">
📧 Email Header Analyzer
</h1>
</a>
</div>
{{if not .From}}
<form method="POST">
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700 max-w-4xl mx-auto">
<form method="POST" class="space-y-4">
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
<textarea name="headers" placeholder="Paste email headers here..." required></textarea>
<br>
<button type="submit">Analyze Headers</button>
<div>
<label class="block text-sm font-medium text-gray-200 mb-2">📝 Email Headers:</label>
<textarea name="headers"
placeholder="Paste email headers here..."
required
rows="12"
class="w-full p-4 bg-gray-900 border border-gray-600 rounded-lg text-gray-100 font-mono text-sm placeholder-gray-400 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 focus:outline-none resize-y"></textarea>
</div>
<div class="text-center">
<button type="submit" class="bg-blue-600 hover:bg-blue-700 text-white font-medium px-8 py-3 rounded-lg transition-colors">
🔍 Analyze Headers
</button>
</div>
</form>
</div>
{{end}}
{{if .From}}
<div id="report" class="container">
<div class="section" style="display: flex; align-items: flex-start; justify-content: space-between; gap: 30px;">
<div style="flex: 1 1 0; min-width: 0;">
<h2>Sender Identification</h2>
<div class="grid">
<div id="report" class="space-y-6">
<!-- Sender Identification -->
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
<h2 class="text-2xl font-bold text-gray-100 mb-4">👤 Sender Identification</h2>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="space-y-3">
<div>
<p><b>Envelope Sender (Return-Path):</b> {{.EnvelopeSender}}</p>
<p><b>From Domain:</b> {{.FromDomain}}</p>
<p><b>Sending Server:</b> {{.SendingServer}}</p>
<span class="text-sm font-medium text-gray-400">Envelope Sender (Return-Path):</span>
<p class="text-gray-100 font-mono text-sm bg-gray-900 p-2 rounded">{{.EnvelopeSender}}</p>
</div>
<div class="score-indicators">
<span class="status {{if .SPFPass}}good{{else}}error{{end}}" title="SPF">SPF {{if .SPFPass}}✓{{else}}✗{{end}}</span>
<span class="status {{if .DMARCPass}}good{{else}}error{{end}}" title="DMARC">DMARC {{if .DMARCPass}}✓{{else}}✗{{end}}</span>
<span class="status {{if .DKIMPass}}good{{else}}error{{end}}" title="DKIM">DKIM {{if .DKIMPass}}✓{{else}}✗{{end}}</span>
<span class="status {{if .Encrypted}}good{{else}}error{{end}}" title="Encrypted">Encrypted {{if .Encrypted}}✓{{else}}✗{{end}}</span>
<div>
<span class="text-sm font-medium text-gray-400">From Domain:</span>
<p class="text-gray-100 font-mono text-sm bg-gray-900 p-2 rounded">{{.FromDomain}}</p>
</div>
<div>
<span class="text-sm font-medium text-gray-400">Sending Server:</span>
<p class="text-gray-100 font-mono text-sm bg-gray-900 p-2 rounded">{{.SendingServer}}</p>
</div>
</div>
<div class="space-y-3">
<h3 class="text-lg font-semibold text-gray-200 mb-3">🔒 Security Status</h3>
<div class="flex flex-wrap gap-2">
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium {{if .SPFPass}}bg-green-900 text-green-200 border border-green-600{{else}}bg-red-900 text-red-200 border border-red-600{{end}}">
SPF {{if .SPFPass}}✓{{else}}✗{{end}}
</span>
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium {{if .DMARCPass}}bg-green-900 text-green-200 border border-green-600{{else}}bg-red-900 text-red-200 border border-red-600{{end}}">
DMARC {{if .DMARCPass}}✓{{else}}✗{{end}}
</span>
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium {{if .DKIMPass}}bg-green-900 text-green-200 border border-green-600{{else}}bg-red-900 text-red-200 border border-red-600{{end}}">
DKIM {{if .DKIMPass}}✓{{else}}✗{{end}}
</span>
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium {{if .Encrypted}}bg-green-900 text-green-200 border border-green-600{{else}}bg-red-900 text-red-200 border border-red-600{{end}}">
Encrypted {{if .Encrypted}}✓{{else}}✗{{end}}
</span>
{{if .Blacklists}}
<span class="status error" title="{{range .Blacklists}}{{.}}, {{end}}">Blacklisted {{len .Blacklists}} times</span>
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-red-900 text-red-200 border border-red-600">
Blacklisted {{len .Blacklists}} times
</span>
{{else}}
<span class="status good">Not listed on major blacklists</span>
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-900 text-green-200 border border-green-600">
Not Blacklisted
</span>
{{end}}
</div>
</div>
{{if .SenderRep}}
<div>
<b><span>Sender Reputation: </span></b><div class="status {{if contains .SenderRep "EXCELLENT"}}good{{else if contains .SenderRep "GOOD"}}good{{else if contains .SenderRep "FAIR"}}warning{{else}}error{{end}}">
<div class="mt-4">
<span class="text-sm font-medium text-gray-400">Sender Reputation:</span>
<div class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium mt-1 {{if contains .SenderRep "EXCELLENT"}}bg-green-900 text-green-200 border border-green-600{{else if contains .SenderRep "GOOD"}}bg-green-900 text-green-200 border border-green-600{{else if contains .SenderRep "FAIR"}}bg-yellow-900 text-yellow-200 border border-yellow-600{{else}}bg-red-900 text-red-200 border border-red-600{{end}}">
{{.SenderRep}}
</div>
</div>
{{end}}
<div class="explanation">
<small>
<b>Envelope Sender</b> is the real sender used for delivery (can differ from From).<br>
<b>From Domain</b> is the domain shown to the recipient.<br>
<b>Sending Server</b> is the host or IP that actually sent the message (from first Received header).<br>
</div>
</div>
<div class="mt-4 p-4 bg-gray-900 rounded-lg border border-gray-600">
<p class="text-sm text-gray-300">
<strong class="text-gray-200">Note:</strong> The <strong>Envelope Sender</strong> is the real sender used for delivery (can differ from From).
The <strong>From Domain</strong> is shown to the recipient. The <strong>Sending Server</strong> is the host/IP that sent the message.
If these differ, the message may be sent on behalf of another user or via a third-party service.
</small>
</p>
</div>
</div>
<!-- All Headers Table -->
<div class="bg-gray-800 rounded-lg border border-gray-700">
<details class="p-6">
<summary class="text-xl font-bold text-gray-100 cursor-pointer hover:text-blue-400 transition-colors">
📋 All Email Headers Table
</summary>
<div class="mt-4 space-y-4">
<div>
<input type="text"
id="headerSearch"
placeholder="Search headers..."
class="w-full max-w-md px-4 py-2 bg-gray-900 border border-gray-600 rounded-lg text-gray-100 placeholder-gray-400 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 focus:outline-none">
</div>
<details id="all-headers" class="section" style="margin-top:10px;">
<summary><b style="font-size: 1.5em;">All Email Headers Table</b></summary>
<div style="margin-bottom:10px;">
<input type="text" id="headerSearch" placeholder="Search headers..." style="width: 100%; max-width: 350px; padding: 5px; border-radius: 4px; border: 1px solid #444; background: #232323; color: #e0e0e0;">
</div>
<div style="overflow-x:auto;">
<table id="headersTable" style="width:100%; border-collapse:collapse; border:1px solid #444;">
<thead>
<div class="overflow-x-auto">
<table id="headersTable" class="w-full border border-gray-600 rounded-lg overflow-hidden">
<thead class="bg-gray-900">
<tr>
<th style="text-align:left; padding:4px 8px; border:1px solid #444; width: 180px; background:#232323;">Header Name</th>
<th style="text-align:left; padding:4px 8px; border:1px solid #444; background:#232323;">Value</th>
<th class="text-left px-4 py-3 text-gray-200 font-medium border-b border-gray-600 w-48">Header Name</th>
<th class="text-left px-4 py-3 text-gray-200 font-medium border-b border-gray-600">Value</th>
</tr>
</thead>
<tbody>
<tbody class="divide-y divide-gray-600">
{{range $k, $v := .AllHeaders}}
<tr>
<td style="vertical-align:top; padding:4px 8px; border:1px solid #444; word-break:break-word;">{{$k}}</td>
<td style="vertical-align:top; padding:4px 8px; border:1px solid #444; white-space:pre-wrap; word-break:break-word;">{{$v}}</td>
<tr class="hover:bg-gray-700/50">
<td class="px-4 py-3 text-gray-300 font-mono text-sm break-words border-r border-gray-600">{{$k}}</td>
<td class="px-4 py-3 text-gray-100 font-mono text-sm whitespace-pre-wrap break-words">{{$v}}</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
</details>
</div>
<div class="section">
<h2>Basic Information</h2>
<div class="grid">
{{if .PhishingRisk}}
<!-- Security Risk Assessment -->
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
<h2 class="text-2xl font-bold text-gray-100 mb-4">🔍 Security Risk Assessment</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="bg-gray-900 rounded-lg p-4 border border-gray-600">
<h3 class="text-lg font-semibold text-gray-200 mb-3">Phishing Risk</h3>
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium {{if eq (index (splitString .PhishingRisk " ") 0) "HIGH"}}bg-red-900 text-red-200 border border-red-600{{else if eq (index (splitString .PhishingRisk " ") 0) "MEDIUM"}}bg-yellow-900 text-yellow-200 border border-yellow-600{{else}}bg-green-900 text-green-200 border border-green-600{{end}}">
{{.PhishingRisk}}
</span>
</div>
<div class="bg-gray-900 rounded-lg p-4 border border-gray-600">
<h3 class="text-lg font-semibold text-gray-200 mb-3">Spoofing Risk</h3>
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium {{if contains .SpoofingRisk "POTENTIAL"}}bg-yellow-900 text-yellow-200 border border-yellow-600{{else}}bg-green-900 text-green-200 border border-green-600{{end}}">
{{.SpoofingRisk}}
</span>
</div>
</div>
</div>
{{end}}
<!-- Basic Information -->
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
<h2 class="text-2xl font-bold text-gray-100 mb-4">📧 Basic Information</h2>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="space-y-3">
<div>
<p><b>From:</b> {{.From}}</p>
<p><b>To:</b> {{.To}}</p>
<p><b>Subject:</b> {{.Subject}}</p>
<p><b>Date:</b> {{.Date}}</p>
{{if .ReplyTo}}<p><b>Reply-To:</b> {{.ReplyTo}}</p>{{end}}
<span class="text-sm font-medium text-gray-400">From:</span>
<p class="text-gray-100 font-mono text-sm bg-gray-900 p-2 rounded break-all">{{.From}}</p>
</div>
<div>
<p><b>Message-ID:</b> {{.MessageID}}</p>
<p><b>Priority:</b> {{.Priority}}</p>
<p><b>Content Type:</b> {{.ContentType}}</p>
<p><b>Encoding:</b> {{.Encoding}}</p>
<span class="text-sm font-medium text-gray-400">To:</span>
<p class="text-gray-100 font-mono text-sm bg-gray-900 p-2 rounded break-all">{{.To}}</p>
</div>
<div>
<span class="text-sm font-medium text-gray-400">Subject:</span>
<p class="text-gray-100 font-mono text-sm bg-gray-900 p-2 rounded break-words">{{.Subject}}</p>
</div>
<div>
<span class="text-sm font-medium text-gray-400">Date:</span>
<p class="text-gray-100 font-mono text-sm bg-gray-900 p-2 rounded">{{.Date}}</p>
</div>
{{if .ReplyTo}}
<div>
<span class="text-sm font-medium text-gray-400">Reply-To:</span>
<p class="text-gray-100 font-mono text-sm bg-gray-900 p-2 rounded break-all">{{.ReplyTo}}</p>
</div>
{{end}}
</div>
<div class="space-y-3">
<div>
<span class="text-sm font-medium text-gray-400">Message-ID:</span>
<p class="text-gray-100 font-mono text-sm bg-gray-900 p-2 rounded break-all">{{.MessageID}}</p>
</div>
<div>
<span class="text-sm font-medium text-gray-400">Priority:</span>
<p class="text-gray-100 font-mono text-sm bg-gray-900 p-2 rounded">{{.Priority}}</p>
</div>
<div>
<span class="text-sm font-medium text-gray-400">Content Type:</span>
<p class="text-gray-100 font-mono text-sm bg-gray-900 p-2 rounded">{{.ContentType}}</p>
</div>
<div>
<span class="text-sm font-medium text-gray-400">Encoding:</span>
<p class="text-gray-100 font-mono text-sm bg-gray-900 p-2 rounded">{{.Encoding}}</p>
</div>
</div>
</div>
</div>
<div class="section">
<h2>Mail Flow</h2>
<ul class="mail-flow">
{{range .Received}}<li>{{.}}</li>{{end}}
</ul>
<!-- Mail Flow -->
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
<h2 class="text-2xl font-bold text-gray-100 mb-4">🔄 Mail Flow</h2>
<div class="space-y-3">
{{range $index, $received := .Received}}
<div class="flex items-start space-x-3">
<div class="flex-shrink-0 w-8 h-8 bg-blue-600 text-white rounded-full flex items-center justify-center text-sm font-bold">
{{add $index 1}}
</div>
<div class="flex-1 p-3 bg-gray-900 rounded-lg border border-gray-600">
<p class="text-gray-100 font-mono text-sm">{{$received}}</p>
</div>
</div>
{{end}}
</div>
</div>
<!-- Delivery Analysis -->
{{if ne .DeliveryDelay "Insufficient data for delay analysis"}}
<div class="section">
<h2>Delivery Analysis</h2>
<p><b>Delivery Timing:</b> {{.DeliveryDelay}}</p>
{{if .GeoLocation}}<p><b>Geographic Info:</b> {{.GeoLocation}}</p>{{end}}
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
<h2 class="text-2xl font-bold text-gray-100 mb-4">⏱️ Delivery Analysis</h2>
<div class="space-y-3">
<div>
<span class="text-sm font-medium text-gray-400">Delivery Timing:</span>
<p class="text-gray-100 font-mono text-sm bg-gray-900 p-2 rounded">{{.DeliveryDelay}}</p>
</div>
{{if .GeoLocation}}
<div>
<span class="text-sm font-medium text-gray-400">Geographic Info:</span>
<p class="text-gray-100 font-mono text-sm bg-gray-900 p-2 rounded">{{.GeoLocation}}</p>
</div>
{{end}}
</div>
</div>
{{end}}
<div class="section">
<h2>Security Analysis</h2>
<div class="security-analysis-vertical">
<div class="section">
<h3>SPF Authentication</h3>
<div class="status {{if .SPFPass}}good{{else}}error{{end}}">
<!-- Security Analysis -->
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
<h2 class="text-2xl font-bold text-gray-100 mb-4">🔒 Security Analysis</h2>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- SPF Authentication -->
<div class="bg-gray-900 rounded-lg p-4 border border-gray-600">
<h3 class="text-lg font-semibold text-gray-200 mb-3">SPF Authentication</h3>
<div class="mb-3">
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium {{if .SPFPass}}bg-green-900 text-green-200 border border-green-600{{else}}bg-red-900 text-red-200 border border-red-600{{end}}">
{{if .SPFPass}}✓ Passed{{else}}✗ Failed{{end}}
</span>
</div>
<p>{{.SPFDetails}}</p>
{{if .SPFRecord}}<pre>{{.SPFRecord}}</pre>{{end}}
<p class="text-gray-300 text-sm mb-3">{{.SPFDetails}}</p>
{{if .SPFRecord}}
<div class="mb-3">
<pre class="bg-gray-800 text-gray-100 p-3 rounded text-xs overflow-x-auto border border-gray-600">{{.SPFRecord}}</pre>
</div>
{{end}}
{{if .SPFHeader}}
<details class="details"><summary>Show SPF Header</summary><pre>{{.SPFHeader}}</pre></details>
<details class="text-sm">
<summary class="text-blue-400 cursor-pointer hover:text-blue-300">Show SPF Header</summary>
<pre class="bg-gray-800 text-gray-100 p-3 rounded text-xs overflow-x-auto border border-gray-600 mt-2">{{.SPFHeader}}</pre>
</details>
{{end}}
</div>
<div class="section">
<h3>DMARC Policy</h3>
<div class="status {{if .DMARCPass}}good{{else}}error{{end}}">
<!-- DMARC Policy -->
<div class="bg-gray-900 rounded-lg p-4 border border-gray-600">
<h3 class="text-lg font-semibold text-gray-200 mb-3">DMARC Policy</h3>
<div class="mb-3">
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium {{if .DMARCPass}}bg-green-900 text-green-200 border border-green-600{{else}}bg-red-900 text-red-200 border border-red-600{{end}}">
{{if .DMARCPass}}✓ Passed{{else}}✗ Failed{{end}}
</span>
</div>
<p>{{.DMARCDetails}}</p>
{{if .DMARCRecord}}<pre>{{.DMARCRecord}}</pre>{{end}}
<p class="text-gray-300 text-sm mb-3">{{.DMARCDetails}}</p>
{{if .DMARCRecord}}
<div class="mb-3">
<pre class="bg-gray-800 text-gray-100 p-3 rounded text-xs overflow-x-auto border border-gray-600">{{.DMARCRecord}}</pre>
</div>
{{end}}
{{if .DMARCHeader}}
<details class="details"><summary>Show DMARC Header</summary><pre>{{.DMARCHeader}}</pre></details>
<details class="text-sm">
<summary class="text-blue-400 cursor-pointer hover:text-blue-300">Show DMARC Header</summary>
<pre class="bg-gray-800 text-gray-100 p-3 rounded text-xs overflow-x-auto border border-gray-600 mt-2">{{.DMARCHeader}}</pre>
</details>
{{end}}
</div>
<div class="section">
<h3>DKIM Signature</h3>
<div class="status {{if .DKIMPass}}good{{else}}error{{end}}">
<!-- DKIM Signature -->
<div class="bg-gray-900 rounded-lg p-4 border border-gray-600">
<h3 class="text-lg font-semibold text-gray-200 mb-3">DKIM Signature</h3>
<div class="mb-3">
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium {{if .DKIMPass}}bg-green-900 text-green-200 border border-green-600{{else}}bg-red-900 text-red-200 border border-red-600{{end}}">
{{if .DKIMPass}}✓ Present{{else}}✗ Missing{{end}}
</span>
</div>
<p>{{.DKIMDetails}}</p>
<p class="text-gray-300 text-sm mb-3">{{.DKIMDetails}}</p>
{{if .DKIM}}
<details class="details"><summary>Show DKIM Header</summary><pre>{{.DKIM}}</pre></details>
<details class="text-sm">
<summary class="text-blue-400 cursor-pointer hover:text-blue-300">Show DKIM Header</summary>
<pre class="bg-gray-800 text-gray-100 p-3 rounded text-xs overflow-x-auto border border-gray-600 mt-2">{{.DKIM}}</pre>
</details>
{{else if .DKIMHeader}}
<details class="details"><summary>Show DKIM Header</summary><pre>{{.DKIMHeader}}</pre></details>
<details class="text-sm">
<summary class="text-blue-400 cursor-pointer hover:text-blue-300">Show DKIM Header</summary>
<pre class="bg-gray-800 text-gray-100 p-3 rounded text-xs overflow-x-auto border border-gray-600 mt-2">{{.DKIMHeader}}</pre>
</details>
{{end}}
</div>
</div>
</div>
<div class="section">
<h2>Encryption</h2>
<div class="status {{if .Encrypted}}good{{else}}error{{end}}">
{{if .Encrypted}}Encrypted (TLS){{else}}Not Encrypted{{end}}
<!-- Encryption -->
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
<h2 class="text-2xl font-bold text-gray-100 mb-4">🔐 Encryption</h2>
<div class="mb-4">
<span class="inline-flex items-center px-4 py-2 rounded-lg text-sm font-medium {{if .Encrypted}}bg-green-900 text-green-200 border border-green-600{{else}}bg-red-900 text-red-200 border border-red-600{{end}}">
{{if .Encrypted}}🔒 Encrypted (TLS){{else}}🔓 Not Encrypted{{end}}
</span>
</div>
<details><summary>Show Encryption Details</summary><pre>{{.EncryptionDetail}}</pre></details>
<details class="text-sm">
<summary class="text-blue-400 cursor-pointer hover:text-blue-300 mb-2">Show Encryption Details</summary>
<pre class="bg-gray-900 text-gray-100 p-4 rounded-lg text-xs overflow-x-auto border border-gray-600">{{.EncryptionDetail}}</pre>
</details>
</div>
{{if .Warnings}}
<div class="section">
<h2>Warnings</h2>
<ul>
{{range .Warnings}}<li class="status warning">⚠️ {{.}}</li>{{end}}
</ul>
<!-- Warnings -->
<div class="bg-yellow-900/20 rounded-lg p-6 border border-yellow-600">
<h2 class="text-2xl font-bold text-yellow-200 mb-4">⚠️ Warnings</h2>
<div class="space-y-2">
{{range .Warnings}}
<div class="flex items-start space-x-2">
<span class="text-yellow-400 mt-1">⚠️</span>
<p class="text-yellow-100">{{.}}</p>
</div>
{{end}}
</div>
</div>
{{end}}
{{if .SecurityFlags}}
<div class="section">
<h2>Security Flags</h2>
<ul>
{{range .SecurityFlags}}<li class="status">🔒 {{.}}</li>{{end}}
</ul>
<!-- Security Flags -->
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
<h2 class="text-2xl font-bold text-gray-100 mb-4">🛡️ Security Flags</h2>
<div class="space-y-2">
{{range .SecurityFlags}}
<div class="flex items-start space-x-2">
<span class="text-green-400 mt-1">🔒</span>
<p class="text-gray-100">{{.}}</p>
</div>
{{end}}
</div>
</div>
{{end}}
{{if .Blacklists}}
<div class="section">
<h2>Blacklist Status</h2>
<div style="margin-bottom: 6px;">
<b>Checked:</b>
<!-- Blacklist Status -->
<div class="bg-red-900/20 rounded-lg p-6 border border-red-600">
<h2 class="text-2xl font-bold text-red-200 mb-4">🚫 Blacklist Status</h2>
<div class="mb-4">
<span class="text-sm font-medium text-gray-400">Checked:</span>
<span class="text-gray-100 ml-2">
{{if .SendingServer}}IP {{.SendingServer}}{{else if .FromDomain}}Domain {{.FromDomain}}{{end}}
</span>
</div>
<div class="mb-4">
<span class="inline-flex items-center px-4 py-2 rounded-lg text-sm font-medium bg-red-900 text-red-200 border border-red-600">
⚠️ Listed on the following blacklists
</span>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
{{range .Blacklists}}
<div class="bg-red-900/30 p-3 rounded border border-red-600">
<p class="text-red-100 text-sm">{{.}}</p>
</div>
{{end}}
</div>
<div class="status error">⚠️ Listed on the following blacklists:</div>
<ul>
{{range .Blacklists}}<li>{{.}}</li>{{end}}
</ul>
</div>
{{end}}
{{if .SpamFlags}}
<div class="section">
<h2>Spam Analysis</h2>
<div class="status {{if gt (len .SpamFlags) 0}}warning{{else}}good{{end}}">
<!-- Spam Analysis -->
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
<h2 class="text-2xl font-bold text-gray-100 mb-4">🛡️ Spam Analysis</h2>
<div class="mb-4">
<span class="inline-flex items-center px-4 py-2 rounded-lg text-sm font-medium {{if gt (len .SpamFlags) 0}}bg-yellow-900 text-yellow-200 border border-yellow-600{{else}}bg-green-900 text-green-200 border border-green-600{{end}}">
{{if gt (len .SpamFlags) 0}}⚠️ Spam Indicators Found{{else}}✓ No Spam Indicators{{end}}
</span>
</div>
{{if .SpamScore}}<p><b>Spam Score:</b> {{.SpamScore}}</p>{{end}}
{{if .SpamScore}}
<div class="mb-4">
<span class="text-sm font-medium text-gray-400">Spam Score:</span>
<span class="text-gray-100 ml-2 font-mono">{{.SpamScore}}</span>
</div>
{{end}}
{{if .SpamFlags}}
<ul>
{{range .SpamFlags}}<li>{{.}}</li>{{end}}
</ul>
<div class="space-y-2">
{{range .SpamFlags}}
<div class="bg-yellow-900/20 p-3 rounded border border-yellow-600">
<p class="text-yellow-100 text-sm">{{.}}</p>
</div>
{{end}}
</div>
{{end}}
</div>
{{end}}
{{if ne .VirusInfo "No virus scanning information found"}}
<div class="section">
<h2>Virus Scanning</h2>
<div class="status good">🛡️ Virus Scanning Information</div>
<p>{{.VirusInfo}}</p>
</div>
{{end}}
{{if .PhishingRisk}}
<div class="section">
<h2>Security Risk Assessment</h2>
<div class="security-analysis-vertical">
<div class="section">
<h3>Phishing Risk</h3>
<div class="status {{if eq (index (splitString .PhishingRisk " ") 0) "HIGH"}}error{{else if eq (index (splitString .PhishingRisk " ") 0) "MEDIUM"}}warning{{else}}good{{end}}">
{{.PhishingRisk}}
</div>
</div>
<div class="section">
<h3>Spoofing Risk</h3>
<div class="status {{if contains .SpoofingRisk "POTENTIAL"}}warning{{else}}good{{end}}">
{{.SpoofingRisk}}
</div>
</div>
<!-- Virus Scanning -->
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
<h2 class="text-2xl font-bold text-gray-100 mb-4">🛡️ Virus Scanning</h2>
<div class="mb-4">
<span class="inline-flex items-center px-4 py-2 rounded-lg text-sm font-medium bg-green-900 text-green-200 border border-green-600">
🛡️ Virus Scanning Information
</span>
</div>
<p class="text-gray-100">{{.VirusInfo}}</p>
</div>
{{end}}
{{if .ListInfo}}
<div class="section">
<h2>Mailing List Information</h2>
<ul>
{{range .ListInfo}}<li>{{.}}</li>{{end}}
</ul>
{{if .AutoReply}}<p class="status">📧 Auto-reply message detected</p>{{end}}
{{if .BulkEmail}}<p class="status">📬 Bulk/marketing email detected</p>{{end}}
<!-- Mailing List Information -->
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
<h2 class="text-2xl font-bold text-gray-100 mb-4">📬 Mailing List Information</h2>
<div class="space-y-2 mb-4">
{{range .ListInfo}}
<div class="bg-gray-900 p-3 rounded border border-gray-600">
<p class="text-gray-100 text-sm">{{.}}</p>
</div>
{{end}}
</div>
<div class="flex flex-wrap gap-2">
{{if .AutoReply}}
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-900 text-blue-200 border border-blue-600">
📧 Auto-reply message detected
</span>
{{end}}
{{if .BulkEmail}}
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-blue-900 text-blue-200 border border-blue-600">
📬 Bulk/marketing email detected
</span>
{{end}}
</div>
</div>
{{end}}
{{if .Compliance}}
<div class="section">
<h2>Compliance Information</h2>
<ul>
{{range .Compliance}}<li class="status good">✓ {{.}}</li>{{end}}
</ul>
<!-- Compliance Information -->
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
<h2 class="text-2xl font-bold text-gray-100 mb-4">✅ Compliance Information</h2>
<div class="space-y-2">
{{range .Compliance}}
<div class="flex items-start space-x-2">
<span class="text-green-400 mt-1"></span>
<p class="text-gray-100">{{.}}</p>
</div>
{{end}}
</div>
</div>
{{end}}
{{if .ARC}}
<div class="section">
<h2>ARC (Authenticated Received Chain)</h2>
<details><summary>Show ARC Headers</summary>
<ul>
{{range .ARC}}<li><pre>{{.}}</pre></li>{{end}}
</ul>
<!-- ARC (Authenticated Received Chain) -->
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
<h2 class="text-2xl font-bold text-gray-100 mb-4">🔗 ARC (Authenticated Received Chain)</h2>
<details class="text-sm">
<summary class="text-blue-400 cursor-pointer hover:text-blue-300 mb-2">Show ARC Headers</summary>
<div class="space-y-3 mt-4">
{{range .ARC}}
<pre class="bg-gray-900 text-gray-100 p-4 rounded-lg text-xs overflow-x-auto border border-gray-600">{{.}}</pre>
{{end}}
</div>
</details>
</div>
{{end}}
{{if ne .BIMI "No BIMI record found"}}
<div class="section">
<h2>Brand Indicators (BIMI)</h2>
<p>{{.BIMI}}</p>
<!-- Brand Indicators (BIMI) -->
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
<h2 class="text-2xl font-bold text-gray-100 mb-4">🎨 Brand Indicators (BIMI)</h2>
<p class="text-gray-100">{{.BIMI}}</p>
</div>
{{end}}
{{if .Attachments}}
<div class="section">
<h2>Attachment Information</h2>
<ul>
{{range .Attachments}}<li>{{.}}</li>{{end}}
</ul>
<!-- Attachment Information -->
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
<h2 class="text-2xl font-bold text-gray-100 mb-4">📎 Attachment Information</h2>
<div class="space-y-2">
{{range .Attachments}}
<div class="bg-gray-900 p-3 rounded border border-gray-600">
<p class="text-gray-100 text-sm font-mono">{{.}}</p>
</div>
{{end}}
</div>
</div>
{{end}}
{{if .URLs}}
<div class="section">
<h2>URL Information</h2>
<ul>
{{range .URLs}}<li>{{.}}</li>{{end}}
</ul>
<!-- URL Information -->
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
<h2 class="text-2xl font-bold text-gray-100 mb-4">🔗 URL Information</h2>
<div class="space-y-2">
{{range .URLs}}
<div class="bg-gray-900 p-3 rounded border border-gray-600">
<p class="text-gray-100 text-sm font-mono break-all">{{.}}</p>
</div>
{{end}}
</div>
</div>
{{end}}
{{if ne .ThreadInfo "No threading information available"}}
<div class="section">
<h2>Message Threading</h2>
<details><summary>Show Threading Information</summary>
<pre>{{.ThreadInfo}}</pre>
<!-- Message Threading -->
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
<h2 class="text-2xl font-bold text-gray-100 mb-4">🧵 Message Threading</h2>
<details class="text-sm">
<summary class="text-blue-400 cursor-pointer hover:text-blue-300 mb-2">Show Threading Information</summary>
<pre class="bg-gray-900 text-gray-100 p-4 rounded-lg text-xs overflow-x-auto border border-gray-600 mt-4">{{.ThreadInfo}}</pre>
</details>
</div>
{{end}}
<div class="section">
<button onclick="exportPDF()" type="button">Export as PDF</button>
<button onclick="exportImage()" type="button">Save as Image</button>
<button onclick="printReport()" type="button">Print Report</button>
<!-- Export Options -->
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700 text-center">
<h2 class="text-xl font-bold text-gray-100 mb-4">📄 Export Options</h2>
<div class="flex flex-wrap justify-center gap-4">
<button onclick="exportPDF()"
type="button"
class="bg-red-600 hover:bg-red-700 text-white font-medium px-6 py-3 rounded-lg transition-colors flex items-center space-x-2">
<span>📄</span>
<span>Export as PDF</span>
</button>
<button onclick="exportImage()"
type="button"
class="bg-blue-600 hover:bg-blue-700 text-white font-medium px-6 py-3 rounded-lg transition-colors flex items-center space-x-2">
<span>🖼️</span>
<span>Save as Image</span>
</button>
<button onclick="printReport()"
type="button"
class="bg-green-600 hover:bg-green-700 text-white font-medium px-6 py-3 rounded-lg transition-colors flex items-center space-x-2">
<span>🖨️</span>
<span>Print Report</span>
</button>
</div>
</div>
</div>
{{end}}
@@ -355,6 +590,10 @@
return str.includes(substr);
}
function add(a, b) {
return a + b;
}
function exportImage() {
if (typeof html2canvas === 'undefined') {
alert('Image export library not loaded. Please refresh the page and try again.');

158
web/landing_page.html Normal file
View File

@@ -0,0 +1,158 @@
{{template "base.html" .}}
{{define "title"}}HeaderAnalyzer - IT & Security Tools{{end}}
{{define "head"}}
<style>
/* Additional custom styles for landing page */
.feature-card {
transition: all 0.3s ease;
}
.feature-card:hover {
transform: translateY(-4px);
}
.gradient-bg {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.category-badge {
background: linear-gradient(45deg, #3b82f6, #8b5cf6);
}
</style>
{{end}}
{{define "content"}}
<div class="min-h-screen">
<!-- Hero Section -->
<div class="text-center mb-16">
<div class="gradient-bg rounded-3xl p-12 mb-8 shadow-2xl">
<h1 class="text-5xl md:text-6xl font-bold text-white" style="border-bottom: none;">IT & Security Tools</h1>
<!--
<p class="text-xl md:text-2xl text-blue-100 mb-8 max-w-3xl mx-auto">
Your comprehensive toolkit for IT diagnostics and online security
</p>
<div class="flex flex-wrap justify-center gap-4 text-sm text-blue-200">
<span class="bg-white bg-opacity-20 px-4 py-2 rounded-full">Email Security</span>
<span class="bg-white bg-opacity-20 px-4 py-2 rounded-full">Password Security</span>
<span class="bg-white bg-opacity-20 px-4 py-2 rounded-full">Data Protection</span>
</div>
-->
</div>
</div>
<!-- Tools Categories -->
<div class="max-w-6xl mx-auto space-y-12">
<!-- IT Tools Category -->
<div class="category-section">
<div class="flex items-center mb-8">
<div class="category-badge text-white px-4 py-2 rounded-full text-sm font-semibold mr-4">
IT TOOLS
</div>
<div class="flex-1 h-px bg-dark-border"></div>
</div>
<div class="grid md:grid-cols-2 gap-6">
<!-- Email Header Analyzer -->
<a href="/analyze" class="feature-card block group">
<div class="bg-dark-surface border border-dark-border rounded-xl p-6 h-full hover:border-green-500 transition-all duration-300">
<div class="flex items-start space-x-4">
<div class="flex items-center justify-center w-12 h-12 bg-green-600 rounded-xl group-hover:bg-green-700 transition-colors">
<svg class="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 20 20">
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z"/>
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z"/>
</svg>
</div>
<div class="flex-1">
<h3 class="text-xl font-bold text-dark-text mb-2 group-hover:text-green-400 transition-colors">
Email Header Analyzer
</h3>
<p class="text-dark-muted mb-4">
Analyze email headers to detect security threats, trace origins, and validate email authenticity. Essential for cybersecurity professionals.
</p>
</div>
</div>
</div>
</a>
<!-- DNS Tools -->
<a href="/dns" class="feature-card block group">
<div class="bg-dark-surface border border-dark-border rounded-xl p-6 h-full hover:border-purple-500 transition-all duration-300">
<div class="flex items-start space-x-4">
<div class="flex items-center justify-center w-12 h-12 bg-purple-600 rounded-xl group-hover:bg-purple-700 transition-colors">
<svg class="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M3 3a1 1 0 000 2v8a2 2 0 002 2h2.586l-1.293 1.293a1 1 0 101.414 1.414L10 15.414l2.293 2.293a1 1 0 001.414-1.414L12.414 15H15a2 2 0 002-2V5a1 1 0 100-2H3zm11.707 4.707a1 1 0 00-1.414-1.414L10 9.586 8.707 8.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
</svg>
</div>
<div class="flex-1">
<h3 class="text-xl font-bold text-dark-text mb-2 group-hover:text-purple-400 transition-colors">
DNS Tools
</h3>
<p class="text-dark-muted mb-4">
Perform comprehensive DNS lookups, diagnose network issues, and verify domain configurations. Perfect for system administrators.
</p>
</div>
</div>
</div>
</a>
</div>
</div>
<!-- Online Security Category -->
<div class="category-section">
<div class="flex items-center mb-8">
<div class="bg-gradient-to-r from-orange-500 to-red-500 text-white px-4 py-2 rounded-full text-sm font-semibold mr-4">
ONLINE SECURITY
</div>
<div class="flex-1 h-px bg-dark-border"></div>
</div>
<div class="grid md:grid-cols-2 gap-6">
<!-- Password Generator -->
<a href="/pwgenerator" class="feature-card block group">
<div class="bg-dark-surface border border-dark-border rounded-xl p-6 h-full hover:border-yellow-500 transition-all duration-300">
<div class="flex items-start space-x-4">
<div class="flex items-center justify-center w-12 h-12 bg-yellow-600 rounded-xl group-hover:bg-yellow-700 transition-colors">
<svg class="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M18 8a6 6 0 01-7.743 5.743L10 14l-0.257-.257A6 6 0 1118 8zm-1.5 0a4.5 4.5 0 11-9 0 4.5 4.5 0 019 0zM10 7a1 1 0 100 2 1 1 0 000-2z" clip-rule="evenodd"/>
</svg>
</div>
<div class="flex-1">
<h3 class="text-xl font-bold text-dark-text mb-2 group-hover:text-yellow-400 transition-colors">
Password Generator
</h3>
<p class="text-dark-muted mb-4">
Generate cryptographically secure passwords with customizable options. Enhance your digital security with strong, unique passwords.
</p>
</div>
</div>
</div>
</a>
<!-- Password Pusher -->
<a href="/pwpush" class="feature-card block group">
<div class="bg-dark-surface border border-dark-border rounded-xl p-6 h-full hover:border-red-500 transition-all duration-300">
<div class="flex items-start space-x-4">
<div class="flex items-center justify-center w-12 h-12 bg-red-600 rounded-xl group-hover:bg-red-700 transition-colors">
<svg class="w-6 h-6 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"/>
</svg>
</div>
<div class="flex-1">
<h3 class="text-xl font-bold text-dark-text mb-2 group-hover:text-red-400 transition-colors">
Password Pusher
</h3>
<p class="text-dark-muted mb-4">
Securely share sensitive information with automatic expiration and view limits. Perfect for sharing passwords and confidential data.
</p>
</div>
</div>
</div>
</a>
</div>
</div>
</div>
</div>
{{end}}

View File

@@ -3,123 +3,195 @@
{{define "title"}}Password Generator - HeaderAnalyzer{{end}}
{{define "content"}}
<div class="password-generator">
<h1>🔐 Password Generator</h1>
<div class="container mx-auto px-4 py-8 max-w-4xl">
<div class="text-center mb-8">
<a href="/pwgenerator" class="inline-block">
<h1 class="text-2xl md:text-3xl font-bold text-gray-100 hover:text-blue-400 transition-colors cursor-pointer mb-4">
🔐 Password Generator
</h1>
</a>
</div>
<!-- Hidden CSRF token for API calls -->
<input type="hidden" id="csrfToken" value="{{.CSRFToken}}">
<div class="tab-buttons">
<button class="tab-btn" id="randomTab">Random Password</button>
<button class="tab-btn active" id="passphraseTab">Passphrase</button>
<!-- Tab Buttons -->
<div class="flex space-x-2 mb-6 bg-gray-800 p-2 rounded-lg border border-gray-700">
<button class="flex-1 py-3 px-4 rounded-lg text-center font-medium transition-colors bg-gray-700 text-gray-300 hover:bg-gray-600" id="randomTab">
🎲 Random Password
</button>
<button class="flex-1 py-3 px-4 rounded-lg text-center font-medium transition-colors bg-blue-600 text-white" id="passphraseTab">
📝 Passphrase
</button>
</div>
<div class="password-output">
<div class="password-text" id="passwordDisplay">Click "Generate Password" to create a secure password</div>
<button class="copy-btn" id="copyBtn" onclick="copyPassword()" style="display: none;">Copy to Clipboard</button>
</div>
<button class="generate-btn" onclick="generatePassword()">🎲 Generate Password</button>
<div class="controls">
<div class="control-group">
<h3>🔧 Basic Settings</h3>
<div class="form-row">
<label for="length">Password Length:</label>
<input type="number" id="length" min="4" max="128" value="{{.Config.Length}}" onchange="updateURL()">
</div>
<div class="form-row">
<label for="includeUpper">Include Uppercase (A-Z):</label>
<input type="checkbox" id="includeUpper" {{if .Config.IncludeUpper}}checked{{end}} onchange="updateURL()">
</div>
<div class="form-row">
<label for="includeLower">Include Lowercase (a-z):</label>
<input type="checkbox" id="includeLower" {{if .Config.IncludeLower}}checked{{end}} onchange="updateURL()">
</div>
<div class="form-row">
<label for="numberCount">Number of Digits:</label>
<input type="number" id="numberCount" min="0" max="20" value="{{.Config.NumberCount}}" onchange="updateURL()">
</div>
<div class="form-row">
<label for="specialChars">Special Characters:</label>
<input type="text" id="specialChars" value="{{.Config.SpecialChars}}" onchange="updateURL()">
</div>
<div class="form-row">
<label for="noConsecutive">No consecutive identical characters:</label>
<input type="checkbox" id="noConsecutive" {{if .Config.NoConsecutive}}checked{{end}} onchange="updateURL()">
<!-- Password Output -->
<!-- Generated Password Display -->
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700 mb-8">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-4">
<h2 class="text-xl font-semibold text-gray-200">🔐 Generated Password</h2>
<div class="flex items-center gap-4 text-sm">
<span id="characterCount" class="text-gray-400 bg-gray-700 px-3 py-1 rounded-full">
0 characters
</span>
<span id="strengthDisplay" class="text-gray-400 bg-gray-700 px-3 py-1 rounded-full">
Not generated
</span>
</div>
</div>
<div class="control-group">
<h3>🎯 Advanced Settings</h3>
<div class="passphrase-controls {{if eq .Config.Type "passphrase"}}active{{end}}" id="passphraseControls">
<div class="form-row">
<label for="wordCount">Number of Words:</label>
<input type="number" id="wordCount" min="2" max="10" value="{{.Config.WordCount}}" onchange="updateURL()">
<div class="relative">
<input type="text" id="generatedPassword" readonly
class="w-full p-4 bg-gray-900 border border-gray-600 rounded-lg text-gray-100 font-mono text-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Click 'Generate Password' to create a new password">
<button onclick="copyPassword()"
class="absolute right-2 top-1/2 transform -translate-y-1/2 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500/20">
📋 Copy
</button>
</div>
</div>
<div class="form-row">
<label for="passphraseUseNumbers">Include Numbers:</label>
<input type="checkbox" id="passphraseUseNumbers" {{if .Config.UseNumbers}}checked{{end}} onchange="updateURL()">
<!-- Generate Button -->
<div class="flex flex-col sm:flex-row items-center justify-center gap-4 mb-8">
<button onclick="generatePassword()"
class="bg-green-600 hover:bg-green-700 text-white font-bold py-4 px-8 rounded-lg text-lg transition-all duration-200 transform hover:scale-105">
🎲 Generate Password
</button>
<button onclick="copyCurrentURL()"
class="bg-blue-600 hover:bg-blue-700 text-white font-medium py-4 px-6 rounded-lg transition-colors duration-200">
🔗 Copy URL with Settings
</button>
<button onclick="resetAllSettings()"
class="bg-red-600 hover:bg-red-700 text-white font-medium py-4 px-6 rounded-lg transition-colors duration-200">
🔄 Reset
</button>
</div>
<div class="form-row">
<label for="passphraseUseSpecial">Include Special Characters:</label>
<input type="checkbox" id="passphraseUseSpecial" {{if .Config.UseSpecial}}checked{{end}} onchange="updateURL()">
<!-- Controls -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
<!-- Basic Settings -->
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
<h3 class="text-xl font-semibold text-gray-200 mb-4">🔧 Basic Settings</h3>
<div class="space-y-4">
<!-- Save Passwords Option (moved from Settings Management) -->
<div class="p-3 bg-gray-900 rounded-lg border border-gray-600">
<div class="flex items-center space-x-3">
<input type="checkbox" id="savePasswords" {{if .Config.SavePasswords}}checked{{end}} onchange="togglePasswordSaving(); autoSaveSettings()"
class="w-4 h-4 text-blue-600 bg-gray-700 border-gray-600 rounded focus:ring-blue-500 focus:ring-2">
<label for="savePasswords" class="text-sm text-gray-300">
Save Generated Passwords (in web browser cookies only)
</label>
</div>
</div>
<div class="form-row">
<label for="numberPosition">Number Position:</label>
<select id="numberPosition" onchange="updateURL()">
<div>
<label for="length" class="block text-sm font-medium text-gray-300 mb-2">Password Length:</label>
<input type="number" id="length" min="4" max="128" value="{{.Config.Length}}" onchange="updateURL(); autoSaveSettings()"
class="w-full px-3 py-2 bg-gray-900 border border-gray-600 rounded-lg text-gray-100 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 focus:outline-none">
</div>
<div class="flex items-center space-x-3">
<input type="checkbox" id="includeUpper" {{if .Config.IncludeUpper}}checked{{end}} onchange="updateURL(); autoSaveSettings()"
class="w-4 h-4 text-blue-600 bg-gray-700 border-gray-600 rounded focus:ring-blue-500 focus:ring-2">
<label for="includeUpper" class="text-sm text-gray-300">Include Uppercase (A-Z)</label>
</div>
<div class="flex items-center space-x-3">
<input type="checkbox" id="includeLower" {{if .Config.IncludeLower}}checked{{end}} onchange="updateURL(); autoSaveSettings()"
class="w-4 h-4 text-blue-600 bg-gray-700 border-gray-600 rounded focus:ring-blue-500 focus:ring-2">
<label for="includeLower" class="text-sm text-gray-300">Include Lowercase (a-z)</label>
</div>
<div>
<label for="numberCount" class="block text-sm font-medium text-gray-300 mb-2">Number of Digits:</label>
<input type="number" id="numberCount" min="0" max="20" value="{{.Config.NumberCount}}" onchange="updateURL(); autoSaveSettings()"
class="w-full px-3 py-2 bg-gray-900 border border-gray-600 rounded-lg text-gray-100 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 focus:outline-none">
</div>
<div>
<label for="specialChars" class="block text-sm font-medium text-gray-300 mb-2">Special Characters:</label>
<input type="text" id="specialChars" value="{{.Config.SpecialChars}}"
onchange="validateSpecialChars(); updateURL(); autoSaveSettings()"
oninput="validateSpecialChars()"
pattern="[!@#$%&*\-_=+.]*"
title="Only these special characters are allowed: !@#$%&*-_=+."
class="w-full px-3 py-2 bg-gray-900 border border-gray-600 rounded-lg text-gray-100 font-mono focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 focus:outline-none">
<div id="specialCharsError" class="text-red-400 text-sm mt-1 hidden">
Only these special characters are allowed: !@#$%&*-_=+.
</div>
<div class="text-gray-500 text-xs mt-1">
Allowed: !@#$%&*-_=+.
</div>
</div>
<div class="flex items-center space-x-3">
<input type="checkbox" id="noConsecutive" {{if .Config.NoConsecutive}}checked{{end}} onchange="updateURL(); autoSaveSettings()"
class="w-4 h-4 text-blue-600 bg-gray-700 border-gray-600 rounded focus:ring-blue-500 focus:ring-2">
<label for="noConsecutive" class="text-sm text-gray-300">No consecutive identical characters</label>
</div>
</div>
</div>
<!-- Advanced Settings -->
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700">
<h3 class="text-xl font-semibold text-gray-200 mb-4">🎯 Advanced Settings</h3>
<div class="space-y-4">
<!-- Passphrase Controls -->
<div class="{{if eq .Config.Type "passphrase"}}block{{else}}hidden{{end}}" id="passphraseControls">
<div class="space-y-4 p-4 bg-gray-900 rounded-lg border border-gray-600">
<h4 class="text-lg font-medium text-gray-200">📝 Passphrase Options</h4>
<div>
<label for="wordCount" class="block text-sm font-medium text-gray-300 mb-2">Number of Words:</label>
<input type="number" id="wordCount" min="2" max="10" value="{{.Config.WordCount}}" onchange="updateURL()"
class="w-full px-3 py-2 bg-gray-800 border border-gray-600 rounded-lg text-gray-100 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 focus:outline-none">
</div>
<div class="flex items-center space-x-3">
<input type="checkbox" id="passphraseUseNumbers" {{if .Config.UseNumbers}}checked{{end}} onchange="updateURL()"
class="w-4 h-4 text-blue-600 bg-gray-700 border-gray-600 rounded focus:ring-blue-500 focus:ring-2">
<label for="passphraseUseNumbers" class="text-sm text-gray-300">Include Numbers</label>
</div>
<div class="flex items-center space-x-3">
<input type="checkbox" id="passphraseUseSpecial" {{if .Config.UseSpecial}}checked{{end}} onchange="updateURL()"
class="w-4 h-4 text-blue-600 bg-gray-700 border-gray-600 rounded focus:ring-blue-500 focus:ring-2">
<label for="passphraseUseSpecial" class="text-sm text-gray-300">Include Special Characters</label>
</div>
<div>
<label for="numberPosition" class="block text-sm font-medium text-gray-300 mb-2">Number Position:</label>
<select id="numberPosition" onchange="updateURL()"
class="w-full px-3 py-2 bg-gray-800 border border-gray-600 rounded-lg text-gray-100 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 focus:outline-none">
<option value="end" {{if eq .Config.NumberPosition "end"}}selected{{end}}>At End</option>
<option value="start" {{if eq .Config.NumberPosition "start"}}selected{{end}}>At Start</option>
<option value="each" {{if eq .Config.NumberPosition "each"}}selected{{end}}>After Each Word</option>
</select>
</div>
</div>
<div class="form-row">
<label>Strength Indicator:</label>
<div id="strengthIndicator" style="color: #999;">Generate a password to see strength</div>
</div>
<div class="form-row">
<label>Word List Status:</label>
<div id="wordListStatus" style="color: #999; font-size: 12px;">Loading...</div>
<div>
<label class="block text-sm font-medium text-gray-300 mb-2">Strength Indicator:</label>
<div id="strengthIndicator" class="text-gray-400 p-3 bg-gray-900 rounded-lg border border-gray-600">
Generate a password to see strength
</div>
</div>
</div>
<!-- Settings Management -->
<div class="control-group" style="margin-top: 20px;">
<h3>💾 Settings & History</h3>
<div class="form-row">
<button onclick="saveSettings()" style="background: #007acc; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-right: 10px;">Save Current Settings</button>
<button onclick="loadSettingsManual()" style="background: #6c757d; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin-right: 10px;">Load Saved Settings</button>
<button onclick="clearSettings()" style="background: #dc3545; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer;">Clear Saved Settings</button>
</div>
<div class="form-row">
<label for="savePasswords">Save Generated Passwords (in web browser cookies only):</label>
<input type="checkbox" id="savePasswords" {{if .Config.SavePasswords}}checked{{end}} onchange="togglePasswordSaving(); updateURL()">
</div>
</div>
<!-- Password History -->
<div class="control-group" style="margin-top: 20px;">
<h3>📚 Password History</h3>
<div id="passwordHistory" style="max-height: 200px; overflow-y: auto; background: #1a1a1a; border: 1px solid #333; border-radius: 4px; padding: 10px;">
<p style="color: #999; font-style: italic;">No passwords generated yet</p>
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700 mt-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-xl font-semibold text-gray-200">📚 Password History</h3>
<button onclick="clearHistory()"
class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white font-medium rounded-lg transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-red-500/20">
🗑️ Clear History
</button>
</div>
<div class="form-row" style="margin-top: 10px;">
<button onclick="clearHistory()" style="background: #dc3545; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer;">Clear History</button>
<div id="passwordHistory" class="bg-gray-900 border border-gray-600 rounded-lg p-4">
<p class="text-gray-400 italic">No passwords generated yet</p>
</div>
</div>
</div>
@@ -142,63 +214,101 @@ document.addEventListener('DOMContentLoaded', function() {
// Load password history
loadPasswordHistory();
// Load word list info
loadWordListInfo();
// Auto-generate if URL has parameters (excluding default)
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.toString()) {
generatePassword();
}
// Add event listeners to automatically save settings on change
document.querySelectorAll('input, select').forEach(element => {
element.addEventListener('change', function() {
updateURL();
saveSettings();
});
});
// Note: Removed auto-save event listeners to prevent excessive saving notifications
});
// Tab switching
function switchTab(mode) {
currentMode = mode;
document.getElementById('randomTab').classList.toggle('active', mode === 'random');
document.getElementById('passphraseTab').classList.toggle('active', mode === 'passphrase');
document.getElementById('passphraseControls').classList.toggle('active', mode === 'passphrase');
// Get tab elements
const randomTab = document.getElementById('randomTab');
const passphraseTab = document.getElementById('passphraseTab');
// Remove active classes from both tabs
randomTab.classList.remove('bg-blue-600', 'text-white');
randomTab.classList.add('bg-gray-700', 'text-gray-300', 'hover:bg-gray-600');
passphraseTab.classList.remove('bg-blue-600', 'text-white');
passphraseTab.classList.add('bg-gray-700', 'text-gray-300', 'hover:bg-gray-600');
// Add active classes to the selected tab
if (mode === 'random') {
randomTab.classList.remove('bg-gray-700', 'text-gray-300', 'hover:bg-gray-600');
randomTab.classList.add('bg-blue-600', 'text-white');
} else {
passphraseTab.classList.remove('bg-gray-700', 'text-gray-300', 'hover:bg-gray-600');
passphraseTab.classList.add('bg-blue-600', 'text-white');
}
// Show/hide passphrase controls
document.getElementById('passphraseControls').classList.toggle('block', mode === 'passphrase');
document.getElementById('passphraseControls').classList.toggle('hidden', mode !== 'passphrase');
updateURL();
saveSettings();
autoSaveSettings(); // Auto-save without notification
}
document.getElementById('randomTab').addEventListener('click', () => switchTab('random'));
document.getElementById('passphraseTab').addEventListener('click', () => switchTab('passphrase'));
// Validate special characters input
function validateSpecialChars() {
const input = document.getElementById('specialChars');
const errorDiv = document.getElementById('specialCharsError');
const allowedChars = '!@#$%&*-_=+.';
const value = input.value;
let isValid = true;
for (let i = 0; i < value.length; i++) {
if (!allowedChars.includes(value[i])) {
isValid = false;
break;
}
}
if (isValid) {
input.classList.remove('border-red-500', 'focus:border-red-500', 'focus:ring-red-500/20');
input.classList.add('border-gray-600', 'focus:border-blue-500', 'focus:ring-blue-500/20');
errorDiv.classList.add('hidden');
} else {
input.classList.remove('border-gray-600', 'focus:border-blue-500', 'focus:ring-blue-500/20');
input.classList.add('border-red-500', 'focus:border-red-500', 'focus:ring-red-500/20');
errorDiv.classList.remove('hidden');
}
return isValid;
}
// URL parameter management
function updateURL() {
const config = getCurrentConfig();
const params = new URLSearchParams();
// Define default values
// Define default values (savePasswords excluded from URL parameters)
const defaults = {
type: "passphrase",
length: 12,
includeUpper: true,
includeLower: true,
numberCount: 1,
specialChars: "!@#$%^&*-_=+",
specialChars: "!@#$%&*-_=+.",
noConsecutive: false,
wordCount: 3,
useNumbers: true,
useSpecial: false,
numberPosition: "end",
savePasswords: false
numberPosition: "end"
};
// Only add parameters that differ from defaults
// Only add parameters that differ from defaults (excluding savePasswords)
Object.keys(config).forEach(key => {
if (config[key] !== defaults[key]) {
if (key !== 'savePasswords' && config[key] !== defaults[key]) {
params.set(key, config[key]);
}
});
@@ -241,19 +351,71 @@ function saveSettings() {
showNotification('Settings saved! They will be remembered when you visit this page again.', 'success');
}
// Auto-save function without showing notification
function autoSaveSettings() {
const config = getCurrentConfig();
config.mode = currentMode;
const settings = JSON.stringify(config);
// Set cookie to expire in 1 year
const expiryDate = new Date();
expiryDate.setFullYear(expiryDate.getFullYear() + 1);
document.cookie = `passwordGenSettings=${encodeURIComponent(settings)}; expires=${expiryDate.toUTCString()}; path=/`;
}
// Copy current URL with settings
function copyCurrentURL() {
updateURL(); // Ensure URL is up to date
const currentURL = window.location.href;
navigator.clipboard.writeText(currentURL).then(function() {
showPopup('URL with current settings copied to clipboard!', 'success');
}, function(err) {
console.error('Could not copy URL: ', err);
showPopup('Failed to copy URL to clipboard', 'error');
});
}
// Reset all settings and cookies
function resetAllSettings() {
showConfirmationPopup(
'Reset All Settings?',
'This will clear all saved settings and password history, returning the page to its default state.',
function() {
// Clear all cookies
document.cookie = 'passwordGenSettings=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
document.cookie = 'passwordHistory=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
showPopup('All settings and history cleared. Redirecting to clean page...', 'info');
setTimeout(() => {
// Redirect to the page without any parameters
window.location.href = window.location.pathname;
}, 1500);
}
);
}
function loadSettings() {
// First try URL parameters
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.toString()) {
const config = {};
for (const [key, value] of urlParams) {
// Skip savePasswords from URL parameters - it's cookie-only
if (key === 'savePasswords') continue;
if (key === 'type') config[key] = value;
else if (key === 'length' || key === 'numberCount' || key === 'wordCount') config[key] = parseInt(value);
else if (key === 'includeUpper' || key === 'includeLower' || key === 'noConsecutive' ||
key === 'useNumbers' || key === 'useSpecial' || key === 'savePasswords') config[key] = value === 'true';
key === 'useNumbers' || key === 'useSpecial') config[key] = value === 'true';
else config[key] = value;
}
applyConfig(config);
// Load savePasswords setting separately from cookies only
loadSavePasswordsSetting();
return;
}
@@ -269,25 +431,82 @@ function loadSettings() {
}
}
function loadSettingsManual() {
// Load savePasswords setting from cookies only (never from URL)
function loadSavePasswordsSetting() {
const settings = getCookie('passwordGenSettings');
if (settings) {
try {
const config = JSON.parse(decodeURIComponent(settings));
applyConfig(config);
updateURL();
showNotification('Settings loaded successfully!', 'success');
} catch (e) {
showNotification('Error loading settings: ' + e.message, 'error');
}
if (config.savePasswords !== undefined) {
document.getElementById('savePasswords').checked = config.savePasswords;
// Update history display based on the setting
if (config.savePasswords) {
const history = getPasswordHistory();
displayPasswordHistory(history);
} else {
showNotification('No saved settings found.', 'warning');
document.getElementById('passwordHistory').innerHTML = '<p style="color: #999; font-style: italic;">Password saving is disabled</p>';
}
}
} catch (e) {
console.error('Failed to parse saved settings for savePasswords:', e);
}
}
}
function clearSettings() {
document.cookie = 'passwordGenSettings=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
showNotification('Saved settings cleared.', 'info');
// Custom confirmation popup function
function showConfirmationPopup(title, message, onConfirm) {
// Create backdrop
const backdrop = document.createElement('div');
backdrop.className = 'fixed inset-0 z-50 bg-black bg-opacity-50 backdrop-blur-sm flex items-center justify-center p-4';
// Create popup
backdrop.innerHTML = `
<div class="bg-gray-800 border border-gray-600 rounded-xl p-6 max-w-md w-full shadow-2xl transform transition-all">
<div class="flex items-center justify-between mb-4">
<h3 class="text-xl font-bold text-red-400">${title}</h3>
<button onclick="this.closest('.fixed').remove()" class="text-gray-400 hover:text-gray-200 transition-colors">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<p class="text-gray-300 mb-6">${message}</p>
<div class="flex space-x-3 justify-end">
<button onclick="this.closest('.fixed').remove()"
class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors">
Cancel
</button>
<button onclick="confirmAction()"
class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg transition-colors">
Yes, Clear All
</button>
</div>
</div>
`;
// Add confirm action to the backdrop element
backdrop.confirmCallback = onConfirm;
// Add global confirmAction function temporarily
window.confirmAction = function() {
backdrop.confirmCallback();
backdrop.remove();
delete window.confirmAction;
};
// Add to page
document.body.appendChild(backdrop);
// Close on backdrop click
backdrop.addEventListener('click', function(e) {
if (e.target === backdrop) {
backdrop.remove();
delete window.confirmAction;
}
});
}
function applyConfig(config) {
@@ -298,7 +517,7 @@ function applyConfig(config) {
document.getElementById('includeUpper').checked = config.includeUpper !== false;
document.getElementById('includeLower').checked = config.includeLower !== false;
document.getElementById('numberCount').value = config.numberCount || 1;
document.getElementById('specialChars').value = config.specialChars || "!@#$%^&*-_=+";
document.getElementById('specialChars').value = config.specialChars || "!@#$%&*-_=+.";
document.getElementById('noConsecutive').checked = config.noConsecutive || false;
document.getElementById('wordCount').value = config.wordCount || 3;
document.getElementById('passphraseUseNumbers').checked = config.useNumbers !== false;
@@ -306,16 +525,8 @@ function applyConfig(config) {
document.getElementById('numberPosition').value = config.numberPosition || "end";
document.getElementById('savePasswords').checked = config.savePasswords || false;
// Update tab state
if (currentMode === 'passphrase') {
document.getElementById('passphraseTab').classList.add('active');
document.getElementById('randomTab').classList.remove('active');
document.getElementById('passphraseControls').classList.add('active');
} else {
document.getElementById('randomTab').classList.add('active');
document.getElementById('passphraseTab').classList.remove('active');
document.getElementById('passphraseControls').classList.remove('active');
}
// Update tab state using the switchTab function to ensure proper styling
switchTab(currentMode);
}
// Password history management
@@ -376,21 +587,31 @@ function loadPasswordHistory() {
function togglePasswordSaving() {
const savePasswords = document.getElementById('savePasswords').checked;
const historyDiv = document.getElementById('passwordHistory');
const viewHistoryBtn = document.getElementById('viewHistoryBtn');
if (savePasswords) {
// Re-display existing history
const history = getPasswordHistory();
displayPasswordHistory(history);
showNotification('Password saving enabled', 'success');
// Show View History button if there's a password displayed
const passwordDisplay = document.getElementById('passwordDisplay').textContent;
if (passwordDisplay && passwordDisplay !== 'Click "Generate Password" to create a secure password') {
viewHistoryBtn.style.display = 'inline-block';
}
} else {
// Clear stored passwords and hide history display
document.cookie = 'passwordHistory=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
historyDiv.innerHTML = '<p style="color: #999; font-style: italic;">Password saving is disabled</p>';
showNotification('Password saving disabled - history cleared', 'info');
// Hide View History button
viewHistoryBtn.style.display = 'none';
}
// Auto-save the setting change
saveSettings();
// Auto-save the setting change without notification
autoSaveSettings();
}
function displayPasswordHistory(history) {
@@ -438,11 +659,16 @@ function copyHistoryPassword(password, index) {
}
function clearHistory() {
if (confirm('Are you sure you want to clear all password history?')) {
showConfirmationPopup(
'Clear Password History?',
'This will permanently delete all saved passwords from your browser. This action cannot be undone.',
function() {
// User confirmed
document.cookie = 'passwordHistory=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
loadPasswordHistory();
alert('Password history cleared.');
showPopup('Password history cleared.', 'info');
}
);
}
// Utility function to get cookie value
@@ -453,23 +679,16 @@ function getCookie(name) {
return null;
}
// Load word list info
function loadWordListInfo() {
fetch('/api/password/info')
.then(response => response.json())
.then(data => {
document.getElementById('wordListStatus').innerHTML =
`${data.wordCount} words loaded<br>Source: ${data.source}<br>Updated: ${data.lastUpdate}`;
})
.catch(error => {
document.getElementById('wordListStatus').textContent = 'Error loading word list info';
});
}
function generatePassword() {
// Validate special characters before generating
if (!validateSpecialChars()) {
showNotification('Please fix special characters field before generating password', 'error');
return;
}
const config = getCurrentConfig();
fetch('/api/password', {
fetch('/api/pwgenerator', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -478,35 +697,45 @@ function generatePassword() {
})
.then(response => response.text())
.then(password => {
document.getElementById('passwordDisplay').textContent = password;
document.getElementById('copyBtn').style.display = 'inline-block';
document.getElementById('generatedPassword').value = password;
// Update character count
document.getElementById('characterCount').textContent = `${password.length} characters`;
// Calculate and display strength
const strength = calculatePasswordStrength(password);
document.getElementById('strengthIndicator').innerHTML = strength;
const strengthInfo = calculatePasswordStrength(password);
document.getElementById('strengthIndicator').innerHTML = strengthInfo.html;
document.getElementById('strengthDisplay').textContent = strengthInfo.text;
// Add to history
addToHistory(password);
})
.catch(error => {
console.error('Error:', error);
document.getElementById('passwordDisplay').textContent = 'Error generating password: ' + error.message;
document.getElementById('generatedPassword').value = 'Error generating password';
document.getElementById('characterCount').textContent = '0 characters';
document.getElementById('strengthDisplay').textContent = 'Error';
});
}
function copyPassword() {
const password = document.getElementById('passwordDisplay').textContent;
if (password && password !== 'Click "Generate Password" to create a secure password') {
const password = document.getElementById('generatedPassword').value;
if (password && password !== 'Click \'Generate Password\' to create a new password') {
navigator.clipboard.writeText(password).then(function() {
const btn = document.getElementById('copyBtn');
btn.textContent = 'Copied!';
btn.classList.add('copied');
setTimeout(function() {
btn.textContent = 'Copy to Clipboard';
btn.classList.remove('copied');
}, 2000);
showNotification('Password copied to clipboard!', 'success');
}, function(err) {
console.error('Could not copy text: ', err);
showNotification('Failed to copy password', 'error');
});
}
}
function scrollToHistory() {
const historyElement = document.getElementById('passwordHistory');
if (historyElement) {
historyElement.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
}
@@ -538,37 +767,15 @@ function calculatePasswordStrength(password) {
else if (score >= 40) { strength = "Moderate"; color = "#ffaa00"; }
else if (score >= 20) { strength = "Weak"; color = "#ff8800"; }
return `<span style="color: ${color}; font-weight: bold;">${strength}</span> (${score}/100)`;
return {
html: `<span style="color: ${color}; font-weight: bold;">${strength}</span> (${score}/100)`,
text: strength
};
}
// Notification system
// Use the showPopup function from base.html instead of custom notifications
function showNotification(message, type = 'info') {
// Remove any existing notifications
const existingNotifications = document.querySelectorAll('.notification');
existingNotifications.forEach(notification => notification.remove());
// Create new notification
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.textContent = message;
// Add to page
document.body.appendChild(notification);
// Show with animation
setTimeout(() => {
notification.classList.add('show');
}, 10);
// Auto-remove after 5 seconds
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => {
if (notification.parentNode) {
notification.remove();
}
}, 300);
}, 5000);
showPopup(message, type);
}
</script>
{{end}}

774
web/pwgenerator.html Normal file
View File

@@ -0,0 +1,774 @@
{{template "base.html" .}}
{{define "title"}}Password Generator - HeaderAnalyzer{{end}}
{{define "content"}}
<div class="container mx-auto px-4 py-8 max-w-4xl">
<div class="text-center mb-8">
<a href="/pwgenerator" class="inline-block">
<h1 class="text-2xl md:text-3xl font-bold text-gray-100 hover:text-blue-400 transition-colors cursor-pointer mb-4">
🔐 Password Generator
</h1>
</a>
</div>
<!-- Hidden CSRF token for API calls -->
<input type="hidden" id="csrfToken" value="{{.CSRFToken}}">
<!-- Tab Buttons -->
<div class="flex space-x-2 mb-6 bg-gray-800 p-2 rounded-lg border border-gray-700">
<button class="flex-1 py-3 px-4 rounded-lg text-center font-medium transition-colors bg-gray-700 text-gray-300 hover:bg-gray-600" id="randomTab">
🎲 Random Password
</button>
<button class="flex-1 py-3 px-4 rounded-lg text-center font-medium transition-colors bg-blue-600 text-white" id="passphraseTab">
📝 Passphrase
</button>
</div>
<!-- Password Output -->
<!-- Generated Password Display -->
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700 mb-8">
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-4">
<h2 class="text-xl font-semibold text-gray-200">🔐 Generated Password</h2>
<div class="flex items-center gap-4 text-sm">
<span id="characterCount" class="text-gray-400 bg-gray-700 px-3 py-1 rounded-full">
0 characters
</span>
</div>
</div>
<div class="relative">
<input type="text" id="generatedPassword" readonly
class="w-full p-4 bg-gray-900 border border-gray-600 rounded-lg text-gray-100 font-mono text-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Click 'Generate Password' to create a new password">
<button onclick="copyPassword()"
class="absolute right-2 top-1/2 transform -translate-y-1/2 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500/20">
📋 Copy
</button>
</div>
</div>
<!-- Generate Button -->
<div class="flex flex-col sm:flex-row items-center justify-center gap-4 mb-8">
<button onclick="generatePassword()"
class="bg-green-600 hover:bg-green-700 text-white font-bold py-4 px-8 rounded-lg text-lg transition-all duration-200 transform hover:scale-105">
🎲 Generate Password
</button>
<button onclick="copyCurrentURL()"
class="bg-blue-600 hover:bg-blue-700 text-white font-medium py-4 px-6 rounded-lg transition-colors duration-200">
🔗 Copy URL with Settings
</button>
<button onclick="resetAllSettings()"
class="bg-red-600 hover:bg-red-700 text-white font-medium py-4 px-6 rounded-lg transition-colors duration-200">
🔄 Reset
</button>
</div>
<!-- Settings -->
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700 mb-8">
<div class="flex items-center justify-between mb-4 cursor-pointer" onclick="toggleSettings()">
<h3 class="text-xl font-semibold text-gray-200">🔧 Password Settings</h3>
<div class="flex items-center space-x-2">
<span id="settingsToggleText" class="text-sm text-gray-400">Click to expand</span>
<svg id="settingsChevron" class="w-5 h-5 text-gray-400 transform transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</div>
</div>
<div id="settingsContent" class="hidden">
<!-- Save Passwords Option -->
<div class="p-3 bg-gray-900 rounded-lg border border-gray-600 mb-6">
<div class="flex items-center space-x-3">
<input type="checkbox" id="savePasswords" onchange="togglePasswordSaving(); autoSaveSettings()"
class="w-4 h-4 text-blue-600 bg-gray-700 border-gray-600 rounded focus:ring-blue-500 focus:ring-2">
<label for="savePasswords" class="text-sm text-gray-300">
Save Generated Passwords (in web browser cookies only)
</label>
</div>
</div>
<!-- Settings Table -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- Left Column: Basic Password Settings -->
<div class="space-y-4">
<h4 class="text-lg font-medium text-gray-200 mb-4 border-b border-gray-600 pb-2">🎲 Random Password Settings</h4>
<div>
<label for="length" class="block text-sm font-medium text-gray-300 mb-2">Password Length:</label>
<input type="number" id="length" min="4" max="128" value="{{.Config.Length}}" onchange="updateURL(); autoSaveSettings()"
class="w-full px-3 py-2 bg-gray-900 border border-gray-600 rounded-lg text-gray-100 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 focus:outline-none">
</div>
<div class="flex items-center space-x-3">
<input type="checkbox" id="includeUpper" {{if .Config.IncludeUpper}}checked{{end}} onchange="updateURL(); autoSaveSettings()"
class="w-4 h-4 text-blue-600 bg-gray-700 border-gray-600 rounded focus:ring-blue-500 focus:ring-2">
<label for="includeUpper" class="text-sm text-gray-300">Include Uppercase (A-Z)</label>
</div>
<div class="flex items-center space-x-3">
<input type="checkbox" id="includeLower" {{if .Config.IncludeLower}}checked{{end}} onchange="updateURL(); autoSaveSettings()"
class="w-4 h-4 text-blue-600 bg-gray-700 border-gray-600 rounded focus:ring-blue-500 focus:ring-2">
<label for="includeLower" class="text-sm text-gray-300">Include Lowercase (a-z)</label>
</div>
<div>
<label for="numberCount" class="block text-sm font-medium text-gray-300 mb-2">Number of Digits:</label>
<input type="number" id="numberCount" min="0" max="20" value="{{.Config.NumberCount}}" onchange="updateURL(); autoSaveSettings()"
class="w-full px-3 py-2 bg-gray-900 border border-gray-600 rounded-lg text-gray-100 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 focus:outline-none">
</div>
<div>
<label for="specialChars" class="block text-sm font-medium text-gray-300 mb-2">Special Characters:</label>
<input type="text" id="specialChars" value="{{.Config.SpecialChars}}"
onchange="validateSpecialChars(); updateURL(); autoSaveSettings()"
oninput="validateSpecialChars()"
pattern="[!@#$%&*\-_=+.]*"
title="Only these special characters are allowed: !@#$%&*-_=+."
class="w-full px-3 py-2 bg-gray-900 border border-gray-600 rounded-lg text-gray-100 font-mono focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 focus:outline-none">
<div id="specialCharsError" class="text-red-400 text-sm mt-1 hidden">
Only these special characters are allowed: !@#$%&*-_=+.
</div>
<div class="text-gray-500 text-xs mt-1">
Allowed: !@#$%&*-_=+.
</div>
</div>
<div>
<label for="minSpecialChars" class="block text-sm font-medium text-gray-300 mb-2">Minimum Special Characters:</label>
<input type="number" id="minSpecialChars" min="0" max="10" value="{{.Config.MinSpecialChars}}"
onchange="updateURL(); autoSaveSettings()"
class="w-full px-3 py-2 bg-gray-900 border border-gray-600 rounded-lg text-gray-100 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 focus:outline-none">
<div class="text-gray-500 text-xs mt-1">
Minimum number of special characters to include in random passwords
</div>
</div>
<div class="flex items-center space-x-3">
<input type="checkbox" id="noConsecutive" {{if .Config.NoConsecutive}}checked{{end}} onchange="updateURL(); autoSaveSettings()"
class="w-4 h-4 text-blue-600 bg-gray-700 border-gray-600 rounded focus:ring-blue-500 focus:ring-2">
<label for="noConsecutive" class="text-sm text-gray-300">No consecutive identical characters</label>
</div>
</div>
<!-- Right Column: Passphrase Settings -->
<div class="space-y-4">
<h4 class="text-lg font-medium text-gray-200 mb-4 border-b border-gray-600 pb-2">📝 Passphrase Options</h4>
<div id="passphraseControls">
<div class="space-y-4">
<div>
<label for="wordCount" class="block text-sm font-medium text-gray-300 mb-2">Number of Words:</label>
<input type="number" id="wordCount" min="2" max="10" value="{{.Config.WordCount}}" onchange="updateURL(); autoSaveSettings()"
class="w-full px-3 py-2 bg-gray-900 border border-gray-600 rounded-lg text-gray-100 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 focus:outline-none">
</div>
<div class="flex items-center space-x-3">
<input type="checkbox" id="passphraseUseNumbers" {{if .Config.UseNumbers}}checked{{end}} onchange="updateURL(); autoSaveSettings()"
class="w-4 h-4 text-blue-600 bg-gray-700 border-gray-600 rounded focus:ring-blue-500 focus:ring-2">
<label for="passphraseUseNumbers" class="text-sm text-gray-300">Include Numbers</label>
</div>
<div class="flex items-center space-x-3">
<input type="checkbox" id="passphraseUseSpecial" {{if .Config.UseSpecial}}checked{{end}} onchange="updateURL(); autoSaveSettings()"
class="w-4 h-4 text-blue-600 bg-gray-700 border-gray-600 rounded focus:ring-blue-500 focus:ring-2">
<label for="passphraseUseSpecial" class="text-sm text-gray-300">Include Special Characters</label>
</div>
<div>
<label for="numberPosition" class="block text-sm font-medium text-gray-300 mb-2">Number Position:</label>
<select id="numberPosition" onchange="updateURL(); autoSaveSettings()"
class="w-full px-3 py-2 bg-gray-900 border border-gray-600 rounded-lg text-gray-100 focus:border-blue-500 focus:ring-2 focus:ring-blue-500/20 focus:outline-none">
<option value="end" {{if eq .Config.NumberPosition "end"}}selected{{end}}>At End</option>
<option value="start" {{if eq .Config.NumberPosition "start"}}selected{{end}}>At Start</option>
<option value="each" {{if eq .Config.NumberPosition "each"}}selected{{end}}>After Each Word</option>
</select>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Password History -->
<div class="bg-gray-800 rounded-lg p-6 border border-gray-700 mt-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-xl font-semibold text-gray-200">📚 Password History</h3>
<button onclick="clearHistory()"
class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white font-medium rounded-lg transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-red-500/20">
🗑️ Clear History
</button>
</div>
<div id="passwordHistory" class="bg-gray-900 border border-gray-600 rounded-lg p-4">
<p class="text-gray-400 italic">No passwords generated yet</p>
</div>
</div>
</div>
{{end}}
</div>
</div>
{{define "scripts"}}
<script>
let currentMode = 'passphrase'; // Default to passphrase
// Initialize the interface based on saved settings or URL parameters
document.addEventListener('DOMContentLoaded', function() {
// Load settings first (from URL parameters or cookies)
loadSettings();
// Update URL to reflect current state
updateURL();
// Load password history
loadPasswordHistory();
// Auto-generate if URL has parameters (excluding default)
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.toString()) {
generatePassword();
}
// Note: Removed auto-save event listeners to prevent excessive saving notifications
});
// Tab switching
function switchTab(mode) {
currentMode = mode;
// Get tab elements
const randomTab = document.getElementById('randomTab');
const passphraseTab = document.getElementById('passphraseTab');
// Remove active classes from both tabs
randomTab.classList.remove('bg-blue-600', 'text-white');
randomTab.classList.add('bg-gray-700', 'text-gray-300', 'hover:bg-gray-600');
passphraseTab.classList.remove('bg-blue-600', 'text-white');
passphraseTab.classList.add('bg-gray-700', 'text-gray-300', 'hover:bg-gray-600');
// Add active classes to the selected tab
if (mode === 'random') {
randomTab.classList.remove('bg-gray-700', 'text-gray-300', 'hover:bg-gray-600');
randomTab.classList.add('bg-blue-600', 'text-white');
} else {
passphraseTab.classList.remove('bg-gray-700', 'text-gray-300', 'hover:bg-gray-600');
passphraseTab.classList.add('bg-blue-600', 'text-white');
}
updateURL();
autoSaveSettings(); // Auto-save without notification
}
document.getElementById('randomTab').addEventListener('click', () => switchTab('random'));
document.getElementById('passphraseTab').addEventListener('click', () => switchTab('passphrase'));
// Toggle settings section
function toggleSettings() {
const content = document.getElementById('settingsContent');
const chevron = document.getElementById('settingsChevron');
const toggleText = document.getElementById('settingsToggleText');
if (content.classList.contains('hidden')) {
// Expand
content.classList.remove('hidden');
chevron.style.transform = 'rotate(180deg)';
toggleText.textContent = 'Click to minimize';
} else {
// Collapse
content.classList.add('hidden');
chevron.style.transform = 'rotate(0deg)';
toggleText.textContent = 'Click to expand';
}
}
// Validate special characters input
function validateSpecialChars() {
const input = document.getElementById('specialChars');
const errorDiv = document.getElementById('specialCharsError');
const allowedChars = '!@#$%&*-_=+.';
const value = input.value;
let isValid = true;
for (let i = 0; i < value.length; i++) {
if (!allowedChars.includes(value[i])) {
isValid = false;
break;
}
}
if (isValid) {
input.classList.remove('border-red-500', 'focus:border-red-500', 'focus:ring-red-500/20');
input.classList.add('border-gray-600', 'focus:border-blue-500', 'focus:ring-blue-500/20');
errorDiv.classList.add('hidden');
} else {
input.classList.remove('border-gray-600', 'focus:border-blue-500', 'focus:ring-blue-500/20');
input.classList.add('border-red-500', 'focus:border-red-500', 'focus:ring-red-500/20');
errorDiv.classList.remove('hidden');
}
return isValid;
}
// URL parameter management
function updateURL() {
const config = getCurrentConfig();
const params = new URLSearchParams();
// Define default values
const defaults = {
type: "passphrase",
length: 12,
includeUpper: true,
includeLower: true,
numberCount: 2,
specialChars: "!@#$%&*-_=+.",
minSpecialChars: 3,
noConsecutive: true,
wordCount: 3,
useNumbers: true,
useSpecial: false,
numberPosition: "end"
};
Object.keys(config).forEach(key => {
if (key !== 'savePasswords' && config[key] !== defaults[key]) {
params.set(key, config[key]);
}
});
// Update the URL without causing a page reload
const queryString = params.toString();
const newURL = queryString ? window.location.pathname + '?' + queryString : window.location.pathname;
window.history.replaceState({}, '', newURL);
}
function getCurrentConfig() {
return {
type: currentMode,
length: parseInt(document.getElementById('length').value),
includeUpper: document.getElementById('includeUpper').checked,
includeLower: document.getElementById('includeLower').checked,
numberCount: parseInt(document.getElementById('numberCount').value),
specialChars: document.getElementById('specialChars').value,
minSpecialChars: parseInt(document.getElementById('minSpecialChars').value),
noConsecutive: document.getElementById('noConsecutive').checked,
wordCount: parseInt(document.getElementById('wordCount').value),
useNumbers: document.getElementById('passphraseUseNumbers').checked,
useSpecial: document.getElementById('passphraseUseSpecial').checked,
numberPosition: document.getElementById('numberPosition').value,
savePasswords: document.getElementById('savePasswords').checked
};
}
// Cookie management
function saveSettings() {
const config = getCurrentConfig();
config.mode = currentMode;
const settings = JSON.stringify(config);
// Set cookie to expire in 1 year
const expiryDate = new Date();
expiryDate.setFullYear(expiryDate.getFullYear() + 1);
document.cookie = `passwordGenSettings=${encodeURIComponent(settings)}; expires=${expiryDate.toUTCString()}; path=/`;
showNotification('Settings saved! They will be remembered when you visit this page again.', 'success');
}
// Auto-save function without showing notification
function autoSaveSettings() {
const config = getCurrentConfig();
config.mode = currentMode;
const settings = JSON.stringify(config);
// Set cookie to expire in 1 year
const expiryDate = new Date();
expiryDate.setFullYear(expiryDate.getFullYear() + 1);
document.cookie = `passwordGenSettings=${encodeURIComponent(settings)}; expires=${expiryDate.toUTCString()}; path=/`;
}
// Copy current URL with settings
function copyCurrentURL() {
updateURL(); // Ensure URL is up to date
const currentURL = window.location.href;
navigator.clipboard.writeText(currentURL).then(function() {
showPopup('URL with current settings copied to clipboard!', 'success');
}, function(err) {
console.error('Could not copy URL: ', err);
showPopup('Failed to copy URL to clipboard', 'error');
});
}
// Reset all settings and cookies
function resetAllSettings() {
showConfirmationPopup(
'Reset All Settings?',
'This will clear all saved settings and password history, returning the page to its default state.',
function() {
// Clear all cookies
document.cookie = 'passwordGenSettings=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
document.cookie = 'passwordHistory=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
showPopup('All settings and history cleared. Redirecting to clean page...', 'info');
setTimeout(() => {
// Redirect to the page without any parameters
window.location.href = window.location.pathname;
}, 1500);
}
);
}
function loadSettings() {
// First try URL parameters
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.toString()) {
const config = {};
for (const [key, value] of urlParams) {
// Explicitly exclude savePasswords from URL parameter processing
if (key === 'savePasswords') {
continue; // Skip this parameter completely
}
else if (key === 'type') config[key] = value;
else if (key === 'length' || key === 'numberCount' || key === 'wordCount' || key === 'minSpecialChars') config[key] = parseInt(value);
else if (key === 'includeUpper' || key === 'includeLower' || key === 'noConsecutive' ||
key === 'useNumbers' || key === 'useSpecial') config[key] = value === 'true';
else config[key] = value;
}
applyConfig(config);
// Load savePasswords setting separately from cookies only
loadSavePasswordsSetting();
return;
}
// Then try cookies
const settings = getCookie('passwordGenSettings');
if (settings) {
try {
const config = JSON.parse(decodeURIComponent(settings));
applyConfig(config);
} catch (e) {
console.error('Failed to parse saved settings:', e);
}
}
}
// Load savePasswords setting from cookies only (never from URL)
function loadSavePasswordsSetting() {
const settings = getCookie('passwordGenSettings');
if (settings) {
try {
const config = JSON.parse(decodeURIComponent(settings));
if (config.savePasswords !== undefined) {
document.getElementById('savePasswords').checked = config.savePasswords;
// Update history display based on the setting
if (config.savePasswords) {
const history = getPasswordHistory();
displayPasswordHistory(history);
} else {
document.getElementById('passwordHistory').innerHTML = '<p style="color: #999; font-style: italic;">Password saving is disabled</p>';
}
}
} catch (e) {
console.error('Failed to parse saved settings for savePasswords:', e);
}
}
}
// Custom confirmation popup function
function showConfirmationPopup(title, message, onConfirm) {
// Create backdrop
const backdrop = document.createElement('div');
backdrop.className = 'fixed inset-0 z-50 bg-black bg-opacity-50 backdrop-blur-sm flex items-center justify-center p-4';
// Create popup
backdrop.innerHTML = `
<div class="bg-gray-800 border border-gray-600 rounded-xl p-6 max-w-md w-full shadow-2xl transform transition-all">
<div class="flex items-center justify-between mb-4">
<h3 class="text-xl font-bold text-red-400">${title}</h3>
<button onclick="this.closest('.fixed').remove()" class="text-gray-400 hover:text-gray-200 transition-colors">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<p class="text-gray-300 mb-6">${message}</p>
<div class="flex space-x-3 justify-end">
<button onclick="this.closest('.fixed').remove()"
class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors">
Cancel
</button>
<button onclick="confirmAction()"
class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg transition-colors">
Yes, Clear All
</button>
</div>
</div>
`;
// Add confirm action to the backdrop element
backdrop.confirmCallback = onConfirm;
// Add global confirmAction function temporarily
window.confirmAction = function() {
backdrop.confirmCallback();
backdrop.remove();
delete window.confirmAction;
};
// Add to page
document.body.appendChild(backdrop);
// Close on backdrop click
backdrop.addEventListener('click', function(e) {
if (e.target === backdrop) {
backdrop.remove();
delete window.confirmAction;
}
});
}
function applyConfig(config) {
// Apply the configuration to form controls
currentMode = config.type || config.mode || 'passphrase';
document.getElementById('length').value = config.length || 12;
document.getElementById('includeUpper').checked = config.includeUpper !== false;
document.getElementById('includeLower').checked = config.includeLower !== false;
document.getElementById('numberCount').value = config.numberCount || 2;
document.getElementById('specialChars').value = config.specialChars || "!@#$%&*-_=+.";
document.getElementById('minSpecialChars').value = config.minSpecialChars || 3;
document.getElementById('noConsecutive').checked = config.noConsecutive || true;
document.getElementById('wordCount').value = config.wordCount || 3;
document.getElementById('passphraseUseNumbers').checked = config.useNumbers !== false;
document.getElementById('passphraseUseSpecial').checked = config.useSpecial || false;
document.getElementById('numberPosition').value = config.numberPosition || "end";
document.getElementById('savePasswords').checked = config.savePasswords || false;
// Update tab state using the switchTab function to ensure proper styling
switchTab(currentMode);
}
// Password history management
function addToHistory(password) {
// Check if password saving is enabled
const savePasswords = document.getElementById('savePasswords').checked;
if (!savePasswords) {
return; // Don't save if checkbox is unchecked
}
let history = getPasswordHistory();
const timestamp = new Date().toLocaleString();
const entry = { password, timestamp, type: currentMode };
// Add to beginning of array
history.unshift(entry);
// Keep only last 20 passwords
if (history.length > 20) {
history = history.slice(0, 20);
}
// Save to cookie
const historyData = JSON.stringify(history);
const expiryDate = new Date();
expiryDate.setMonth(expiryDate.getMonth() + 3); // 3 months
document.cookie = `passwordHistory=${encodeURIComponent(historyData)}; expires=${expiryDate.toUTCString()}; path=/`;
// Update display
displayPasswordHistory(history);
}
function getPasswordHistory() {
const historyData = getCookie('passwordHistory');
if (historyData) {
try {
return JSON.parse(decodeURIComponent(historyData));
} catch (e) {
return [];
}
}
return [];
}
function loadPasswordHistory() {
const savePasswords = document.getElementById('savePasswords').checked;
if (savePasswords) {
const history = getPasswordHistory();
displayPasswordHistory(history);
} else {
const historyDiv = document.getElementById('passwordHistory');
historyDiv.innerHTML = '<p style="color: #999; font-style: italic;">Password saving is disabled</p>';
}
}
function togglePasswordSaving() {
const savePasswords = document.getElementById('savePasswords').checked;
const historyDiv = document.getElementById('passwordHistory');
const viewHistoryBtn = document.getElementById('viewHistoryBtn');
if (savePasswords) {
// Re-display existing history
const history = getPasswordHistory();
displayPasswordHistory(history);
showNotification('Password saving enabled', 'success');
// Show View History button if there's a password displayed
const passwordDisplay = document.getElementById('passwordDisplay');
if (passwordDisplay && passwordDisplay.textContent && passwordDisplay.textContent !== 'Click "Generate Password" to create a secure password') {
if (viewHistoryBtn) viewHistoryBtn.style.display = 'inline-block';
}
} else {
// Clear stored passwords and hide history display
document.cookie = 'passwordHistory=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
historyDiv.innerHTML = '<p style="color: #999; font-style: italic;">Password saving is disabled</p>';
showNotification('Password saving disabled - history cleared', 'info');
// Hide View History button
if (viewHistoryBtn) viewHistoryBtn.style.display = 'none';
}
// Auto-save the setting change without notification
autoSaveSettings();
}
function displayPasswordHistory(history) {
const historyDiv = document.getElementById('passwordHistory');
if (history.length === 0) {
historyDiv.innerHTML = '<p style="color: #999; font-style: italic;">No passwords generated yet</p>';
return;
}
let html = '';
history.forEach((entry, index) => {
const shortPassword = entry.password.length > 30 ? entry.password.substring(0, 30) + '...' : entry.password;
html += `
<div style="margin-bottom: 10px; padding: 8px; background: #2a2a2a; border-radius: 4px; display: flex; align-items: center; justify-content: space-between;">
<div style="flex: 1;">
<div style="font-family: monospace; color: #00ff88; margin-bottom: 2px;">${shortPassword}</div>
<div style="font-size: 12px; color: #999;">${entry.type}${entry.timestamp}</div>
</div>
<button onclick="copyHistoryPassword('${entry.password.replace(/'/g, "\\'")}', ${index})"
style="background: #007acc; color: white; border: none; padding: 4px 8px; border-radius: 3px; cursor: pointer; margin-left: 10px;">
Copy
</button>
</div>
`;
});
historyDiv.innerHTML = html;
}
function copyHistoryPassword(password, index) {
navigator.clipboard.writeText(password).then(function() {
// Temporarily change button text
const buttons = document.querySelectorAll('#passwordHistory button');
if (buttons[index]) {
const originalText = buttons[index].textContent;
buttons[index].textContent = 'Copied!';
buttons[index].style.background = '#00aa44';
setTimeout(function() {
buttons[index].textContent = originalText;
buttons[index].style.background = '#007acc';
}, 1500);
}
});
}
function clearHistory() {
showConfirmationPopup(
'Clear Password History?',
'This will permanently delete all saved passwords from your browser. This action cannot be undone.',
function() {
// User confirmed
document.cookie = 'passwordHistory=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
loadPasswordHistory();
showPopup('Password history cleared.', 'info');
}
);
}
// Utility function to get cookie value
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
return null;
}
function generatePassword() {
// Validate special characters before generating
if (!validateSpecialChars()) {
showNotification('Please fix special characters field before generating password', 'error');
return;
}
const config = getCurrentConfig();
fetch('/api/pwgenerator', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(config)
})
.then(response => response.text())
.then(password => {
document.getElementById('generatedPassword').value = password;
// Update character count
document.getElementById('characterCount').textContent = `${password.length} characters`;
// Add to history
addToHistory(password);
})
.catch(error => {
console.error('Error:', error);
document.getElementById('generatedPassword').value = 'Error generating password';
document.getElementById('characterCount').textContent = '0 characters';
});
}
function copyPassword() {
const password = document.getElementById('generatedPassword').value;
if (password && password !== 'Click \'Generate Password\' to create a new password') {
navigator.clipboard.writeText(password).then(function() {
showNotification('Password copied to clipboard!', 'success');
}, function(err) {
console.error('Could not copy text: ', err);
showNotification('Failed to copy password', 'error');
});
}
}
function scrollToHistory() {
const historyElement = document.getElementById('passwordHistory');
if (historyElement) {
historyElement.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
}
// Use the showPopup function from base.html instead of custom notifications
function showNotification(message, type = 'info') {
showPopup(message, type);
}
</script>
{{end}}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff