Squid dashboard

 cat > /opt/squid-dashboard/app.py << 'PYEOF'

from flask import Flask, request, jsonify, session, redirect, render_template_string

import subprocess, re


app = Flask(__name__)

app.secret_key = 'squid2024'


SQUID_CONF = '/etc/squid/squid.conf'

LOG_FILE = '/var/log/squid/access.log'


USERS = {

    'admin': {'password': 'Squid@2024', 'role': 'admin'},

    'junior': {'password': 'Junior@2024', 'role': 'junior'}

}


def login_required(f):

    from functools import wraps

    @wraps(f)

    def decorated(*args, **kwargs):

        if 'user' not in session:

            return redirect('/login')

        return f(*args, **kwargs)

    return decorated


def read_conf():

    with open(SQUID_CONF, 'r') as f:

        return f.read()


def write_conf(content):

    with open(SQUID_CONF, 'w') as f:

        f.write(content)


def get_ips_from_acl(acl_name):

    conf = read_conf()

    pattern = r'acl ' + acl_name + r' src (.+)'

    match = re.search(pattern, conf)

    if match:

        return match.group(1).strip().split()

    return []


def set_ips_in_acl(acl_name, ips):

    conf = read_conf()

    ip_str = ' '.join(ips)

    new_line = 'acl ' + acl_name + ' src ' + ip_str

    conf = re.sub(r'acl ' + acl_name + r' src .+', new_line, conf)

    write_conf(conf)


LOGIN_PAGE = '''<!DOCTYPE html>

<html>

<head><title>Squid Login</title>

<style>

*{margin:0;padding:0;box-sizing:border-box}

body{font-family:Arial,sans-serif;background:#1e3a5f;display:flex;align-items:center;justify-content:center;min-height:100vh}

.box{background:#fff;padding:2rem;border-radius:10px;width:340px;box-shadow:0 8px 32px rgba(0,0,0,0.3)}

h2{text-align:center;color:#1e3a5f;margin-bottom:1.5rem;font-size:20px}

input{width:100%;padding:10px 12px;margin-bottom:12px;border:1px solid #ddd;border-radius:6px;font-size:14px}

button{width:100%;padding:11px;background:#1e3a5f;color:#fff;border:none;border-radius:6px;font-size:15px;cursor:pointer}

button:hover{background:#16305a}

.err{background:#fed7d7;color:#c53030;padding:8px 12px;border-radius:6px;font-size:13px;margin-bottom:12px}

</style></head>

<body>

<div class="box">

<h2>Squid Control Panel</h2>

{% if error %}<div class="err">{{ error }}</div>{% endif %}

<form method="post">

<input name="username" placeholder="Username" required>

<input name="password" type="password" placeholder="Password" required>

<button type="submit">Login</button>

</form>

</div>

</body>

</html>'''


