From 70a38701c172771dc21b20f0b8861ab7280e9879 Mon Sep 17 00:00:00 2001 From: Kanwaljeet Date: Fri, 29 May 2026 16:52:13 +0530 Subject: [PATCH] Added dev and devops tools --- app.py | 3 + favorites.json | 2 +- scripts/dev-tools/base64_tool.sh | 29 ++ scripts/devops-tools/docker_status.sh | 36 +++ scripts/devops-tools/resource_monitor.sh | 60 ++++ scripts/devops-tools/ssl_expiry.sh | 23 ++ ui/app.js | 388 +++++++++++++++++++++++ ui/index.html | 134 +++++++- ui/style.css | 140 ++++++++ 9 files changed, 812 insertions(+), 3 deletions(-) create mode 100644 scripts/dev-tools/base64_tool.sh create mode 100644 scripts/devops-tools/docker_status.sh create mode 100644 scripts/devops-tools/resource_monitor.sh create mode 100644 scripts/devops-tools/ssl_expiry.sh diff --git a/app.py b/app.py index 6258144..6f0656c 100644 --- a/app.py +++ b/app.py @@ -2288,6 +2288,7 @@ def parse_script_metadata(filepath): "name": os.path.basename(filepath).replace(".sh", "").replace("_", " ").title(), "desc": "", "tag": "", + "url": "", "path": filepath, } try: @@ -2300,6 +2301,8 @@ def parse_script_metadata(filepath): metadata["desc"] = line[7:].strip() elif line.startswith("# tag:"): metadata["tag"] = line[6:].strip() + elif line.startswith("# url:"): + metadata["url"] = line[6:].strip() elif not line.startswith("#") and line: break except Exception: # nosec B110 diff --git a/favorites.json b/favorites.json index f161a82..6237732 100644 --- a/favorites.json +++ b/favorites.json @@ -1 +1 @@ -["docker/list_containers.sh", "linux/test123.sh"] \ No newline at end of file +["docker/list_containers.sh", "linux/test123.sh", "devops-tools/docker_status.sh"] \ No newline at end of file diff --git a/scripts/dev-tools/base64_tool.sh b/scripts/dev-tools/base64_tool.sh new file mode 100644 index 0000000..8e4ebdd --- /dev/null +++ b/scripts/dev-tools/base64_tool.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# name: Base64 Utility +# desc: Quick base64 encoder and decoder. +# tag: dev-tools + +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[1;33m' +NC='\033[0m' + +echo -e "${BLUE}====================================================${NC}" +echo -e "${GREEN} BASE64 UTILITY ${NC}" +echo -e "${BLUE}====================================================${NC}" + +echo -e "Select action:" +echo -e "1) ${YELLOW}Encode${NC} text to Base64" +echo -e "2) ${YELLOW}Decode${NC} Base64 to text" +read -p "Enter Choice (1 or 2): " CHOICE + +if [ "${CHOICE}" = "1" ]; then + read -p "Enter text to encode: " PLAIN_TEXT + echo -n "${PLAIN_TEXT}" | base64 +elif [ "${CHOICE}" = "2" ]; then + read -p "Enter base64 string to decode: " B64_TEXT + echo -n "${B64_TEXT}" | base64 --decode + echo "" +else + echo "Invalid option selected." +fi diff --git a/scripts/devops-tools/docker_status.sh b/scripts/devops-tools/docker_status.sh new file mode 100644 index 0000000..1a6d4a1 --- /dev/null +++ b/scripts/devops-tools/docker_status.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# name: Docker Status +# desc: Checks docker service status and lists containers and images. +# tag: devops-tools + +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' + +echo -e "${BLUE}====================================================${NC}" +echo -e "${GREEN} DOCKER STATUS REPORT ${NC}" +echo -e "${BLUE}====================================================${NC}" + +if ! command -v docker &> /dev/null; then + echo -e "${RED}Error: Docker CLI is not installed on this machine.${NC}" + exit 1 +fi + +echo -e "\n${YELLOW}[Docker Daemon Health]${NC}" +if docker info &> /dev/null; then + echo -e "${GREEN}Docker daemon is running perfectly.${NC}" +else + echo -e "${RED}Docker daemon is not running or accessible without root.${NC}" + exit 1 +fi + +echo -e "\n${YELLOW}[Running Containers]${NC}" +docker ps --format "table {{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Ports}}" + +echo -e "\n${YELLOW}[Resource Usage Summary]${NC}" +docker stats --no-stream --format "table {{.Container}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}" 2>/dev/null || echo "Stats unavailable" + +echo -e "\n${YELLOW}[Active Images]${NC}" +docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}" | head -n 10 diff --git a/scripts/devops-tools/resource_monitor.sh b/scripts/devops-tools/resource_monitor.sh new file mode 100644 index 0000000..f7399db --- /dev/null +++ b/scripts/devops-tools/resource_monitor.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# name: Resource Alarm Monitor +# desc: Continuously checks CPU, Memory, and Disk, throwing colorful alerts when thresholds are breached. +# tag: devops-tools + +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' + +CPU_THRESHOLD=80 +MEM_THRESHOLD=85 +DISK_THRESHOLD=90 + +echo -e "${BLUE}====================================================${NC}" +echo -e "${GREEN} RESOURCE ALARM MONITOR ${NC}" +echo -e "${BLUE}====================================================${NC}" + +echo -e "CPU Alert Limit: ${YELLOW}${CPU_THRESHOLD}%${NC}" +echo -e "RAM Alert Limit: ${YELLOW}${MEM_THRESHOLD}%${NC}" +echo -e "Disk Alert Limit: ${YELLOW}${DISK_THRESHOLD}%${NC}\n" + +DISK_USAGE=$(df -h / | tail -1 | awk '{print $5}' | sed 's/%//') +if [ "${DISK_USAGE}" -gt "${DISK_THRESHOLD}" ]; then + echo -e "${RED}[ALERT] Disk usage is critically high: ${DISK_USAGE}%${NC}" +else + echo -e "${GREEN}[OK] Disk Usage: ${DISK_USAGE}%${NC}" +fi + +if [ "$(uname)" = "Darwin" ]; then + MEM_USAGE=$(memory_pressure | grep "System-wide memory free percentage" | awk '{print 100 - $5}') + if [ -z "${MEM_USAGE}" ]; then + MEM_USAGE=$(vm_stat | awk '/free/ {free=$3} /active/ {active=$3} /inactive/ {inactive=$3} END {total=free+active+inactive; print (active+inactive)/total*100}' | cut -d. -f1) + fi +else + MEM_USAGE=$(free | grep Mem | awk '{print int($3/$2 * 100)}') +fi + +if [ -n "${MEM_USAGE}" ]; then + if [ "${MEM_USAGE}" -gt "${MEM_THRESHOLD}" ]; then + echo -e "${RED}[ALERT] Memory usage is critically high: ${MEM_USAGE}%${NC}" + else + echo -e "${GREEN}[OK] Memory Usage: ${MEM_USAGE}%${NC}" + fi +fi + +if [ "$(uname)" = "Darwin" ]; then + CPU_USAGE=$(ps -A -o %cpu | awk '{s+=$1} END {print int(s)}') +else + CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print int(100 - $1)}') +fi + +if [ -n "${CPU_USAGE}" ]; then + if [ "${CPU_USAGE}" -gt "${CPU_THRESHOLD}" ]; then + echo -e "${RED}[ALERT] CPU load is high: ${CPU_USAGE}%${NC}" + else + echo -e "${GREEN}[OK] CPU Load: ${CPU_USAGE}%${NC}" + fi +fi diff --git a/scripts/devops-tools/ssl_expiry.sh b/scripts/devops-tools/ssl_expiry.sh new file mode 100644 index 0000000..b8c298e --- /dev/null +++ b/scripts/devops-tools/ssl_expiry.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# name: SSL Expiry Checker +# desc: Check and verify SSL/TLS certificate validity and expiration for any web host. +# tag: devops-tools, ssl +# url: https://www.ssllabs.com/ssltest/ + +echo "=== SSL Expiry Checker ===" +read -p "Enter domain (e.g. google.com): " domain +if [ -z "$domain" ]; then + domain="google.com" +fi +echo "Connecting to $domain:443..." +echo "" +if ! command -v openssl >/dev/null 2>&1; then + echo "Error: openssl utility is not installed." + exit 1 +fi +res=$(echo | openssl s_client -servername "$domain" -connect "$domain":443 2>/dev/null | openssl x509 -noout -dates -issuer) +if [ -z "$res" ]; then + echo "Failed to retrieve SSL certificate details." +else + echo "$res" +fi diff --git a/ui/app.js b/ui/app.js index c4fb694..b43882d 100644 --- a/ui/app.js +++ b/ui/app.js @@ -3368,6 +3368,33 @@ function bindEvents() { document.getElementById('btn-add-script').addEventListener('click', () => openModal('new')); document.getElementById('btn-refresh').addEventListener('click', () => loadScripts()); + // Predefined Tools Dropdown toggle + const btnToolsDropdown = document.getElementById('btn-tools-dropdown'); + const toolsDropdownMenu = document.getElementById('tools-dropdown-menu'); + if (btnToolsDropdown && toolsDropdownMenu) { + btnToolsDropdown.addEventListener('click', (e) => { + e.stopPropagation(); + toolsDropdownMenu.classList.toggle('active'); + }); + + // Hide dropdown when clicking outside + document.addEventListener('click', (e) => { + if (!toolsDropdownMenu.contains(e.target) && e.target !== btnToolsDropdown) { + toolsDropdownMenu.classList.remove('active'); + } + }); + + // Event delegation for dropdown items + toolsDropdownMenu.querySelectorAll('.dropdown-item').forEach(item => { + item.addEventListener('click', (e) => { + e.stopPropagation(); + toolsDropdownMenu.classList.remove('active'); + const toolKey = item.getAttribute('data-tool'); + loadPredefinedScript(toolKey); + }); + }); + } + // Script Details Actions const btnRun = document.getElementById('btn-run'); if (btnRun) { @@ -4650,3 +4677,364 @@ if (!window.hasRegisteredLifecycleCleanup) { } }); } + +// ─── Predefined Dev & DevOps Tools Templates ──────────────── +const PREDEFINED_TOOLS = { + sys_info: { + name: "System Info Dashboard", + category: "dev-tools", + filename: "system_info.sh", + url: "https://explainshell.com", + content: `#!/bin/bash +# name: System Info Dashboard +# desc: Detailed system diagnostic displaying CPU load, Memory stats, Disk status, and network details. +# tag: dev-tools, system +# url: https://explainshell.com + +echo "=== System Info Dashboard ===" +echo "Date: $(date)" +echo "OS: $(uname -a)" +echo "" +echo "--- CPU LOAD ---" +uptime +echo "" +echo "--- MEMORY USAGE ---" +if [[ "$OSTYPE" == "darwin"* ]]; then + vm_stat | perl -ne '/page size of (\\d+) bytes/ && ($s=$1); /Pages free:\\s+(\\d+)/ && ($f=$1); /Pages active:\\s+(\\d+)/ && ($a=$1); /Pages inactive:\\s+(\\d+)/ && ($i=$1); /Pages speculative:\\s+(\\d+)/ && ($sp=$1); /Pages wired down:\\s+(\\d+)/ && ($w=$1); END { printf "Active: %.2fMB\\nInactive: %.2fMB\\nSpeculative: %.2fMB\\nWired: %.2fMB\\nFree: %.2fMB\\n", ($a*$s)/1048576, ($i*$s)/1048576, ($sp*$s)/1048576, ($w*$s)/1048576, ($f*$s)/1048576 }' +else + free -m +fi +echo "" +echo "--- DISK SPACE ---" +df -h | grep -E '^/dev/' || df -h +echo "" +echo "--- NETWORK DEVICES ---" +ifconfig -a || ip a +` + }, + port_scanner: { + name: "Process Port Lister", + category: "dev-tools", + filename: "process_port_lister.sh", + url: "https://portchecktool.com", + content: `#!/bin/bash +# name: Process Port Lister +# desc: Scan and list all active listening ports and the respective processes bound to them. +# tag: dev-tools, network +# url: https://portchecktool.com + +echo "=== Process Port Lister ===" +echo "Scanning for active listening sockets..." +echo "" +if command -v lsof >/dev/null 2>&1; then + lsof -i -P -n | grep LISTEN || echo "No listening ports found via lsof." +elif command -v netstat >/dev/null 2>&1; then + netstat -antp | grep LISTEN || netstat -an | grep LISTEN || echo "No listening ports found via netstat." +elif command -v ss >/dev/null 2>&1; then + ss -lntp || ss -ln || echo "No listening ports found via ss." +else + echo "Error: Neither lsof, netstat, nor ss found on this system." +fi +` + }, + find_large_files: { + name: "Find Large Files (>100MB)", + category: "dev-tools", + filename: "find_large_files.sh", + url: "https://tldr.sh", + content: `#!/bin/bash +# name: Find Large Files (>100MB) +# desc: Search the current directory recursively and list all files larger than 100MB. +# tag: dev-tools, search +# url: https://tldr.sh + +TARGET_DIR="." +echo "=== Find Large Files (>100MB) ===" +echo "Searching in: $TARGET_DIR" +echo "This might take a moment..." +echo "" +find "$TARGET_DIR" -type f -size +100M -exec du -h {} + 2>/dev/null | sort -rh || find "$TARGET_DIR" -type f -size +100000k -exec du -h {} + 2>/dev/null | sort -rh +echo "" +echo "Search complete." +` + }, + json_formatter: { + name: "JSON Formatter/Validator", + category: "dev-tools", + filename: "json_formatter.sh", + url: "https://jsonformatter.org", + content: `#!/bin/bash +# name: JSON Formatter/Validator +# desc: Pipe JSON input directly into Python's json.tool for format-indenting and validity checking. +# tag: dev-tools, json +# url: https://jsonformatter.org + +echo "=== JSON Formatter/Validator ===" +echo "Enter/Paste your raw JSON content below, then press Ctrl+D to format:" +echo "" +TMP_FILE=$(mktemp) +cat > "$TMP_FILE" +echo "" +echo "--- Formatted Output ---" +if command -v python3 >/dev/null 2>&1; then + python3 -m json.tool "$TMP_FILE" +elif command -v python >/dev/null 2>&1; then + python -m json.tool "$TMP_FILE" +elif command -v jq >/dev/null 2>&1; then + jq . "$TMP_FILE" +else + echo "Error: Neither python3, python, nor jq found to parse JSON." + cat "$TMP_FILE" +fi +rm -f "$TMP_FILE" +` + }, + base64_util: { + name: "Base64 Encode/Decode", + category: "dev-tools", + filename: "base64_util.sh", + url: "https://www.base64decode.org", + content: `#!/bin/bash +# name: Base64 Encode/Decode +# desc: Easily encode regular text or decode encoded Base64 strings. +# tag: dev-tools, utility +# url: https://www.base64decode.org + +echo "=== Base64 Utility ===" +echo "1) Encode Text to Base64" +echo "2) Decode Base64 to Text" +read -p "Select option (1 or 2): " choice +echo "" +if [ "$choice" = "1" ]; then + read -p "Enter text to encode: " txt + echo -n "$txt" | base64 +elif [ "$choice" = "2" ]; then + read -p "Enter Base64 string to decode: " b64 + echo -n "$b64" | base64 --decode || echo -n "$b64" | base64 -d +else + echo "Invalid choice." +fi +echo "" +` + }, + docker_status: { + name: "Docker Container Status", + category: "devops-tools", + filename: "docker_status.sh", + url: "https://hub.docker.com", + content: `#!/bin/bash +# name: Docker Container Status +# desc: Check if docker daemon is running, inspect CPU/memory stats, and list active containers. +# tag: devops-tools, docker +# url: https://hub.docker.com + +echo "=== Docker Container Status ===" +if ! command -v docker >/dev/null 2>&1; then + echo "Error: docker command line tool is not installed." + exit 1 +fi +echo "--- Docker System Info ---" +docker info --format 'Containers: {{.Containers}}, Running: {{.ContainersRunning}}, Paused: {{.ContainersPaused}}, Stopped: {{.ContainersStopped}}' || echo "Error connecting to Docker Daemon." +echo "" +echo "--- Container Resource Stats ---" +docker stats --no-stream --format "table {{.Name}}\\t{{.CPUPerc}}\\t{{.MemUsage}}\\t{{.NetIO}}" 2>/dev/null || echo "No running containers or stats unavailable." +echo "" +echo "--- All Containers ---" +docker ps -a --format "table {{.Names}}\\t{{.Image}}\\t{{.Status}}\\t{{.Ports}}" +` + }, + ssl_expiry: { + name: "SSL Expiry Checker", + category: "devops-tools", + filename: "ssl_expiry.sh", + url: "https://www.ssllabs.com/ssltest/", + content: `#!/bin/bash +# name: SSL Expiry Checker +# desc: Check and verify SSL/TLS certificate validity and expiration for any web host. +# tag: devops-tools, ssl +# url: https://www.ssllabs.com/ssltest/ + +echo "=== SSL Expiry Checker ===" +read -p "Enter domain (e.g. google.com): " domain +if [ -z "$domain" ]; then + domain="google.com" +fi +echo "Connecting to $domain:443..." +echo "" +if ! command -v openssl >/dev/null 2>&1; then + echo "Error: openssl utility is not installed." + exit 1 +fi +res=$(echo | openssl s_client -servername "$domain" -connect "$domain":443 2>/dev/null | openssl x509 -noout -dates -issuer) +if [ -z "$res" ]; then + echo "Failed to retrieve SSL certificate details." +else + echo "$res" +fi +` + }, + git_diagnostics: { + name: "Git Repo Diagnostics", + category: "devops-tools", + filename: "git_diagnostics.sh", + url: "https://github.com", + content: `#!/bin/bash +# name: Git Repo Diagnostics +# desc: Run a detailed diagnostics audit on the local Git repository branches, statuses, and remotes. +# tag: devops-tools, git +# url: https://github.com + +echo "=== Git Repo Diagnostics ===" +if ! command -v git >/dev/null 2>&1; then + echo "Error: git is not installed." + exit 1 +fi +if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + echo "Error: Current directory is not a Git repository." + exit 1 +fi +echo "Current Branch: $(git branch --show-current)" +echo "Git Root: $(git rev-parse --show-toplevel)" +echo "" +echo "--- Git Status ---" +git status -s +echo "" +echo "--- Recent Commits ---" +git log -n 5 --oneline +echo "" +echo "--- Configured Remotes ---" +git remote -v +` + }, + resource_alarm: { + name: "Resource Alarm Monitor", + category: "devops-tools", + filename: "resource_alarm.sh", + url: "https://grafana.com", + content: `#!/bin/bash +# name: Resource Alarm Monitor +# desc: Continuously monitor host disk and memory usage, triggering high usage console alerts. +# tag: devops-tools, monitoring +# url: https://grafana.com + +DISK_THRESHOLD=80 +MEM_THRESHOLD=85 + +echo "=== Resource Alarm Monitor ===" +echo "Disk Warning Limit: $DISK_THRESHOLD%" +echo "Memory Warning Limit: $MEM_THRESHOLD%" +echo "" + +# Disk Check +disk_val=$(df / | tail -1 | awk '{print $5}' | sed 's/%//') +if [ "$disk_val" -gt "$DISK_THRESHOLD" ]; then + echo "⚠️ ALARM: Disk usage on root / is at \${disk_val}%!" +else + echo "Disk usage is normal: \${disk_val}%" +fi + +# Memory Check +if [[ "$OSTYPE" == "darwin"* ]]; then + # MacOS memory approximation + free_pages=$(vm_stat | grep "Pages free" | awk '{print $3}' | sed 's/\\.//') + active_pages=$(vm_stat | grep "Pages active" | awk '{print $3}' | sed 's/\\.//') + spec_pages=$(vm_stat | grep "Pages speculative" | awk '{print $3}' | sed 's/\\.//') + wire_pages=$(vm_stat | grep "Pages wired" | awk '{print $3}' | sed 's/\\.//') + used_pages=$((active_pages + wire_pages + spec_pages)) + total_pages=$((used_pages + free_pages)) + mem_val=$((used_pages * 100 / total_pages)) +else + mem_val=$(free | grep Mem | awk '{print int($3/$2 * 100)}') +fi + +if [ "$mem_val" -gt "$MEM_THRESHOLD" ]; then + echo "⚠️ ALARM: Memory usage is at \${mem_val}%!" +else + echo "Memory usage is normal: \${mem_val}%" +fi +` + }, + k8s_pods: { + name: "Kubernetes Pod Status", + category: "devops-tools", + filename: "k8s_pods.sh", + url: "https://kubernetes.io/docs/", + content: `#!/bin/bash +# name: Kubernetes Pod Status +# desc: Connect to configured cluster, check node connectivity, list pods across namespaces. +# tag: devops-tools, kubernetes +# url: https://kubernetes.io/docs/ + +echo "=== Kubernetes Pod Status ===" +if ! command -v kubectl >/dev/null 2>&1; then + echo "Error: kubectl command-line tool is not installed." + exit 1 +fi +echo "--- Cluster Info ---" +kubectl cluster-info || echo "Error connecting to Kubernetes Cluster." +echo "" +echo "--- Nodes Status ---" +kubectl get nodes 2>/dev/null || echo "No nodes found or connection failed." +echo "" +echo "--- Pods (All Namespaces) ---" +kubectl get pods --all-namespaces +` + }, + nginx_tester: { + name: "Nginx Config Integrity", + category: "devops-tools", + filename: "nginx_tester.sh", + url: "https://nginx.org/en/docs/", + content: `#!/bin/bash +# name: Nginx Config Integrity +# desc: Validate host Nginx configuration syntax and test responsiveness of localhost. +# tag: devops-tools, nginx +# url: https://nginx.org/en/docs/ + +echo "=== Nginx Config Integrity ===" +if ! command -v nginx >/dev/null 2>&1; then + echo "Error: nginx command is not installed or not in PATH." + exit 1 +fi +echo "--- Testing Configuration Syntax ---" +nginx -t 2>&1 || sudo nginx -t 2>&1 +echo "" +echo "--- Nginx System Process Status ---" +if command -v systemctl >/dev/null 2>&1; then + systemctl status nginx --no-pager || echo "systemctl failed to get status." +else + ps aux | grep nginx | grep -v grep +fi +` + } +}; + +async function loadPredefinedScript(key) { + const template = PREDEFINED_TOOLS[key]; + if (!template) return; + + try { + notify(`Linking ${template.name}...`, 'info'); + const res = await fetch(API.save, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + category: template.category, + filename: template.filename, + content: template.content, + password: getUnlockPassword(`${template.category}/${template.filename}`) + }) + }); + const data = await res.json(); + if (data.success) { + await loadScripts(); + await selectScript(data.path); + notify(`${template.name} has been linked and is ready to run!`, 'success'); + } else { + notify(`Failed to link script: ${data.error || 'unknown error'}`, 'error'); + } + } catch (err) { + console.error('Failed to link predefined script:', err); + notify(`Failed to link script: ${err.message}`, 'error'); + } +} diff --git a/ui/index.html b/ui/index.html index cb41145..bc78c4a 100644 --- a/ui/index.html +++ b/ui/index.html @@ -12,7 +12,7 @@ - + @@ -77,6 +77,15 @@

