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:
pkill -f app.py 2>/dev/null; sleep 1
python3 /opt/squid-dashboard/app.py &Browser mein kholo:
http://10.10.19.7:8080Login:
- Admin:
admin/Squid@2024 - Junior:
junior/Junior@2024
ReplyDeleteAfter 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