MAIN_PAGE = '''<!DOCTYPE html>

<html>

<head><title>Squid Dashboard</title>

<style>

*{margin:0;padding:0;box-sizing:border-box}

body{font-family:Arial,sans-serif;background:#f0f4f8;color:#222}

.bar{background:#1e3a5f;color:#fff;padding:12px 24px;display:flex;justify-content:space-between;align-items:center}

.bar h1{font-size:17px}

.bar-right{display:flex;align-items:center;gap:12px;font-size:13px}

.logout{color:#fff;background:rgba(255,255,255,0.15);padding:5px 12px;border-radius:4px;text-decoration:none}

.rbtn{background:#c53030;color:#fff;border:none;padding:6px 14px;border-radius:4px;cursor:pointer;font-size:13px}

.rbtn:hover{background:#9b2c2c}

.wrap{max-width:1100px;margin:20px auto;padding:0 16px}

.stats{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-bottom:20px}

.stat{background:#fff;border-radius:8px;padding:16px;text-align:center;box-shadow:0 2px 6px rgba(0,0,0,0.07)}

.stat-n{font-size:30px;font-weight:bold;color:#1e3a5f}

.stat-l{font-size:12px;color:#888;margin-top:4px}

.grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}

.card{background:#fff;border-radius:8px;padding:18px;box-shadow:0 2px 6px rgba(0,0,0,0.07)}

.card h3{font-size:14px;font-weight:600;color:#1e3a5f;border-bottom:2px solid #ebf4ff;padding-bottom:8px;margin-bottom:12px}

.tag{display:inline-flex;align-items:center;gap:4px;background:#ebf4ff;border:1px solid #bee3f8;padding:3px 9px;border-radius:12px;font-size:12px;margin:2px}

.tag button{background:none;border:none;color:#e53e3e;cursor:pointer;font-size:15px;line-height:1;padding:0}

.row{display:flex;gap:8px;margin-top:10px}

.row input{flex:1;padding:7px 10px;border:1px solid #ddd;border-radius:5px;font-size:13px}

.btn{padding:7px 14px;background:#1e3a5f;color:#fff;border:none;border-radius:5px;font-size:13px;cursor:pointer}

.btn:hover{background:#16305a}

.trow{display:flex;justify-content:space-between;align-items:center;padding:9px 0;border-bottom:1px solid #f0f0f0}

.trow:last-child{border:none}

.trow-label{font-size:13px;font-weight:500}

.trow-sub{font-size:11px;color:#999}

.toggle{position:relative;width:42px;height:22px;cursor:pointer}

.toggle input{opacity:0;width:0;height:0}

.sl{position:absolute;inset:0;background:#ccc;border-radius:22px;transition:.25s}

.sl:before{content:"";position:absolute;width:16px;height:16px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.25s}

input:checked+.sl{background:#38a169}

input:checked+.sl:before{transform:translateX(20px)}

.logbox{background:#111827;color:#86efac;font-family:monospace;font-size:11px;padding:12px;border-radius:5px;height:190px;overflow-y:auto;white-space:pre-wrap;word-break:break-all}

.full{grid-column:1/-1}

table{width:100%;font-size:13px;border-collapse:collapse}

th{text-align:left;color:#888;font-weight:500;padding:6px 8px;border-bottom:1px solid #eee}

td{padding:7px 8px;border-bottom:1px solid #f5f5f5}

.ok{background:#c6f6d5;color:#276749;padding:2px 8px;border-radius:10px;font-size:11px}

.no{background:#fed7d7;color:#9b2c2c;padding:2px 8px;border-radius:10px;font-size:11px}

.msg{font-size:12px;margin-top:6px;padding:5px 10px;border-radius:4px;display:none}

.msg.ok2{background:#c6f6d5;color:#276749}

.msg.err2{background:#fed7d7;color:#9b2c2c}

</style></head>

<body>

<div class="bar">

  <h1>Squid Control Dashboard</h1>

  <div class="bar-right">

    <span>{{ session.user }} ({{ session.role }})</span>

    {% if session.role == "admin" %}

    <button class="rbtn" onclick="reloadSquid()">Reload Squid</button>

    {% endif %}

    <a href="/logout" class="logout">Logout</a>

  </div>

</div>

<div class="wrap">

  <div class="stats">

    <div class="stat"><div class="stat-n" id="sn-users">--</div><div class="stat-l">Active IPs</div></div>

    <div class="stat"><div class="stat-n" id="sn-req">--</div><div class="stat-l">Log Entries</div></div>

    <div class="stat"><div class="stat-n" id="sn-denied">--</div><div class="stat-l">Denied</div></div>

  </div>

  <div class="grid">

    <div class="card">

      <h3>YouTube Allowed IPs</h3>

      <div id="yt-tags"></div>

      <div class="row"><input id="yt-in" placeholder="10.10.10.x"><button class="btn" onclick="addIP('youtube_allow_ips','yt-in','yt-tags','yt-msg')">Add</button></div>

      <div class="msg" id="yt-msg"></div>

    </div>

    <div class="card">

      <h3>Facebook Allowed IPs</h3>

      <div id="fb-tags"></div>

      <div class="row"><input id="fb-in" placeholder="10.10.10.x"><button class="btn" onclick="addIP('fb_allow_ips','fb-in','fb-tags','fb-msg')">Add</button></div>

      <div class="msg" id="fb-msg"></div>

    </div>

    <div class="card">

      <h3>Job Sites Allowed IPs</h3>

      <div id="job-tags"></div>

      <div class="row"><input id="job-in" placeholder="10.10.10.x"><button class="btn" onclick="addIP('jobsites_allow_ips','job-in','job-tags','job-msg')">Add</button></div>

      <div class="msg" id="job-msg"></div>

    </div>

    <div class="card">

      <h3>Speed Unlimited IPs</h3>

      <div id="speed-tags"></div>

      <div class="row"><input id="speed-in" placeholder="10.10.10.x"><button class="btn" onclick="addIP('no_speed_limit_ip','speed-in','speed-tags','speed-msg')">Add</button></div>

      <div class="msg" id="speed-msg"></div>

    </div>

    <div class="card">

      <h3>Site Access Control</h3>

      <div id="site-toggles"></div>

    </div>

    <div class="card">

      <h3>Live Access Log</h3>

      <div class="logbox" id="logbox">Loading...</div>

    </div>

    <div class="card full">

      <h3>Traffic per IP (last 200 requests)</h3>

      <table><thead><tr><th>IP</th><th>Requests</th><th>Last Domain</th><th>Status</th></tr></thead>

      <tbody id="traffic"></tbody></table>

    </div>

  </div>

</div>

<script>

const role = "{{ session.role }}";

const sites = {

  yt:"YouTube (.youtube.com)",

  fb:"Facebook (.facebook.com)",

  bbc:"BBC (.bbc.com)",

  jobsites:"Job Sites (linkedin, rozee.pk)",

  speedtest_sites:"Speedtest.net"

};


function showMsg(id,text,ok){

  const el=document.getElementById(id);

  el.textContent=text;

  el.className="msg "+(ok?"ok2":"err2");

  el.style.display="block";

  setTimeout(()=>el.style.display="none",3000);

}


function renderTags(containerId,ips,acl){

  const el=document.getElementById(containerId);

  if(!ips.length){el.innerHTML="<span style='color:#aaa;font-size:12px'>Koi IP nahi</span>";return;}

  el.innerHTML=ips.map(ip=>

    `<span class="tag">${ip}<button onclick="removeIP('${acl}','${ip}','${containerId}')" title="Remove">x</button></span>`

  ).join("");

}


function loadIPs(acl,containerId){

  fetch("/api/ips/"+acl).then(r=>r.json()).then(d=>renderTags(containerId,d.ips,acl));

}


function addIP(acl,inputId,containerId,msgId){

  const ip=document.getElementById(inputId).value.trim();

  if(!ip)return;

  fetch("/api/ips/"+acl,{

    method:"POST",headers:{"Content-Type":"application/json"},

    body:JSON.stringify({action:"add",ip:ip})

  }).then(r=>r.json()).then(d=>{

    if(d.ok){document.getElementById(inputId).value="";loadIPs(acl,containerId);showMsg(msgId,"IP add ho gayi! Squid reload karo.",true);}

    else showMsg(msgId,d.error||"Error",false);

  });

}


function removeIP(acl,ip,containerId){

  fetch("/api/ips/"+acl,{

    method:"POST",headers:{"Content-Type":"application/json"},

    body:JSON.stringify({action:"remove",ip:ip})

  }).then(r=>r.json()).then(d=>{if(d.ok)loadIPs(acl,containerId);});

}


function loadToggles(){

  fetch("/api/sites").then(r=>r.json()).then(d=>{

    let html="";

    for(const [k,label] of Object.entries(sites)){

      const on=d[k];

      const dis=role!=="admin"?"disabled":"";

      html+=`<div class="trow">

        <div class="trow-label">${label}</div>

        <label class="toggle"><input type="checkbox" ${on?"checked":""} ${dis} onchange="toggleSite('${k}',this.checked)">

        <span class="sl"></span></label></div>`;

    }

    document.getElementById("site-toggles").innerHTML=html;

  });

}


function toggleSite(site,enabled){

  fetch("/api/sites",{

    method:"POST",headers:{"Content-Type":"application/json"},

    body:JSON.stringify({site:site,enabled:enabled})

  });

}


function loadLogs(){

  fetch("/api/logs").then(r=>r.json()).then(d=>{

    const lb=document.getElementById("logbox");

    lb.textContent=d.logs;

    lb.scrollTop=lb.scrollHeight;

  });

}


function loadStats(){

  fetch("/api/stats").then(r=>r.json()).then(d=>{

    document.getElementById("sn-users").textContent=d.users;

    document.getElementById("sn-req").textContent=d.requests;

    document.getElementById("sn-denied").textContent=d.denied;

  });

}


function loadTraffic(){

  fetch("/api/traffic").then(r=>r.json()).then(d=>{

    document.getElementById("traffic").innerHTML=d.rows.map(r=>

      `<tr><td><code>${r.ip}</code></td><td>${r.requests}</td><td>${r.domain}</td>

      <td><span class="${r.status==="allowed"?"ok":"no"}">${r.status}</span></td></tr>`

    ).join("");

  });

}


function reloadSquid(){

  if(!confirm("Squid reload karna chahte hain?"))return;

  fetch("/api/reload",{method:"POST"}).then(r=>r.json()).then(d=>{

    alert(d.ok?"Squid reloaded!":"Error: "+d.error);

  });

}


loadIPs("youtube_allow_ips","yt-tags");

loadIPs("fb_allow_ips","fb-tags");

loadIPs("jobsites_allow_ips","job-tags");

loadIPs("no_speed_limit_ip","speed-tags");

loadToggles();loadLogs();loadStats();loadTraffic();

setInterval(()=>{loadLogs();loadStats();loadTraffic();},8000);

</script>

</body></html>'''


