*수근님만 보세요.
(아래 파일은 실제 파일이 아닌, 현재 파일을 재구성해서 만든 가장 속도가 빠른 템플릿입니다.)
user1@ctrl:~$ sudo cat /etc/systemd/system/clover-flask.service
[Unit]
Description=Clover Flask App (Auto Port Clean)
After=network.target
[Service]
User=user1
WorkingDirectory=/var/www/html/Clover
Environment="PYTHONUNBUFFERED=1"
ExecStartPre=/bin/sh -c 'fuser -k 5000/tcp 2>/dev/null || true'
ExecStart=/var/www/html/Clover/.venv/bin/python3 -u app.py
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
user1@ctrl:~$
user1@ctrl:/var/www/html/Clover$ sudo cat app.py
from flask import Flask, render_template, jsonify
import os
import re
app = Flask(__name__)
# ── 외부 도구 URL ──────────────────────────────────────
EXTERNAL = {
"openstack": "<https://openstack.seongung.cloud>",
"grafana_node": "<https://grafana.seongung.cloud/d/rYdddlPWk/node-exporter-full?orgId=1&from=now-24h&to=now&timezone=browser&var-ds_prometheus=afr9gaxgzeqdcb&var-job=node-exporter&var-node=node01&refresh=1m>",
"grafana_docker": "<https://grafana.seongung.cloud/d/ad4dcng/docker-monitoring-with-node-selection?orgId=1&from=now-3h&to=now&timezone=browser&var-job=cadvisor&var-node=10.0.1.13&var-port=8080&refresh=10s>",
"vault": "<https://vault.seongung.cloud>",
"minio": "<https://minio.seongung.cloud>",
}
MENU = [
{"id": "dashboard", "name": "Dashboard", "icon": "🏠", "mode": "native"},
{"id": "openstack", "name": "OpenStack", "icon": "☁️", "mode": "iframe", "url": EXTERNAL["openstack"]},
{"id": "monitoring", "name": "Monitoring", "icon": "📊", "mode": "iframe", "url": EXTERNAL["grafana_node"]},
{"id": "webnode", "name": "WebNode", "icon": "🌐", "mode": "iframe", "url": EXTERNAL["grafana_docker"]},
{"id": "security", "name": "Security", "icon": "🛡️", "mode": "none"},
{"id": "vault", "name": "Vault", "icon": "🔐", "mode": "iframe", "url": EXTERNAL["vault"]},
{"id": "minio", "name": "MinIO", "icon": "📦", "mode": "iframe", "url": EXTERNAL["minio"]},
]
LOG_FILE = "/var/log/auth.log"
def render_page(section_id: str):
section = next((m for m in MENU if m["id"] == section_id), MENU[0])
return render_template("base.html", menu=MENU, active=section["id"], section=section)
@app.route("/")
def dashboard():
return render_page("dashboard")
@app.route("/<section>")
def section_view(section):
return render_page(section)
@app.route("/api/recent-users")
def recent_users():
users = []
try:
with open(LOG_FILE, "r", encoding="utf-8") as f:
lines = f.readlines()
for line in lines[-200:]:
if "Accepted" in line and "for" in line:
match = re.search(r"for (\S+) from (\S+)", line)
if match:
user = match.group(1)
ip = match.group(2)
time_str = " ".join(line.split()[:3])
users.append({
"user": user,
"ip": ip,
"time": time_str,
"raw": line.strip()
})
seen = set()
unique_users = []
for u in reversed(users):
key = f"{u['user']}_{u['ip']}"
if key not in seen:
seen.add(key)
unique_users.append(u)
if len(unique_users) >= 10:
break
return jsonify(unique_users)
except Exception as e:
return jsonify({"error": str(e)}), 500
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
user1@ctrl:/var/www/html/Clover$
node01 & node02 webstack
node01 node02 webstack → app.py 다 수정하시고 경훈님께
웹스택 .