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"
)
@@ -17,17 +18,18 @@ func PasswordAPIHandler(w http.ResponseWriter, r *http.Request) {
}
var requestData struct {
Type string `json:"type"`
Length int `json:"length"`
IncludeUpper bool `json:"includeUpper"`
IncludeLower bool `json:"includeLower"`
NumberCount int `json:"numberCount"`
SpecialChars string `json:"specialChars"`
NoConsecutive bool `json:"noConsecutive"`
WordCount int `json:"wordCount"`
NumberPosition string `json:"numberPosition"`
UseNumbers bool `json:"useNumbers"`
UseSpecial bool `json:"useSpecial"`
Type string `json:"type"`
Length int `json:"length"`
IncludeUpper bool `json:"includeUpper"`
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"`
UseNumbers bool `json:"useNumbers"`
UseSpecial bool `json:"useSpecial"`
}
if err := json.NewDecoder(r.Body).Decode(&requestData); err != nil {
@@ -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

@@ -19,18 +19,18 @@ type Handler struct {
}
type PasswordConfig struct {
Type string
Length int
IncludeUpper bool
IncludeLower bool
NumberCount int
SpecialChars string
NoConsecutive bool
WordCount int
UseNumbers bool
UseSpecial bool
NumberPosition string
SavePasswords bool
Type string
Length int
IncludeUpper bool
IncludeLower bool
NumberCount int
SpecialChars string
MinSpecialChars int
NoConsecutive bool
WordCount int
UseNumbers bool
UseSpecial bool
NumberPosition string
}
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,24 +72,24 @@ 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
}
// Parse URL parameters to set default values
config := PasswordConfig{
Type: getStringParam(r, "type", "passphrase"),
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),
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),
Type: getStringParam(r, "type", "passphrase"),
Length: getIntParam(r, "length", 12),
IncludeUpper: getBoolParam(r, "includeUpper", true),
IncludeLower: getBoolParam(r, "includeLower", true),
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"),
}
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
if err := p.validator.ValidatePassword(req.Password); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
// Only validate password if one is provided (passwords are optional)
if req.Password != "" {
if err := p.validator.ValidatePassword(req.Password); err != nil {
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>
<!-- 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>
<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,59 +2,169 @@
{{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">
<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)" 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 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>
<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>
</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="dns-results" id="dnsResults"></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,59 +216,74 @@ 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 = '';
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';
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');
}
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);
}
</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>
{{if not .From}}
<form method="POST">
<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}}
<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>
{{end}}
</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>
<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="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="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>
{{if .SenderRep}}
<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>
<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>
{{if .Blacklists}}
<span class="status error" title="{{range .Blacklists}}{{.}}, {{end}}">Blacklisted {{len .Blacklists}} times</span>
{{else}}
<span class="status good">Not listed on major blacklists</span>
{{end}}
</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.
</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>
</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}}">
{{.SenderRep}}
<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 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 class="divide-y divide-gray-600">
{{range $k, $v := .AllHeaders}}
<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>
{{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>
If these differ, the message may be sent on behalf of another user or via a third-party service.
</small>
</div>
</div>
</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>
<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>
</tr>
</thead>
<tbody>
{{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>
{{end}}
</tbody>
</table>
</div>
</details>
<div class="section">
<h2>Basic Information</h2>
<div class="grid">
<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}}
</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>
</div>
</div>
</details>
</div>
<div class="section">
<h2>Mail Flow</h2>
<ul class="mail-flow">
{{range .Received}}<li>{{.}}</li>{{end}}
</ul>
</div>
{{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}}
{{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}}
<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}}">
{{if .SPFPass}}✓ Passed{{else}}✗ Failed{{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>
<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>
<p>{{.SPFDetails}}</p>
{{if .SPFRecord}}<pre>{{.SPFRecord}}</pre>{{end}}
<div>
<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>
<!-- 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="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}}
<!-- 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 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}}">
{{if .DMARCPass}}✓ Passed{{else}}✗ Failed{{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}}">
{{if .DKIMPass}}✓ Present{{else}}✗ Missing{{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>
{{if .SendingServer}}IP {{.SendingServer}}{{else if .FromDomain}}Domain {{.FromDomain}}{{end}}
<!-- 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}}">
{{if gt (len .SpamFlags) 0}}⚠️ Spam Indicators Found{{else}}✓ No Spam Indicators{{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}}
@@ -354,6 +589,10 @@
function contains(str, substr) {
return str.includes(substr);
}
function add(a, b) {
return a + b;
}
function exportImage() {
if (typeof html2canvas === 'undefined') {

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()">
<!-- 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 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()">
</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>
<div class="form-row">
<label for="passphraseUseNumbers">Include Numbers:</label>
<input type="checkbox" id="passphraseUseNumbers" {{if .Config.UseNumbers}}checked{{end}} onchange="updateURL()">
</div>
<div class="form-row">
<label for="passphraseUseSpecial">Include Special Characters:</label>
<input type="checkbox" id="passphraseUseSpecial" {{if .Config.UseSpecial}}checked{{end}} onchange="updateURL()">
</div>
<div class="form-row">
<label for="numberPosition">Number Position:</label>
<select id="numberPosition" onchange="updateURL()">
<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>
</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 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>
<!-- 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>
<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>
<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>
</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');
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) {
showNotification('Error loading settings: ' + e.message, 'error');
console.error('Failed to parse saved settings for savePasswords:', e);
}
} else {
showNotification('No saved settings found.', 'warning');
}
}
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?')) {
document.cookie = 'passwordHistory=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
loadPasswordHistory();
alert('Password history cleared.');
}
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
@@ -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