mirror of
https://github.com/simon987/sist2.git
synced 2025-04-19 18:26:43 +00:00
Add log cleanup features
This commit is contained in:
parent
f0fd708082
commit
5522bcfa9b
@ -112,6 +112,16 @@ class Sist2AdminApi {
|
|||||||
getSist2AdminInfo() {
|
getSist2AdminInfo() {
|
||||||
return axios.get(`${this.baseUrl}/api/`);
|
return axios.get(`${this.baseUrl}/api/`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getLogsToDelete(jobName, n) {
|
||||||
|
return axios.get(`${this.baseUrl}/api/job/${jobName}/logs_to_delete`, {
|
||||||
|
params: {n: n}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteTaskLogs(taskId) {
|
||||||
|
return axios.post(`${this.baseUrl}/api/task/${taskId}/delete_logs`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new Sist2AdminApi()
|
export default new Sist2AdminApi()
|
@ -9,18 +9,31 @@
|
|||||||
</b-form-checkbox>
|
</b-form-checkbox>
|
||||||
|
|
||||||
<label>{{ $t("jobOptions.cron") }}</label>
|
<label>{{ $t("jobOptions.cron") }}</label>
|
||||||
<b-form-input class="text-monospace" :state="cronValid" v-model="job.cron_expression" :disabled="!job.schedule_enabled" @change="update()"></b-form-input>
|
<b-form-input class="text-monospace" :state="cronValid" v-model="job.cron_expression"
|
||||||
|
:disabled="!job.schedule_enabled" @change="update()"></b-form-input>
|
||||||
|
|
||||||
|
<label>{{ $t("jobOptions.keepNLogs") }}</label>
|
||||||
|
<b-input-group>
|
||||||
|
<b-form-input type="number" v-model="job.keep_last_n_logs" @change="update()"></b-form-input>
|
||||||
|
<b-input-group-append>
|
||||||
|
<b-button variant="danger" @click="onDeleteNowClick()">{{ $t("jobOptions.deleteNow") }}</b-button>
|
||||||
|
</b-input-group-append>
|
||||||
|
</b-input-group>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
import Sist2AdminApi from "@/Sist2AdminApi";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "JobOptions",
|
name: "JobOptions",
|
||||||
props: ["job"],
|
props: ["job"],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
cronValid: undefined
|
cronValid: undefined,
|
||||||
|
logsToDelete: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -52,6 +65,30 @@ export default {
|
|||||||
this.$emit("change", this.job);
|
this.$emit("change", this.job);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onDeleteNowClick() {
|
||||||
|
Sist2AdminApi.getLogsToDelete(this.job.name, this.job.keep_last_n_logs).then(resp => {
|
||||||
|
const toDelete = resp.data;
|
||||||
|
const message = `Delete ${toDelete.length} log files?`;
|
||||||
|
|
||||||
|
this.$bvModal.msgBoxConfirm(message, {
|
||||||
|
title: this.$t("confirmation"),
|
||||||
|
size: "sm",
|
||||||
|
buttonSize: "sm",
|
||||||
|
okVariant: "danger",
|
||||||
|
okTitle: this.$t("delete"),
|
||||||
|
cancelTitle: this.$t("cancel"),
|
||||||
|
footerClass: "p-2",
|
||||||
|
hideHeaderClose: false,
|
||||||
|
centered: true
|
||||||
|
}).then(value => {
|
||||||
|
if (value) {
|
||||||
|
toDelete.forEach(row => {
|
||||||
|
Sist2AdminApi.deleteTaskLogs(row["id"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
@ -5,10 +5,13 @@ export default {
|
|||||||
go: "Go",
|
go: "Go",
|
||||||
online: "online",
|
online: "online",
|
||||||
offline: "offline",
|
offline: "offline",
|
||||||
|
view: "View",
|
||||||
delete: "Delete",
|
delete: "Delete",
|
||||||
runNow: "Index now",
|
runNow: "Index now",
|
||||||
create: "Create",
|
create: "Create",
|
||||||
|
cancel: "Cancel",
|
||||||
test: "Test",
|
test: "Test",
|
||||||
|
confirmation: "Confirmation",
|
||||||
|
|
||||||
jobTitle: "job configuration",
|
jobTitle: "job configuration",
|
||||||
tasks: "Tasks",
|
tasks: "Tasks",
|
||||||
@ -99,6 +102,8 @@ export default {
|
|||||||
jobOptions: {
|
jobOptions: {
|
||||||
title: "Job options",
|
title: "Job options",
|
||||||
cron: "Job schedule",
|
cron: "Job schedule",
|
||||||
|
keepNLogs: "Keep last N log files. Set to -1 to keep all logs.",
|
||||||
|
deleteNow: "Delete now",
|
||||||
scheduleEnabled: "Enable scheduled re-scan",
|
scheduleEnabled: "Enable scheduled re-scan",
|
||||||
noJobAvailable: "No jobs available.",
|
noJobAvailable: "No jobs available.",
|
||||||
desktopNotifications: "Desktop notifications"
|
desktopNotifications: "Desktop notifications"
|
||||||
|
@ -23,7 +23,18 @@
|
|||||||
:per-page="10"
|
:per-page="10"
|
||||||
>
|
>
|
||||||
<template #cell(logs)="data">
|
<template #cell(logs)="data">
|
||||||
<router-link :to="`/log/${data.item.logs}`">{{ $t("logs") }}</router-link>
|
<template v-if="data.item._row.has_logs">
|
||||||
|
<b-button variant="link" size="sm" :to="`/log/${data.item.id}`">
|
||||||
|
{{ $t("view") }}
|
||||||
|
</b-button>
|
||||||
|
/
|
||||||
|
<b-button variant="link" size="sm" @click="deleteLogs(data.item.id)">
|
||||||
|
{{ $t("delete") }}
|
||||||
|
</b-button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #cell(delete)="data">
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</b-table>
|
</b-table>
|
||||||
@ -122,8 +133,9 @@ export default {
|
|||||||
name: row.name,
|
name: row.name,
|
||||||
duration: this.taskDuration(row),
|
duration: this.taskDuration(row),
|
||||||
time: moment.utc(row.started).local().format("dd, MMM Do YYYY, HH:mm:ss"),
|
time: moment.utc(row.started).local().format("dd, MMM Do YYYY, HH:mm:ss"),
|
||||||
logs: row.id,
|
logs: null,
|
||||||
status: row.return_code === 0 ? "ok" : "failed"
|
status: row.return_code === 0 ? "ok" : "failed",
|
||||||
|
_row: row
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -137,6 +149,11 @@ export default {
|
|||||||
const end = moment.utc(task.ended);
|
const end = moment.utc(task.ended);
|
||||||
|
|
||||||
return humanDuration(end.diff(start))
|
return humanDuration(end.diff(start))
|
||||||
|
},
|
||||||
|
deleteLogs(taskId) {
|
||||||
|
Sist2AdminApi.deleteTaskLogs(taskId).then(() => {
|
||||||
|
this.updateHistory();
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -147,4 +164,8 @@ export default {
|
|||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-link {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -21,7 +21,8 @@ from config import LOG_FOLDER, logger, WEBSERVER_PORT, DATA_FOLDER, SIST2_BINARY
|
|||||||
from jobs import Sist2Job, Sist2ScanTask, TaskQueue, Sist2IndexTask, JobStatus
|
from jobs import Sist2Job, Sist2ScanTask, TaskQueue, Sist2IndexTask, JobStatus
|
||||||
from notifications import Subscribe, Notifications
|
from notifications import Subscribe, Notifications
|
||||||
from sist2 import Sist2
|
from sist2 import Sist2
|
||||||
from state import migrate_v1_to_v2, RUNNING_FRONTENDS, TESSERACT_LANGS, DB_SCHEMA_VERSION
|
from state import migrate_v1_to_v2, RUNNING_FRONTENDS, TESSERACT_LANGS, DB_SCHEMA_VERSION, migrate_v3_to_v4, \
|
||||||
|
get_log_files_to_remove, delete_log_file
|
||||||
from web import Sist2Frontend
|
from web import Sist2Frontend
|
||||||
|
|
||||||
sist2 = Sist2(SIST2_BINARY, DATA_FOLDER)
|
sist2 = Sist2(SIST2_BINARY, DATA_FOLDER)
|
||||||
@ -80,7 +81,6 @@ async def get_jobs():
|
|||||||
|
|
||||||
@app.put("/api/job/{name:str}")
|
@app.put("/api/job/{name:str}")
|
||||||
async def update_job(name: str, new_job: Sist2Job):
|
async def update_job(name: str, new_job: Sist2Job):
|
||||||
|
|
||||||
new_job.last_modified = datetime.utcnow()
|
new_job.last_modified = datetime.utcnow()
|
||||||
job = db["jobs"][name]
|
job = db["jobs"][name]
|
||||||
if not job:
|
if not job:
|
||||||
@ -133,6 +133,16 @@ async def kill_job(task_id: str):
|
|||||||
return task_queue.kill_task(task_id)
|
return task_queue.kill_task(task_id)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/task/{task_id:str}/delete_logs")
|
||||||
|
async def delete_task_logs(task_id: str):
|
||||||
|
if not db["task_done"][task_id]:
|
||||||
|
raise HTTPException(status_code=404)
|
||||||
|
|
||||||
|
delete_log_file(db, task_id)
|
||||||
|
|
||||||
|
return "ok"
|
||||||
|
|
||||||
|
|
||||||
def _run_job(job: Sist2Job):
|
def _run_job(job: Sist2Job):
|
||||||
job.last_modified = datetime.utcnow()
|
job.last_modified = datetime.utcnow()
|
||||||
if job.status == JobStatus("created"):
|
if job.status == JobStatus("created"):
|
||||||
@ -157,6 +167,11 @@ async def run_job(name: str):
|
|||||||
return "ok"
|
return "ok"
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/job/{name:str}/logs_to_delete")
|
||||||
|
async def task_history(n: int, name: str):
|
||||||
|
return get_log_files_to_remove(db, name, n)
|
||||||
|
|
||||||
|
|
||||||
@app.delete("/api/job/{name:str}")
|
@app.delete("/api/job/{name:str}")
|
||||||
async def delete_job(name: str):
|
async def delete_job(name: str):
|
||||||
job = db["jobs"][name]
|
job = db["jobs"][name]
|
||||||
@ -320,7 +335,6 @@ async def ws_tail_log(websocket: WebSocket):
|
|||||||
async with Subscribe(notifications) as ob:
|
async with Subscribe(notifications) as ob:
|
||||||
async for notification in ob.notifications():
|
async for notification in ob.notifications():
|
||||||
await websocket.send_json(notification)
|
await websocket.send_json(notification)
|
||||||
print(notification)
|
|
||||||
|
|
||||||
except ConnectionClosed:
|
except ConnectionClosed:
|
||||||
return
|
return
|
||||||
@ -380,6 +394,9 @@ if __name__ == '__main__':
|
|||||||
if db["sist2_admin"]["info"]["version"] == "2":
|
if db["sist2_admin"]["info"]["version"] == "2":
|
||||||
logger.error("Cannot migrate database from v2 to v3. Delete state.db to proceed.")
|
logger.error("Cannot migrate database from v2 to v3. Delete state.db to proceed.")
|
||||||
exit(-1)
|
exit(-1)
|
||||||
|
if db["sist2_admin"]["info"]["version"] == "3":
|
||||||
|
logger.info("Migrating to v4 database schema")
|
||||||
|
migrate_v3_to_v4(db)
|
||||||
|
|
||||||
start_frontends()
|
start_frontends()
|
||||||
cron.initialize(db, _run_job)
|
cron.initialize(db, _run_job)
|
||||||
|
@ -16,7 +16,7 @@ from pydantic import BaseModel
|
|||||||
from config import logger, LOG_FOLDER
|
from config import logger, LOG_FOLDER
|
||||||
from notifications import Notifications
|
from notifications import Notifications
|
||||||
from sist2 import ScanOptions, IndexOptions, Sist2
|
from sist2 import ScanOptions, IndexOptions, Sist2
|
||||||
from state import RUNNING_FRONTENDS
|
from state import RUNNING_FRONTENDS, get_log_files_to_remove, delete_log_file
|
||||||
from web import Sist2Frontend
|
from web import Sist2Frontend
|
||||||
|
|
||||||
|
|
||||||
@ -35,6 +35,8 @@ class Sist2Job(BaseModel):
|
|||||||
cron_expression: str
|
cron_expression: str
|
||||||
schedule_enabled: bool = False
|
schedule_enabled: bool = False
|
||||||
|
|
||||||
|
keep_last_n_logs: int = -1
|
||||||
|
|
||||||
previous_index: str = None
|
previous_index: str = None
|
||||||
index_path: str = None
|
index_path: str = None
|
||||||
previous_index_path: str = None
|
previous_index_path: str = None
|
||||||
@ -301,8 +303,14 @@ class TaskQueue:
|
|||||||
"ended": task.ended,
|
"ended": task.ended,
|
||||||
"started": task.started,
|
"started": task.started,
|
||||||
"name": task.display_name,
|
"name": task.display_name,
|
||||||
"return_code": task_result
|
"return_code": task_result,
|
||||||
|
"has_logs": 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logs_to_delete = get_log_files_to_remove(self._db, task.job.name, task.job.keep_last_n_logs)
|
||||||
|
for row in logs_to_delete:
|
||||||
|
delete_log_file(self._db, row["id"])
|
||||||
|
|
||||||
if isinstance(task, Sist2IndexTask):
|
if isinstance(task, Sist2IndexTask):
|
||||||
self._notifications.notify({
|
self._notifications.notify({
|
||||||
"message": "notifications.indexCompleted",
|
"message": "notifications.indexCompleted",
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from hexlib.db import Table, PersistentState
|
from hexlib.db import Table, PersistentState
|
||||||
import pickle
|
import pickle
|
||||||
|
|
||||||
from tesseract import get_tesseract_langs
|
from tesseract import get_tesseract_langs
|
||||||
|
import sqlite3
|
||||||
|
from config import LOG_FOLDER
|
||||||
|
|
||||||
RUNNING_FRONTENDS: Dict[str, int] = {}
|
RUNNING_FRONTENDS: Dict[str, int] = {}
|
||||||
|
|
||||||
TESSERACT_LANGS = get_tesseract_langs()
|
TESSERACT_LANGS = get_tesseract_langs()
|
||||||
|
|
||||||
DB_SCHEMA_VERSION = "3"
|
DB_SCHEMA_VERSION = "4"
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
@ -50,8 +53,35 @@ class PickleTable(Table):
|
|||||||
yield dict((k, _deserialize(v)) for k, v in row.items())
|
yield dict((k, _deserialize(v)) for k, v in row.items())
|
||||||
|
|
||||||
|
|
||||||
def migrate_v1_to_v2(db: PersistentState):
|
def get_log_files_to_remove(db: PersistentState, job_name: str, n: int):
|
||||||
|
if n < 0:
|
||||||
|
return []
|
||||||
|
|
||||||
|
counter = 0
|
||||||
|
to_remove = []
|
||||||
|
|
||||||
|
for row in db["task_done"].sql("WHERE has_logs=1 ORDER BY started DESC"):
|
||||||
|
if row["name"].endswith(f"[{job_name}]"):
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
if counter > n:
|
||||||
|
to_remove.append(row)
|
||||||
|
|
||||||
|
return to_remove
|
||||||
|
|
||||||
|
|
||||||
|
def delete_log_file(db: PersistentState, task_id: str):
|
||||||
|
db["task_done"][task_id] = {
|
||||||
|
"has_logs": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.remove(os.path.join(LOG_FOLDER, f"sist2-{task_id}.log"))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_v1_to_v2(db: PersistentState):
|
||||||
shutil.copy(db.dbfile, db.dbfile + "-before-migrate-v2.bak")
|
shutil.copy(db.dbfile, db.dbfile + "-before-migrate-v2.bak")
|
||||||
|
|
||||||
# Frontends
|
# Frontends
|
||||||
@ -77,3 +107,16 @@ def migrate_v1_to_v2(db: PersistentState):
|
|||||||
db["sist2_admin"]["info"] = {
|
db["sist2_admin"]["info"] = {
|
||||||
"version": "2"
|
"version": "2"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_v3_to_v4(db: PersistentState):
|
||||||
|
shutil.copy(db.dbfile, db.dbfile + "-before-migrate-v4.bak")
|
||||||
|
|
||||||
|
conn = sqlite3.connect(db.dbfile)
|
||||||
|
conn.execute("ALTER TABLE task_done ADD COLUMN has_logs INTEGER DEFAULT 1")
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
db["sist2_admin"]["info"] = {
|
||||||
|
"version": "4"
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user