1
0
mirror of https://github.com/donaldzou/WGDashboard.git synced 2024-11-22 07:10:09 +01:00

Job logs api are done, still need to build the UI to view logs

This commit is contained in:
Donald Zou 2024-07-27 18:51:43 -04:00
parent 48dc8033f5
commit b65828416f
8 changed files with 189 additions and 86 deletions

View File

@ -83,7 +83,61 @@ class CustomJsonEncoder(DefaultJSONProvider):
app.json = CustomJsonEncoder(app) app.json = CustomJsonEncoder(app)
class Log:
def __init__(self, LogID: str, JobID: str, LogDate: str, Status: str, Message: str):
self.LogID = LogID
self.JobID = JobID
self.LogDate = LogDate
self.Status = Status
self.Message = Message
def toJson(self):
return {
"LogID": self.LogID,
"JobID": self.JobID,
"LogDate": self.LogDate,
"Status": self.Status,
"Message": self.Message
}
class Logger:
def __init__(self):
self.loggerdb = sqlite3.connect(os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard_log.db'),
check_same_thread=False)
self.loggerdb.row_factory = sqlite3.Row
self.logs:list(Log) = []
self.loggerdbCursor = self.loggerdb.cursor()
self.__createLogDatabase()
def __createLogDatabase(self):
existingTable = self.loggerdbCursor.execute("SELECT name from sqlite_master where type='table'").fetchall()
existingTable = [t['name'] for t in existingTable]
if "JobLog" not in existingTable:
self.loggerdbCursor.execute("CREATE TABLE JobLog (LogID VARCHAR NOT NULL, JobID NOT NULL, LogDate DATETIME DEFAULT (strftime('%Y-%m-%d %H:%M:%S','now')), Status VARCHAR NOT NULL, Message VARCHAR, PRIMARY KEY (LogID))")
self.loggerdb.commit()
def log(self, JobID: str, Status: bool = True, Message: str = "") -> bool:
try:
self.loggerdbCursor.execute(f"INSERT INTO JobLog (LogID, JobID, Status, Message) VALUES (?, ?, ?, ?)",
(str(uuid.uuid4()), JobID, Status, Message,))
self.loggerdb.commit()
self.getLogs()
except Exception as e:
print(e)
return False
return True
def getLogs(self, all: bool = False):
try:
table = self.loggerdb.execute(f"SELECT * FROM JobLog ORDER BY LogDate DESC {'LIMIT 5' if not all else ''}").fetchall()
self.logs.clear()
for l in table:
self.logs.append(
Log(l["LogID"], l["JobID"], l["LogDate"], l["Status"], l["Message"]))
except Exception as e:
pass
class PeerJob: class PeerJob:
def __init__(self, JobID: str, Configuration: str, Peer: str, def __init__(self, JobID: str, Configuration: str, Peer: str,
Field: str, Operator: str, Value: str, CreationDate: datetime, ExpireDate: datetime, Action: str): Field: str, Operator: str, Value: str, CreationDate: datetime, ExpireDate: datetime, Action: str):
@ -146,13 +200,6 @@ class PeerJobs:
''') ''')
self.jobdb.commit() self.jobdb.commit()
if "PeerJobs_Logs" not in existingTable:
self.jobdbCursor.execute('''
CREATE TABLE PeerJobs_Logs (LogID VARCHAR NOT NULL, JobID NOT NULL, LogDate DATETIME, Status VARCHAR NOT NULL,
Message VARCHAR NOT NULL, PRIMARY KEY (LogID))
''')
self.jobdb.commit()
def toJson(self): def toJson(self):
return [x.toJson() for x in self.Jobs] return [x.toJson() for x in self.Jobs]
@ -182,7 +229,7 @@ class PeerJobs:
if (len(str(Job.CreationDate))) == 0: if (len(str(Job.CreationDate))) == 0:
return False, "Job does not exist" return False, "Job does not exist"
self.jobdbCursor.execute(''' self.jobdbCursor.execute('''
DELETE FROM PeerJobs WHERE JobID = ? UPDATE PeerJobs SET ExpireDate = strftime('%Y-%m-%d %H:%M:%S','now') WHERE JobID = ?
''', (Job.JobID,)) ''', (Job.JobID,))
self.jobdb.commit() self.jobdb.commit()
self.__getJobs() self.__getJobs()
@ -192,23 +239,22 @@ class PeerJobs:
except Exception as e: except Exception as e:
return False, str(e) return False, str(e)
def finishJob(self, Job: PeerJob) -> tuple[bool, list] | tuple[bool, str]: # def finishJob(self, Job: PeerJob) -> tuple[bool, list] | tuple[bool, str]:
try: # try:
if (len(str(Job.CreationDate))) == 0: # if (len(str(Job.CreationDate))) == 0:
return False, "Job does not exist" # return False, "Job does not exist"
self.jobdbCursor.execute(''' # self.jobdbCursor.execute('''
UPDATE PeerJobs SET ExpireDate = strftime('%Y-%m-%d %H:%M:%S','now') WHERE JobId = ? # UPDATE PeerJobs SET ExpireDate = strftime('%Y-%m-%d %H:%M:%S','now') WHERE JobId = ?
''', (Job.JobID,)) # ''', (Job.JobID,))
self.jobdb.commit() # self.jobdb.commit()
self.__getJobs() # self.__getJobs()
return True, list( # return True, list(
filter(lambda x: x.Configuration == Job.Configuration and x.Peer == Job.Peer and x.JobID == Job.JobID, # filter(lambda x: x.Configuration == Job.Configuration and x.Peer == Job.Peer and x.JobID == Job.JobID,
self.Jobs)) # self.Jobs))
except Exception as e: # except Exception as e:
return False, str(e) # return False, str(e)
def runJob(self): def runJob(self):
print("======")
needToDelete = [] needToDelete = []
for job in self.Jobs: for job in self.Jobs:
print(job.toJson()) print(job.toJson())
@ -222,22 +268,32 @@ class PeerJobs:
y: float = float(job.Value) y: float = float(job.Value)
else: else:
x: datetime = datetime.now() x: datetime = datetime.now()
y: datetime = datetime.fromtimestamp(float(job.Value)) y: datetime = datetime.strptime(job.Value, "%Y-%m-%dT%H:%M")
runAction: bool = self.__runJob_Compare(x, y, job.Operator) runAction: bool = self.__runJob_Compare(x, y, job.Operator)
print("Running Job:" + str(runAction) + "\n") print("Running Job:" + str(runAction) + "\n")
if runAction: if runAction:
s = False
if job.Action == "restrict": if job.Action == "restrict":
print(str(c.restrictPeers([fp.id]).get_json())) s = c.restrictPeers([fp.id]).get_json()
elif job.Action == "delete": elif job.Action == "delete":
print(str(c.deletePeers([fp.id]).get_json())) s = c.deletePeers([fp.id]).get_json()
needToDelete.append(job)
if s['status'] is True:
JobLogger.log(job.JobID, s["status"],
f"Peer {fp.id} from {c.Name} is successfully {job.Action}ed."
)
needToDelete.append(job)
else:
JobLogger.log(job.JobID, s["status"],
f"Peer {fp.id} from {c.Name} failed {job.Action}ed."
)
print(f'''[{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}] Peer Job Schedule: Ran {len(needToDelete)} job(s)''')
for j in needToDelete: for j in needToDelete:
self.deleteJob(j) self.deleteJob(j)
def __runJob_Compare(self, x: float | datetime, y: float | datetime, operator: str): def __runJob_Compare(self, x: float | datetime, y: float | datetime, operator: str):
print(x, y, operator)
if operator == "eq": if operator == "eq":
return x == y return x == y
if operator == "neq": if operator == "neq":
@ -990,6 +1046,7 @@ class DashboardConfig:
DashboardConfig = DashboardConfig() DashboardConfig = DashboardConfig()
WireguardConfigurations: dict[str, WireguardConfiguration] = {} WireguardConfigurations: dict[str, WireguardConfiguration] = {}
AllPeerJobs: PeerJobs = PeerJobs() AllPeerJobs: PeerJobs = PeerJobs()
JobLogger: Logger = Logger()
''' '''
Private Functions Private Functions
@ -1678,8 +1735,10 @@ def backGroundThread():
def peerJobScheduleBackgroundThread(): def peerJobScheduleBackgroundThread():
with app.app_context(): with app.app_context():
print("-- Peer Schedule: Waiting 5 sec") print(f'''[{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}] Peer Job Schedule: Waiting for 10 Seconds''')
time.sleep(10)
while True: while True:
print(f'''[{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}] Peer Job Schedule: Running''')
AllPeerJobs.runJob() AllPeerJobs.runJob()
time.sleep(10) time.sleep(10)
@ -1701,9 +1760,9 @@ bgThread = threading.Thread(target=backGroundThread)
bgThread.daemon = True bgThread.daemon = True
bgThread.start() bgThread.start()
bg2Thread = threading.Thread(target=peerJobScheduleBackgroundThread) scheduleJobThread = threading.Thread(target=peerJobScheduleBackgroundThread)
bg2Thread.daemon = True scheduleJobThread.daemon = True
bg2Thread.start() scheduleJobThread.start()
if __name__ == "__main__": if __name__ == "__main__":
app.run(host=app_ip, debug=False, port=app_port) app.run(host=app_ip, debug=False, port=app_port)