@app.route('/login',methods=['GET','POST'])

def login():

    error=None

    if request.method=='POST':

        u=request.form.get('username','')

        p=request.form.get('password','')

        if u in USERS and USERS[u]['password']==p:

            session['user']=u

            session['role']=USERS[u]['role']

            return redirect('/')

        error='Wrong username or password'

    return render_template_string(LOGIN_PAGE,error=error)


@app.route('/logout')

def logout():

    session.clear()

    return redirect('/login')


@app.route('/')

@login_required

def index():

    return render_template_string(MAIN_PAGE)


@app.route('/api/ips/<acl>')

@login_required

def get_ips(acl):

    allowed=['youtube_allow_ips','fb_allow_ips','jobsites_allow_ips','no_speed_limit_ip']

    if acl not in allowed:

        return jsonify({'error':'Invalid'}),400

    return jsonify({'ips':get_ips_from_acl(acl)})


@app.route('/api/ips/<acl>',methods=['POST'])

@login_required

def modify_ip(acl):

    allowed=['youtube_allow_ips','fb_allow_ips','jobsites_allow_ips','no_speed_limit_ip']

    if acl not in allowed:

        return jsonify({'error':'Invalid'}),400

    data=request.get_json()

    action=data.get('action')

    ip=data.get('ip','').strip()

    if not ip:

        return jsonify({'error':'No IP'}),400

    ips=get_ips_from_acl(acl)

    if action=='add':

        if ip not in ips:

            ips.append(ip)

            set_ips_in_acl(acl,ips)

        return jsonify({'ok':True})

    elif action=='remove':

        ips=[x for x in ips if x!=ip]

        set_ips_in_acl(acl,ips)

        return jsonify({'ok':True})

    return jsonify({'error':'Invalid action'}),400