DevShell

+ + + +
+ + + + + + + + + + + + + + + + +
@@ -120,8 +182,72 @@

Scripts

+ 0 + + +
+ + + + + + + + + + + + + + + + +
- +
diff --git a/ui/style.css b/ui/style.css index 79b4ee0..292c83e 100644 --- a/ui/style.css +++ b/ui/style.css @@ -372,6 +372,146 @@ body { align-items: center; justify-content: space-between; border-bottom: 1px solid var(--border); + position: relative; +} + +/* Predefined Dev & DevOps Tools Dropdown styling */ +.tools-dropdown-menu { + position: absolute; + top: 48px; + left: 20px; + width: 220px; + background: var(--bg-card); + border: 1px solid var(--border-bright); + border-radius: var(--radius-lg); + box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.6), 0 8px 10px -6px rgba(0, 0, 0, 0.6); + z-index: 1000; + padding: 6px 0; + display: none; + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); +} + +.tools-dropdown-menu.active { + display: block; + animation: dropdownFade 0.18s cubic-bezier(0.16, 1, 0.3, 1) forwards; +} + +@keyframes dropdownFade { + from { + opacity: 0; + transform: translateY(-8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.tools-dropdown-menu .dropdown-header { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.8px; + color: var(--text-muted); + padding: 8px 14px 4px; + user-select: none; +} + +.tools-dropdown-menu .dropdown-item { + display: flex; + align-items: center; + padding: 8px 14px; + font-size: 12px; + color: var(--text-secondary); + transition: var(--transition-fast); + cursor: pointer; + user-select: none; +} + +.tools-dropdown-menu .dropdown-item svg { + color: var(--text-muted); + transition: var(--transition-fast); +} + +.tools-dropdown-menu .dropdown-item:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +.tools-dropdown-menu .dropdown-item:hover svg { + color: var(--accent); + transform: scale(1.08); +} + +.tools-dropdown-menu .dropdown-divider { + height: 1px; + background: var(--border); + margin: 6px 0; +} + +/* Header Predefined Dev & DevOps Tools Dropdown styling */ +.header-tools-dropdown { + position: absolute; + top: 50px; + right: 70px; + width: 230px; + background: var(--bg-card); + border: 1px solid var(--border-bright); + border-radius: var(--radius-lg); + box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.6), 0 8px 10px -6px rgba(0, 0, 0, 0.6); + z-index: 1000; + padding: 6px 0; + display: none; + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); +} + +.header-tools-dropdown.active { + display: block; + animation: dropdownFade 0.18s cubic-bezier(0.16, 1, 0.3, 1) forwards; +} + +.header-tools-dropdown .dropdown-header { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.8px; + color: var(--text-muted); + padding: 8px 14px 4px; + user-select: none; +} + +.header-tools-dropdown .dropdown-item { + display: flex; + align-items: center; + padding: 8px 14px; + font-size: 12px; + color: var(--text-secondary); + transition: var(--transition-fast); + cursor: pointer; + user-select: none; +} + +.header-tools-dropdown .dropdown-item svg { + color: var(--text-muted); + transition: var(--transition-fast); +} + +.header-tools-dropdown .dropdown-item:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +.header-tools-dropdown .dropdown-item:hover svg { + color: var(--accent); + transform: scale(1.08); +} + +.header-tools-dropdown .dropdown-divider { + height: 1px; + background: var(--border); + margin: 6px 0; } .sidebar-title {