View File

@ -12,9 +12,24 @@ const store = DashboardConfigurationStore();
</nav> </nav>
<Suspense> <Suspense>
<RouterView v-slot="{ Component }"> <RouterView v-slot="{ Component }">
<Transition name="fade" mode="out-in"> <Transition name="fade2" mode="out-in">
<Component :is="Component"></Component> <Component :is="Component"></Component>
</Transition> </Transition>
</RouterView> </RouterView>
</Suspense> </Suspense>
</template> </template>
<style scoped>
.app-enter-active,
.app-leave-active {
transition: all 0.3s ease-in-out;
/*position: absolute;*/
/*padding-top: 50px*/
}
.app-enter-from,
.app-leave-to {
transform: translateX(-30px);
opacity: 0;
}
</style>

View File

@ -34,9 +34,8 @@ export default {
</script> </script>
<template> <template>
<div class="card shadow-sm rounded-3" <div class="card shadow-sm rounded-3 peerCard bg-transparent"
:class="{'border-warning': Peer.restricted}" :class="{'border-warning': Peer.restricted}">
>
<div> <div>
<div v-if="!Peer.restricted" class="card-header bg-transparent d-flex align-items-center gap-2 border-0"> <div v-if="!Peer.restricted" class="card-header bg-transparent d-flex align-items-center gap-2 border-0">
<div class="dot ms-0" :class="{active: Peer.status === 'running'}"></div> <div class="dot ms-0" :class="{active: Peer.status === 'running'}"></div>
@ -77,8 +76,7 @@ export default {
<div class="ms-auto px-2 rounded-3 subMenuBtn" <div class="ms-auto px-2 rounded-3 subMenuBtn"
:class="{active: this.subMenuOpened}" :class="{active: this.subMenuOpened}"
> >
<a role="button" class="text-body" <a role="button" class="text-body"
@click="this.subMenuOpened = true"> @click="this.subMenuOpened = true">
<h5 class="mb-0"><i class="bi bi-three-dots"></i></h5> <h5 class="mb-0"><i class="bi bi-three-dots"></i></h5>
</a> </a>
@ -101,7 +99,7 @@ export default {
<style scoped> <style scoped>
.slide-fade-leave-active, .slide-fade-enter-active { .slide-fade-leave-active, .slide-fade-enter-active{
transition: all 0.2s cubic-bezier(1, 0.5, 0.8, 1); transition: all 0.2s cubic-bezier(1, 0.5, 0.8, 1);
} }
@ -114,4 +112,12 @@ export default {
.subMenuBtn.active{ .subMenuBtn.active{
background-color: #ffffff20; background-color: #ffffff20;
} }
.peerCard{
transition: box-shadow 0.1s cubic-bezier(1, 0.5, 0.8, 1);
}
.peerCard:hover{
box-shadow: var(--bs-box-shadow) !important;
}
</style> </style>

View File

@ -88,7 +88,8 @@ export default {
})) }))
) )
} }
} },
} }
</script> </script>
@ -113,11 +114,11 @@ export default {
<TransitionGroup name="schedulePeerJobTransition" tag="div" class="position-relative"> <TransitionGroup name="schedulePeerJobTransition" tag="div" class="position-relative">
<SchedulePeerJob <SchedulePeerJob
@refresh="(j) => job = j" @refresh="(j) => this.selectedPeer.jobs[index] = j"
@delete="this.deleteJob(job)" @delete="this.deleteJob(job)"
:dropdowns="this.dropdowns" :dropdowns="this.dropdowns"
:key="job.JobID" :key="job.JobID"
:pjob="job" v-for="(job) in this.selectedPeer.jobs"> :pjob="job" v-for="(job, index) in this.selectedPeer.jobs">
</SchedulePeerJob> </SchedulePeerJob>
<div class="card" key="none" v-if="this.selectedPeer.jobs.length === 0"> <div class="card" key="none" v-if="this.selectedPeer.jobs.length === 0">

View File

@ -173,6 +173,19 @@ export default {
}, (res) => { }, (res) => {
this.configurationInfo = res.data.configurationInfo; this.configurationInfo = res.data.configurationInfo;
this.configurationPeers = res.data.configurationPeers; this.configurationPeers = res.data.configurationPeers;
// let modals = [this.peerSetting, this.peerScheduleJobs, this.peerQRCode]
// modals.forEach(x => {
//
// if (x.modalOpen && this.configurationPeers.find(p => p.id === x.selectedPeer.id)){
// x.selectedPeer = this.configurationPeers.find(p => p.id === x.selectedPeer.id)
// console.log(this.configurationPeers.find(p => p.id === x.selectedPeer.id))
// }else{
// x.modalOpen = false
// }
// })
this.configurationPeers.forEach(x => { this.configurationPeers.forEach(x => {
x.restricted = false; x.restricted = false;
}) })

View File

@ -33,8 +33,9 @@ export default {
deep: true, deep: true,
immediate: true, immediate: true,
handler(newValue){ handler(newValue){
console.log(newValue) if (!this.edit){
this.job = JSON.parse(JSON.stringify(newValue)) this.job = JSON.parse(JSON.stringify(newValue))
}
} }
} }
}, },
@ -47,7 +48,8 @@ export default {
if (res.status){ if (res.status){
this.edit = false; this.edit = false;
this.store.newMessage("Server", "Job Saved!", "success") this.store.newMessage("Server", "Job Saved!", "success")
this.$emit("refresh", this.data) console.log(res.data)
this.$emit("refresh", res.data[0])
this.newJob = false; this.newJob = false;
}else{ }else{
this.store.newMessage("Server", res.message, "danger") this.store.newMessage("Server", res.message, "danger")

View File

@ -12,7 +12,7 @@ export default {
return {store, wireguardConfigurationStore} return {store, wireguardConfigurationStore}
}, },
props: { props: {
searchString: String,
configuration: Object configuration: Object
}, },
data(){ data(){
@ -28,10 +28,24 @@ export default {
'10000': '10 Seconds', '10000': '10 Seconds',
'30000': '30 Seconds', '30000': '30 Seconds',
'60000': '1 Minutes' '60000': '1 Minutes'
} },
searchString: "",
searchStringTimeout: undefined
} }
}, },
methods: { methods: {
debounce(){
if (!this.searchStringTimeout){
this.searchStringTimeout = setTimeout(() => {
this.wireguardConfigurationStore.searchString = this.searchString;
}, 300)
}else{
clearTimeout(this.searchStringTimeout)
this.searchStringTimeout = setTimeout(() => {
this.wireguardConfigurationStore.searchString = this.searchString;
}, 300)
}
},
updateSort(sort){ updateSort(sort){
fetchPost("/api/updateDashboardConfigurationItem", { fetchPost("/api/updateDashboardConfigurationItem", {
section: "Server", section: "Server",
@ -68,25 +82,34 @@ export default {
</script> </script>
<template> <template>
<div> <div class="mb-3">
<div class="d-flex gap-2 mb-3 z-3"> <div class="d-flex gap-2 z-3">
<RouterLink <RouterLink
to="create" to="create"
class="text-decoration-none btn btn-primary rounded-3 btn-sm"> class="text-decoration-none btn text-primary-emphasis bg-primary-subtle rounded-3 border-1 border-primary-subtle shadow-sm">
<i class="bi bi-plus-lg me-2"></i>Peers <i class="bi bi-plus-lg me-2"></i>Peer
</RouterLink> </RouterLink>
<!-- <RouterLink--> <!-- <RouterLink-->
<!-- to="jobs"--> <!-- to="jobs"-->
<!-- class="text-decoration-none btn btn-primary rounded-3 btn-sm">--> <!-- class="text-decoration-none btn btn-primary rounded-3 btn-sm">-->
<!-- <i class="bi bi-app-indicator me-2"></i>Jobs--> <!-- <i class="bi bi-app-indicator me-2"></i>Jobs-->
<!-- </RouterLink>--> <!-- </RouterLink>-->
<button class="btn btn-sm btn-primary rounded-3" @click="this.downloadAllPeer()"> <button class="btn text-primary-emphasis bg-primary-subtle rounded-3 border-1 border-primary-subtle shadow-sm"
@click="this.downloadAllPeer()">
<i class="bi bi-download me-2"></i> Download All <i class="bi bi-download me-2"></i> Download All
</button> </button>
<div class="d-flex align-items-center ms-auto">
<div class="dropdown ms-auto"> <!-- <label class="d-flex me-2 text-muted" for="searchPeers"><i class="bi bi-search me-1"></i></label>-->
<button class="btn btn-secondary btn-sm dropdown-toggle rounded-3" type="button" data-bs-toggle="dropdown" aria-expanded="false"> <input class="form-control rounded-3 bg-secondary-subtle border-1 border-secondary-subtle shadow-sm"
placeholder="Search..."
id="searchPeers"
@keyup="this.debounce()"
v-model="this.searchString">
</div>
<div class="dropdown">
<button class="btn dropdown-toggle text-secondary-emphasis bg-secondary-subtle rounded-3 border-1 border-secondary-subtle shadow-sm"
type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-filter-circle me-2"></i> <i class="bi bi-filter-circle me-2"></i>
Sort Sort
</button> </button>
@ -101,7 +124,8 @@ export default {
</ul> </ul>
</div> </div>
<div class="dropdown"> <div class="dropdown">
<button class="btn btn-secondary btn-sm dropdown-toggle rounded-3" type="button" data-bs-toggle="dropdown" aria-expanded="false"> <button class="btn dropdown-toggle text-secondary-emphasis bg-secondary-subtle rounded-3 border-1 border-secondary-subtle shadow-sm"
type="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-arrow-repeat me-2"></i>Refresh Interval <i class="bi bi-arrow-repeat me-2"></i>Refresh Interval
</button> </button>
<ul class="dropdown-menu shadow mt-2 rounded-3"> <ul class="dropdown-menu shadow mt-2 rounded-3">
@ -113,24 +137,7 @@ export default {
</a></li> </a></li>
</ul> </ul>
</div> </div>
<!-- <button class="btn btn-outline-secondary btn-sm rounded-3" type="button"-->
<!-- @click="this.store.Peers.Selecting = !this.store.Peers.Selecting"-->
<!-- >-->
<!-- <i class="bi bi-app-indicator me-2"></i>-->
<!-- Select-->
<!-- </button>-->
<div class="d-flex align-items-center">
<!-- <label class="d-flex me-2 text-muted" for="searchPeers"><i class="bi bi-search me-1"></i></label>-->
<input class="form-control form-control-sm rounded-3"
placeholder="Search..."
id="searchPeers"
v-model="this.wireguardConfigurationStore.searchString">
</div>
</div> </div>
</div> </div>
</template> </template>

View File

@ -1097,17 +1097,17 @@ pre.index-alert {
.list-move, /* apply transition to moving elements */ .list-move, /* apply transition to moving elements */
.list-enter-active, .list-enter-active,
.list-leave-active { .list-leave-active {
transition: all 0.4s cubic-bezier(0.82, 0.58, 0.17, 0.9); transition: all 0.5s ease-in-out;
} }
.list-leave-active{ /*.list-leave-active{*/
position: absolute; /* position: absolute !important;*/
} /*}*/
.list-enter-from, .list-enter-from,
.list-leave-to { .list-leave-to {
opacity: 0; opacity: 0;
transform: translateY(30px); transform: scale(1.1);
} }
/* ensure leaving items are taken out of layout flow so that moving /* ensure leaving items are taken out of layout flow so that moving