SITE_STATUS={'yt':True,'fb':True,'bbc':False,'jobsites':True,'speedtest_sites':True}


@app.route('/api/sites')

@login_required

def get_sites():

    return jsonify(SITE_STATUS)


@app.route('/api/sites',methods=['POST'])

@login_required

def toggle_site():

    data=request.get_json()

    site=data.get('site')

    if site in SITE_STATUS:

        SITE_STATUS[site]=data.get('enabled',False)

    return jsonify({'ok':True})


@app.route('/api/reload',methods=['POST'])

@login_required

def reload_squid():

    if session.get('role')!='admin':

        return jsonify({'error':'Admin only'}),403

    try:

        r=subprocess.run(['systemctl','reload','squid'],capture_output=True,text=True,timeout=15)

        if r.returncode==0:

            return jsonify({'ok':True})

        return jsonify({'error':r.stderr})

    except Exception as e:

        return jsonify({'error':str(e)})


@app.route('/api/logs')

@login_required

def get_logs():

    try:

        r=subprocess.run(['tail','-n','40',LOG_FILE],capture_output=True,text=True)

        return jsonify({'logs':r.stdout or 'No logs yet'})

    except:

        return jsonify({'logs':'Cannot read log file'})


@app.route('/api/stats')

@login_required

def get_stats():

    try:

        r=subprocess.run(['tail','-n','500',LOG_FILE],capture_output=True,text=True)

        lines=[l for l in r.stdout.strip().split('\n') if l]

        ips=set()

        denied=0

        for l in lines:

            p=l.split()

            if len(p)>2:

                ips.add(p[2])

            if 'DENIED' in l:

                denied+=1

        return jsonify({'users':len(ips),'requests':len(lines),'denied':denied})

    except:

        return jsonify({'users':0,'requests':0,'denied':0})


