sinhronizacija
This commit is contained in:
Binary file not shown.
263
main.py
263
main.py
@@ -9,45 +9,20 @@ from fastapi import HTTPException
|
|||||||
import os
|
import os
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
import threading
|
||||||
|
|
||||||
if os.environ.get("DATABASE_URL") == None:
|
if os.environ.get("DATABASE_URL") == None:
|
||||||
os.environ["DATABASE_URL"] = "sqlite:///./wall_messages.db"
|
os.environ["DATABASE_URL"] = "sqlite:///./wall_messages.db"
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
start_time = time.time()
|
cpu_threshold = 80
|
||||||
@app.get("/uptime")
|
ram_threshold = 80
|
||||||
def get_uptime():
|
disk_threshold = 80
|
||||||
uptime_seconds = int(time.time() - start_time)
|
|
||||||
uptime_str = str(datetime.timedelta(seconds=uptime_seconds))
|
|
||||||
return {"uptime_seconds": uptime_seconds, "uptime": uptime_str}
|
|
||||||
|
|
||||||
@app.get("/disk")
|
|
||||||
def get_disk_usage():
|
|
||||||
usage = psutil.disk_usage('/')
|
|
||||||
return {
|
|
||||||
"total": usage.total,
|
|
||||||
"used": usage.used,
|
|
||||||
"free": usage.free,
|
|
||||||
"percent": usage.percent
|
|
||||||
}
|
|
||||||
|
|
||||||
@app.get("/ram")
|
|
||||||
def get_ram_usage():
|
|
||||||
mem = psutil.virtual_memory()
|
|
||||||
return {
|
|
||||||
"total": mem.total,
|
|
||||||
"available": mem.available,
|
|
||||||
"used": mem.used,
|
|
||||||
"percent": mem.percent
|
|
||||||
}
|
|
||||||
@app.get("/cpu")
|
|
||||||
def get_cpu_usage():
|
|
||||||
cpu_percent = psutil.cpu_percent(interval=1)
|
|
||||||
return {"cpu_percent": cpu_percent}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cpu_normal = True
|
||||||
|
ram_normal = True
|
||||||
|
disk_normal = True
|
||||||
|
|
||||||
DATABASE_URL = "sqlite:///./wall_messages.db"
|
DATABASE_URL = "sqlite:///./wall_messages.db"
|
||||||
|
|
||||||
@@ -55,89 +30,184 @@ engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
|
|||||||
SessionLocal = sessionmaker(bind=engine)
|
SessionLocal = sessionmaker(bind=engine)
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
|
|
||||||
class WallMessageDB(Base):
|
class LimitsModel(Base):
|
||||||
__tablename__ = "wall_messages"
|
__tablename__ = "limits"
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
text = Column(String, nullable=False)
|
cpu_threshold = Column(Integer, default=80)
|
||||||
timestamp = Column(DateTime, default=datetime.datetime.utcnow(), nullable=False)
|
ram_threshold = Column(Integer, default=80)
|
||||||
|
disk_threshold = Column(Integer, default=80)
|
||||||
|
|
||||||
Base.metadata.create_all(bind=engine)
|
class OverageEventModel(Base):
|
||||||
|
__tablename__ = "overages"
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
cpu_overage = Column(Integer, default=0)
|
||||||
|
ram_overage = Column(Integer, default=0)
|
||||||
|
disk_overage = Column(Integer, default=0)
|
||||||
|
timestamp = Column(DateTime, default=datetime.datetime.now(), nullable=False)
|
||||||
|
|
||||||
class WallMessageCreate(BaseModel):
|
class OverageEvent(BaseModel):
|
||||||
text: str
|
|
||||||
|
|
||||||
class WallMessageRead(BaseModel):
|
|
||||||
id: int
|
id: int
|
||||||
text: str
|
cpu_overage: int
|
||||||
|
ram_overage: int
|
||||||
|
disk_overage: int
|
||||||
timestamp: datetime.datetime
|
timestamp: datetime.datetime
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
@app.post("/wall", response_model=WallMessageRead)
|
Base.metadata.create_all(bind=engine)
|
||||||
def post_wall_message(msg: WallMessageCreate):
|
|
||||||
db = SessionLocal()
|
|
||||||
db_msg = WallMessageDB(text=msg.text)
|
|
||||||
db.add(db_msg)
|
|
||||||
db.commit()
|
|
||||||
db.refresh(db_msg)
|
|
||||||
db.close()
|
|
||||||
return db_msg
|
|
||||||
|
|
||||||
@app.get("/wall", response_model=list[WallMessageRead])
|
|
||||||
def get_wall_messages():
|
|
||||||
db = SessionLocal()
|
|
||||||
messages = db.query(WallMessageDB).order_by(WallMessageDB.timestamp.desc()).all()
|
|
||||||
db.close()
|
|
||||||
return messages
|
|
||||||
|
|
||||||
@app.get("/wall/{msg_id}", response_model=WallMessageRead)
|
@app.on_event("startup")
|
||||||
def get_wall_message(msg_id: int):
|
def startup_event():
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
msg = db.query(WallMessageDB).filter(WallMessageDB.id == msg_id).first()
|
if not db.query(LimitsModel).first():
|
||||||
|
limits = LimitsModel(
|
||||||
|
cpu_threshold=50,
|
||||||
|
ram_threshold=50,
|
||||||
|
disk_threshold=80
|
||||||
|
)
|
||||||
|
db.add(limits)
|
||||||
|
db.commit()
|
||||||
db.close()
|
db.close()
|
||||||
if not msg:
|
|
||||||
raise HTTPException(status_code=404, detail="Message not found")
|
|
||||||
return msg
|
|
||||||
|
|
||||||
@app.put("/wall/{msg_id}", response_model=WallMessageRead)
|
def check_and_log_overages():
|
||||||
def update_wall_message(msg_id: int, msg: WallMessageCreate):
|
while True:
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
db_msg = db.query(WallMessageDB).filter(WallMessageDB.id == msg_id).first()
|
limits = db.query(LimitsModel).first()
|
||||||
if not db_msg:
|
if limits:
|
||||||
|
cpu_limit = limits.cpu_threshold
|
||||||
|
ram_limit = limits.ram_threshold
|
||||||
|
disk_limit = limits.disk_threshold
|
||||||
|
|
||||||
|
cpu = psutil.cpu_percent(interval=0.5)
|
||||||
|
ram = psutil.virtual_memory().percent
|
||||||
|
disk = psutil.disk_usage('/').percent
|
||||||
|
|
||||||
|
cpu_over = int(cpu > cpu_limit)
|
||||||
|
ram_over = int(ram > ram_limit)
|
||||||
|
disk_over = int(disk > disk_limit)
|
||||||
|
|
||||||
|
last_event = db.query(OverageEventModel).order_by(OverageEventModel.timestamp.desc()).first()
|
||||||
|
should_log = False
|
||||||
|
|
||||||
|
if last_event is None:
|
||||||
|
should_log = True
|
||||||
|
else:
|
||||||
|
if (
|
||||||
|
last_event.cpu_overage != cpu_over or
|
||||||
|
last_event.ram_overage != ram_over or
|
||||||
|
last_event.disk_overage != disk_over
|
||||||
|
):
|
||||||
|
should_log = True
|
||||||
|
|
||||||
|
if should_log:
|
||||||
|
event = OverageEventModel(
|
||||||
|
cpu_overage=cpu_over,
|
||||||
|
ram_overage=ram_over,
|
||||||
|
disk_overage=disk_over,
|
||||||
|
timestamp=datetime.datetime.now()
|
||||||
|
)
|
||||||
|
db.add(event)
|
||||||
|
db.commit()
|
||||||
db.close()
|
db.close()
|
||||||
raise HTTPException(status_code=404, detail="Message not found")
|
time.sleep(10)
|
||||||
db_msg.text = msg.text
|
|
||||||
db.commit()
|
|
||||||
db.refresh(db_msg)
|
|
||||||
db.close()
|
|
||||||
return db_msg
|
|
||||||
|
|
||||||
@app.delete("/wall/{msg_id}")
|
threading.Thread(target=check_and_log_overages, daemon=True).start()
|
||||||
def delete_wall_message(msg_id: int):
|
|
||||||
|
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
class Info(BaseModel):
|
||||||
|
uptime: datetime.datetime
|
||||||
|
cpu: float
|
||||||
|
ram: float
|
||||||
|
disk: float
|
||||||
|
cpu_threshold: int
|
||||||
|
ram_threshold: int
|
||||||
|
disk_threshold: int
|
||||||
|
cpu_normal: bool
|
||||||
|
ram_normal: bool
|
||||||
|
disk_normal: bool
|
||||||
|
|
||||||
|
class Thresholds(BaseModel):
|
||||||
|
cpu_threshold: int
|
||||||
|
ram_threshold: int
|
||||||
|
disk_threshold: int
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/info", response_class=Info)
|
||||||
|
def get_info():
|
||||||
|
cpu = psutil.cpu_percent(interval=0.5)
|
||||||
|
ram = psutil.virtual_memory().percent
|
||||||
|
disk = psutil.disk_usage('/').percent
|
||||||
|
uptime = time.time() - start_time
|
||||||
|
info = Info(
|
||||||
|
uptime=uptime,
|
||||||
|
cpu=cpu,
|
||||||
|
ram=ram,
|
||||||
|
disk=disk,
|
||||||
|
cpu_threshold=cpu_threshold,
|
||||||
|
ram_threshold=ram_threshold,
|
||||||
|
disk_threshold=disk_threshold,
|
||||||
|
cpu_normal=cpu <= cpu_threshold,
|
||||||
|
ram_normal=ram <= ram_threshold,
|
||||||
|
disk_normal=disk <= disk_threshold
|
||||||
|
)
|
||||||
|
return info
|
||||||
|
|
||||||
|
@app.get("/limits", response_model=Thresholds)
|
||||||
|
def get_limits():
|
||||||
|
return {
|
||||||
|
"cpu_threshold": cpu_threshold,
|
||||||
|
"ram_threshold": ram_threshold,
|
||||||
|
"disk_threshold": disk_threshold
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.post("/limits")
|
||||||
|
def set_limits(thresholds: Thresholds):
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
db_msg = db.query(WallMessageDB).filter(WallMessageDB.id == msg_id).first()
|
limits = db.query(LimitsModel).first()
|
||||||
if not db_msg:
|
if limits:
|
||||||
|
limits.cpu_threshold = thresholds.cpu_threshold
|
||||||
|
limits.ram_threshold = thresholds.ram_threshold
|
||||||
|
limits.disk_threshold = thresholds.disk_threshold
|
||||||
|
db.commit()
|
||||||
|
db.refresh(limits)
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"cpu": cpu_threshold,
|
||||||
|
"ram": ram_threshold,
|
||||||
|
"disk": disk_threshold
|
||||||
|
}
|
||||||
|
@app.get("/overages", response_model=list[OverageEvent])
|
||||||
|
def get_overages():
|
||||||
|
db = SessionLocal()
|
||||||
|
overages = db.query(OverageEventModel).order_by(OverageEventModel.timestamp.desc()).all()
|
||||||
|
db.close()
|
||||||
|
return overages
|
||||||
|
|
||||||
|
@app.get("/overages/{overage_id}", response_model=OverageEvent)
|
||||||
|
def get_overage(overage_id: int):
|
||||||
|
db = SessionLocal()
|
||||||
|
overage = db.query(OverageEventModel).filter(OverageEventModel.id == overage_id).first()
|
||||||
|
db.close()
|
||||||
|
if not overage:
|
||||||
|
raise HTTPException(status_code=404, detail="Overage event not found")
|
||||||
|
return overage
|
||||||
|
|
||||||
|
@app.delete("/overages/{overage_id}")
|
||||||
|
def delete_overage(overage_id: int):
|
||||||
|
db = SessionLocal()
|
||||||
|
overage = db.query(OverageEventModel).filter(OverageEventModel.id == overage_id).first()
|
||||||
|
if not overage:
|
||||||
db.close()
|
db.close()
|
||||||
raise HTTPException(status_code=404, detail="Message not found")
|
raise HTTPException(status_code=404, detail="Overage event not found")
|
||||||
db.delete(db_msg)
|
db.delete(overage)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.close()
|
db.close()
|
||||||
return {"status": "success", "message": f"Deleted message {msg_id}"}
|
return {"detail": "Overage event deleted"}
|
||||||
|
|
||||||
class WallMessage(BaseModel):
|
|
||||||
message: str
|
|
||||||
|
|
||||||
wall_messages = []
|
|
||||||
|
|
||||||
@app.post("/wall")
|
|
||||||
def post_wall_message(msg: WallMessage):
|
|
||||||
wall_messages.append(msg.message)
|
|
||||||
return {"status": "success", "message": msg.message}
|
|
||||||
|
|
||||||
@app.get("/wall")
|
|
||||||
def get_wall_messages():
|
|
||||||
return wall_messages
|
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
@@ -146,4 +216,5 @@ app.add_middleware(
|
|||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
app.mount("/", StaticFiles(directory=".", html=True), name="static")
|
|
||||||
|
app.mount("/", StaticFiles(directory="./webroot", html=True), name="static")
|
||||||
BIN
wall_messages.db
BIN
wall_messages.db
Binary file not shown.
@@ -17,7 +17,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>System Monitor</h1>
|
<h1>NUIKS SysMonitor</h1>
|
||||||
<div class="stat"><span class="label">CPU Usage:</span> <span class="value" id="cpu">--</span></div>
|
<div class="stat"><span class="label">CPU Usage:</span> <span class="value" id="cpu">--</span></div>
|
||||||
<div class="stat"><span class="label">RAM Usage:</span> <span class="value" id="ram">--</span></div>
|
<div class="stat"><span class="label">RAM Usage:</span> <span class="value" id="ram">--</span></div>
|
||||||
<div class="stat"><span class="label">SSD Usage:</span> <span class="value" id="disk">--</span></div>
|
<div class="stat"><span class="label">SSD Usage:</span> <span class="value" id="disk">--</span></div>
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="container" style="margin-top:24px;">
|
<div class="container" style="margin-top:24px;">
|
||||||
<h1>Wall Messages</h1>
|
<h1>Limit overages</h1>
|
||||||
<form id="addMessageForm" style="margin-bottom:16px;">
|
<form id="addMessageForm" style="margin-bottom:16px;">
|
||||||
<input type="text" id="newMessage" placeholder="Type a message..." style="width:70%;padding:8px;" required>
|
<input type="text" id="newMessage" placeholder="Type a message..." style="width:70%;padding:8px;" required>
|
||||||
<button type="submit" class="refresh-btn" style="display:inline-block;margin:0;">Add</button>
|
<button type="submit" class="refresh-btn" style="display:inline-block;margin:0;">Add</button>
|
||||||
@@ -33,6 +33,25 @@
|
|||||||
<ul id="messages" style="list-style:none;padding:0;"></ul>
|
<ul id="messages" style="list-style:none;padding:0;"></ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="container" style="margin-top:24px;">
|
||||||
|
<h1>Resource Limits</h1>
|
||||||
|
<div class="stat">
|
||||||
|
<span class="label">CPU Limit:</span>
|
||||||
|
<input type="range" id="cpuLimit" min="1" max="100" value="50" style="flex:1;margin:0 12px;">
|
||||||
|
<span class="value" id="cpuLimitValue">50%</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<span class="label">RAM Limit:</span>
|
||||||
|
<input type="range" id="ramLimit" min="1" max="100" value="50" style="flex:1;margin:0 12px;">
|
||||||
|
<span class="value" id="ramLimitValue">50%</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<span class="label">SSD Limit:</span>
|
||||||
|
<input type="range" id="diskLimit" min="1" max="100" value="50" style="flex:1;margin:0 12px;">
|
||||||
|
<span class="value" id="diskLimitValue">50%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
async function fetchStats() {
|
async function fetchStats() {
|
||||||
try {
|
try {
|
||||||
@@ -53,7 +72,15 @@
|
|||||||
document.getElementById('uptime').textContent = 'Error';
|
document.getElementById('uptime').textContent = 'Error';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function formatTimestamp(ts) {
|
||||||
|
// Try to parse as ISO string, fallback to Date.parse
|
||||||
|
let date = new Date(ts);
|
||||||
|
if (isNaN(date.getTime())) {
|
||||||
|
// If ts is seconds since epoch
|
||||||
|
date = new Date(Number(ts) * 1000);
|
||||||
|
}
|
||||||
|
return date.toLocaleString();
|
||||||
|
}
|
||||||
// Wall Messages CRUD
|
// Wall Messages CRUD
|
||||||
async function fetchMessages() {
|
async function fetchMessages() {
|
||||||
const res = await fetch('/wall');
|
const res = await fetch('/wall');
|
||||||
@@ -68,7 +95,7 @@
|
|||||||
li.style.padding = '8px 0';
|
li.style.padding = '8px 0';
|
||||||
|
|
||||||
const span = document.createElement('span');
|
const span = document.createElement('span');
|
||||||
span.textContent = msg.text;
|
span.textContent = formatTimestamp(msg.timestamp) + " " + msg.text;
|
||||||
|
|
||||||
const editBtn = document.createElement('button');
|
const editBtn = document.createElement('button');
|
||||||
editBtn.textContent = 'Edit';
|
editBtn.textContent = 'Edit';
|
||||||
@@ -116,12 +143,60 @@
|
|||||||
|
|
||||||
async function updateMessage(id, text) {
|
async function updateMessage(id, text) {
|
||||||
await fetch(`/wall/${id}`, {
|
await fetch(`/wall/${id}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({text})
|
body: JSON.stringify({text})
|
||||||
});
|
});
|
||||||
fetchMessages();
|
fetchMessages();
|
||||||
}
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// --- Resource Limits Sliders ---
|
||||||
|
async function fetchLimits() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/limits');
|
||||||
|
const limits = await res.json();
|
||||||
|
document.getElementById('cpuLimit').value = limits.cpu;
|
||||||
|
document.getElementById('ramLimit').value = limits.ram;
|
||||||
|
document.getElementById('diskLimit').value = limits.disk;
|
||||||
|
document.getElementById('cpuLimitValue').textContent = limits.cpu + '%';
|
||||||
|
document.getElementById('ramLimitValue').textContent = limits.ram + '%';
|
||||||
|
document.getElementById('diskLimitValue').textContent = limits.disk + '%';
|
||||||
|
} catch (e) {
|
||||||
|
// fallback: set to 50 if error
|
||||||
|
document.getElementById('cpuLimit').value = 50;
|
||||||
|
document.getElementById('ramLimit').value = 50;
|
||||||
|
document.getElementById('diskLimit').value = 50;
|
||||||
|
document.getElementById('cpuLimitValue').textContent = '50%';
|
||||||
|
document.getElementById('ramLimitValue').textContent = '50%';
|
||||||
|
document.getElementById('diskLimitValue').textContent = '50%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateLimits() {
|
||||||
|
const cpu = parseInt(document.getElementById('cpuLimit').value, 10);
|
||||||
|
const ram = parseInt(document.getElementById('ramLimit').value, 10);
|
||||||
|
const disk = parseInt(document.getElementById('diskLimit').value, 10);
|
||||||
|
await fetch('/limits', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify({ cpu, ram, disk })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupLimitSliders() {
|
||||||
|
['cpu', 'ram', 'disk'].forEach(resource => {
|
||||||
|
const slider = document.getElementById(resource + 'Limit');
|
||||||
|
const valueSpan = document.getElementById(resource + 'LimitValue');
|
||||||
|
slider.oninput = function() {
|
||||||
|
valueSpan.textContent = slider.value + '%';
|
||||||
|
};
|
||||||
|
slider.onchange = function() {
|
||||||
|
updateLimits();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('addMessageForm').onsubmit = function(e) {
|
document.getElementById('addMessageForm').onsubmit = function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -137,6 +212,10 @@
|
|||||||
fetchStats();
|
fetchStats();
|
||||||
setInterval(fetchStats, 5000); // Fetch every 5 seconds
|
setInterval(fetchStats, 5000); // Fetch every 5 seconds
|
||||||
fetchMessages();
|
fetchMessages();
|
||||||
|
setInterval(fetchMessages, 5000); // Fetch messages every 5 seconds
|
||||||
|
fetchLimits();
|
||||||
|
setupLimitSliders();
|
||||||
|
setInterval(fetchLimits, 5000); // Fetch limits every 5 seconds
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
Reference in New Issue
Block a user