@app.route('/api/traffic')

@login_required

def get_traffic():

    try:

        r=subprocess.run(['tail','-n','200',LOG_FILE],capture_output=True,text=True)

        lines=[l for l in r.stdout.strip().split('\n') if l]

        ip_data={}

        for l in lines:

            p=l.split()

            if len(p)<7:

                continue

            ip=p[2]

            domain=p[6]

            status='denied' if 'DENIED' in l else 'allowed'

            if ip not in ip_data:

                ip_data[ip]={'ip':ip,'requests':0,'domain':domain,'status':status}

            ip_data[ip]['requests']+=1

            ip_data[ip]['domain']=domain

        rows=sorted(ip_data.values(),key=lambda x:x['requests'],reverse=True)[:10]

        return jsonify({'rows':rows})

    except:

        return jsonify({'rows':[]})


if __name__=='__main__':

    app.run(host='0.0.0.0',port=8080,debug=False)

PYEOF




-------------------------------------------




Phir run karo:

bash
pkill -f app.py 2>/dev/null; sleep 1
python3 /opt/squid-dashboard/app.py &

Browser mein kholo:

http://10.10.19.7:8080

Login:

  • Admin: admin / Squid@2024
  • Junior: junior / Junior@2024

Comments




  1. After restart service enable portal


    cat > /etc/systemd/system/squid-dashboard.service << 'EOF'
    [Unit]
    Description=Squid Dashboard
    After=network.target

    [Service]
    ExecStart=/usr/bin/python3 /opt/squid-dashboard/app.py
    Restart=always
    User=root

    [Install]
    WantedBy=multi-user.target
    EOF
    ---------

    systemctl daemon-reload
    systemctl enable squid-dashboard
    systemctl start squid-dashboard

    ReplyDelete

Post a Comment

Popular posts from this blog

Windows 11 rdp problem resolved

Microsoft Activation Scripts (MAS)

Windows 11 ke common RDP issues fix karta hai