added persistent and cant_close to PA_JobManager_FE_Message, used them from pa_job_manager to set status messages with persistence/close buttons appropriately for items like fix_dups/stale_jobs. When "fixing" now, the persistent Status message stays, but its now positioned approx. below the navbar on the right and is ok. Started on changing status to a more sensible naming conventions (away from alert to level) - more work to complete this

This commit is contained in:
2023-01-10 17:45:02 +11:00
parent 56c2d586b6
commit 0784861331
8 changed files with 46 additions and 64 deletions

15
TODO
View File

@@ -1,17 +1,7 @@
### GENERAL
* get all status messages to use toasts AND get func to also increase/descrease the job counter as appropriate)
- [DONE] all (success/creation) status messages use toasts
-- [DONE] make a helper func for setting toast body and use it in base.html && file_support.js
-- [DONE] make a helper func for setting 'Active Jobs' text/badge and call it when document ready (rather/both than start of base.html)
-- [DONE] trigger a timeout to play back pa_job_mgr message to FE and reset 'Active Jobs' text/badge until it hits 0
-- [DONE] (re)start this code if any new jobs is created (on move ONLY FOR NOW
-- [DONE] make js StatusMsg() take a 'data' (json response) and process that
-- [DONE] make it include message from the API endpoint doing the work, not in the js code...
-- [TODO] fix base.html to only call toast() and only in document.ready()
-- [DONE] this means handling danger, and persistent / no time-out toast()s
-- [DONE] warning works now (colors, etc.)
-- [DONE] clean up has to be a POST back to server to clear DB (jscript/async has no other way)
-- [TODO] go through the crazy persistent status msgs for checkdups, etc. and make sure they all work
-- [TODO] ensure all pa_job_mgr jobs are sending messages back to the FE -- SHOULD??? I just hook FinishJob???
* should be using jsonify to return real json to my API calls, e.g:
use make_response( jsonify (... ) )
@@ -19,7 +9,8 @@
all GETs stay as is for now (as they expect a html reply, not data, and then html is base.html+ and it handles the status)
-- ONLY time this will fail is multiple status messages (which can occur I believe if a Wake of job mgr and then a job is created in DB, the success will only be shown)
-- [TODO] simple fix will be to get 'hidden' jobs (wake job_mgr and maybe? metadata) to post failure events to the JobMgr_FE DB queue
-- [TODO] when ALL converted, replace 'alert="..."' with 'status="..."'
-- [TODO] -- change class Status to class UILog
-- [TODO] -- change alert to level
* delete files should behave like /move_files (stay on same page) as well as the status messages above

View File

@@ -593,7 +593,7 @@ def move_files():
return make_response( jsonify(
job_id=job.id,
message=f"Created&nbsp;<a class='link-light' href=/job/{job.id}>Job #{job.id}</a>&nbsp;to move selected file(s)",
status="success", alert="success" ) )
level="success", alert="success", persistent=False, cant_close=False ) )
@login_required
@app.route("/viewlist", methods=["POST"])

View File

@@ -3,11 +3,14 @@ var next_toast_id=1
function NewToast(data)
{
console.log(data)
// make new div, include data.alert as background colour, and data.message as toast body
d_id='st' + String(next_toast_id)
div='<div id="' + d_id + '"'
if( data.persistent === true )
div+=' data-bs-autohide="false"'
if( data.job_id !== undefined )
div+=' job_id=' + String(data.job_id)
div +=' class="toast hide align-items-center border-0'
if( data.alert == "success" || data.alert == "danger" )
div += ' text-white'
@@ -42,6 +45,7 @@ function NewToast(data)
// can reuse any that are hidden, OR, create a new one by appending as needed (so we can have 2+ toasts on screen)
function StatusMsg(st)
{
console.log('StatusMsg' + st )
el=NewToast(st)
$('#' + el ).toast("show")
// if there is a job_id, then clear the message for it or it will be picked up again on reload
@@ -49,7 +53,11 @@ function StatusMsg(st)
// now, we will do this to get a first pass working
if( st.job_id !== undefined )
{
$.ajax( { type: 'POST', url: '/clearmsgforjob/'+st.job_id, success: function(data) { } } )
console.log( 'set hidden.bs.toast handler for: ' + st.job_id )
$('#' + el).on( 'hidden.bs.toast',
function() {
$.ajax( { type: 'POST', url: '/clearmsgforjob/'+st.job_id, success: function(data) { console.log('cleared job id' )} } )
} )
}
}

9
job.py
View File

@@ -74,6 +74,8 @@ class PA_JobManager_Message(db.Model):
job_id = db.Column(db.Integer, db.ForeignKey('job.id') )
alert = db.Column(db.String)
message = db.Column(db.String)
persistent = db.Column(db.Boolean)
cant_close = db.Column(db.Boolean)
job = db.relationship ("Job" )
def __repr__(self):
return f"<id: {self.id}, job_id: {self.job_id}, alert: {self.alert}, message: {self.message}, job: {self.job}"
@@ -90,6 +92,8 @@ def GetJM_Message():
# ClearJM_Message: used in html to clear any message just displayed
################################################################################
def ClearJM_Message(id):
print(f"DDP: DID NOT clear JM message: {id}" )
return
PA_JobManager_Message.query.filter(PA_JobManager_Message.id==id).delete()
db.session.commit()
return
@@ -307,12 +311,11 @@ def joblog_search():
@login_required
def CheckForJobs():
num=GetNumActiveJobs()
print( f"called: /checkforjobs -- num={num}" )
sts=[]
print("CheckForJobs called" )
for msg in PA_JobManager_Message.query.all():
print("there is a PA_J_MGR status message" )
u='<a class="link-light" href="' + url_for('joblog', id=msg.job_id) + '">Job # ' + str(msg.job_id) + '</a>: '
sts.append( { 'message': u+msg.message, 'alert': msg.alert, 'job_id': msg.job_id } )
sts.append( { 'message': u+msg.message, 'alert': msg.alert, 'job_id': msg.job_id, 'persistent': msg.persistent, 'cant_close': msg.cant_close } )
return make_response( jsonify( num_active_jobs=num, sts=sts ) )
###############################################################################

View File

@@ -503,6 +503,8 @@ class PA_JobManager_FE_Message(Base):
job_id = Column(Integer, ForeignKey('job.id') )
alert = Column(String)
message = Column(String)
persistent = Column(Boolean)
cant_close = Column(Boolean)
def __repr__(self):
return "<id: {}, job_id: {}, alert: {}, message: {}".format(self.id, self.job_id, self.alert, self.message)
@@ -561,8 +563,8 @@ def NewJob(name, num_files=0, wait_for=None, jex=None, parent_job=None ):
# MessageToFE(): sends a specific alert/messasge for a given job via the DB to
# the front end
##############################################################################
def MessageToFE( job_id, alert, message ):
msg = PA_JobManager_FE_Message( job_id=job_id, alert=alert, message=message)
def MessageToFE( job_id, alert, message, persistent, cant_close ):
msg = PA_JobManager_FE_Message( job_id=job_id, alert=alert, message=message, persistent=persistent, cant_close=cant_close)
session.add(msg)
session.commit()
return msg.id
@@ -904,7 +906,7 @@ def HandleJobs(first_run=False):
job.pa_job_state = 'Stale'
session.add(job)
AddLogForJob( job, "ERROR: Job has been marked stale as it did not complete" )
MessageToFE( job.id, "danger", f'Stale job, click&nbsp; <a href="javascript:document.body.innerHTML+=\'<form id=_fm method=GET action=/stale_jobs></form>\'; document.getElementById(\'_fm\').submit();">here</a>&nbsp;to restart or cancel' )
MessageToFE( job.id, "danger", f'Stale job, click&nbsp; <a href="javascript:document.body.innerHTML+=\'<form id=_fm method=GET action=/stale_jobs></form>\'; document.getElementById(\'_fm\').submit();">here</a>&nbsp;to restart or cancel', True, False )
session.commit()
continue
if job.pa_job_state == 'New':
@@ -930,7 +932,7 @@ def HandleJobs(first_run=False):
# threading.Thread(target=RunJob, args=(job,)).start()
except Exception as e:
try:
MessageToFE( job.id, "danger", "Failed with: {} (try job log for details)".format(e) )
MessageToFE( job.id, "danger", "Failed with: {} (try job log for details)".format(e), True, False )
except Exception as e2:
print("ERROR: Failed to let front-end know, but back-end Failed to run job (id: {}, name: {} -- orig exep was: {}, this exception was: {})".format( job.id, job.name, e, e2) )
print("INFO: PA job manager is waiting for a job")
@@ -954,7 +956,7 @@ def JobScanNow(job):
JobProgressState( job, "In Progress" )
ProcessImportDirs(job)
FinishJob( job, "Completed (scan for new files)" )
MessageToFE( job.id, "success", "Completed (scan for new files)" )
MessageToFE( job.id, "success", "Completed (scan for new files)", False, False )
return
##############################################################################
@@ -964,7 +966,7 @@ def JobScanStorageDir(job):
JobProgressState( job, "In Progress" )
ProcessStorageDirs(job)
FinishJob( job, "Completed (scan for new files)" )
MessageToFE( job.id, "success", "Completed (scan for new files)" )
MessageToFE( job.id, "success", "Completed (scan for new files)", False, False )
return
@@ -1078,7 +1080,7 @@ def JobForceScan(job):
ProcessImportDirs(job)
ProcessStorageDirs(job)
FinishJob(job, "Completed (forced remove and recreation of all file data)")
MessageToFE( job.id, "success", "Completed (forced remove and recreation of all file data)" )
MessageToFE( job.id, "success", "Completed (forced remove and recreation of all file data)", False, False )
return
##############################################################################
@@ -1319,6 +1321,10 @@ def MoveFileToRecycleBin(job,del_me):
bin_path=session.query(Path).join(PathType).filter(PathType.name=='Bin').first()
parent_dir=session.query(Dir).join(PathDirLink).filter(PathDirLink.path_id==bin_path.id).first()
# check/delete if we already have a deleted file of this name/number (only # happened in testing, but jic)
# 99.9% of the time, this will just delete nothing
session.query(DelFile).filter(DelFile.file_eid==del_me.id).delete()
# if we ever need to restore, lets remember this file's original path
# (use a string in case the dir/path is ever deleted from FS (and then DB) and we need to recreate)
del_file_details = DelFile( file_eid=del_me.id, orig_path_prefix=del_me.in_dir.in_path.path_prefix )
@@ -2036,7 +2042,7 @@ def JobCheckForDups(job):
for row in res:
if row.count > 0:
AddLogForJob(job, f"Found duplicates, Creating Status message in front-end for attention")
MessageToFE( job.id, "danger", f'Found duplicate(s), click&nbsp; <a href="javascript:document.body.innerHTML+=\'<form id=_fm method=POST action=/fix_dups></form>\'; document.getElementById(\'_fm\').submit();">here</a>&nbsp;to finalise import by removing duplicates' )
MessageToFE( job.id, "danger", f'Found duplicate(s), click&nbsp; <a href="javascript:document.body.innerHTML+=\'<form id=_fm method=POST action=/fix_dups></form>\'; document.getElementById(\'_fm\').submit();">here</a>&nbsp;to finalise import by removing duplicates', True, True )
else:
FinishJob(job, f"No duplicates found")
FinishJob(job, f"Finished looking for duplicates")
@@ -2107,7 +2113,8 @@ def JobRemoveDups(job):
# Need to put another checkdups job in now to force / validate we have no dups
next_job=NewJob( "checkdups" )
AddLogForJob(job, "adding <a href='/job/{}'>job id={} {}</a> to confirm there are no more duplicates".format( next_job.id, next_job.id, next_job.name ) )
AddLogForJob(job, f"adding <a href='/job/{next_job.id}'>job id={next_job.id} {next_job.name}</a> to confirm there are no more duplicates" )
MessageToFE( job.id, "success", f"Finished Job#{job.id} removing duplicate files", False, False )
return
####################################################################################################################################
@@ -2137,7 +2144,7 @@ def JobMoveFiles(job):
move_me=session.query(Entry).get(jex.value)
MoveEntriesToOtherFolder( job, move_me, dst_storage_path, f"{prefix}{suffix}" )
next_job=NewJob( "checkdups" )
MessageToFE( job.id, "success", "Completed (move of selected files)" )
MessageToFE( job.id, "success", "Completed (move of selected files)", False, False )
FinishJob(job, f"Finished move selected file(s)")
return
@@ -2152,7 +2159,7 @@ def JobDeleteFiles(job):
del_me=session.query(Entry).join(File).filter(Entry.id==jex.value).first()
MoveFileToRecycleBin(job,del_me)
next_job=NewJob( "checkdups" )
MessageToFE( job.id, "success", "Completed (delete of selected files)" )
MessageToFE( job.id, "success", "Completed (delete of selected files)", False, False )
FinishJob(job, f"Finished deleting selected file(s)")
return
@@ -2167,7 +2174,7 @@ def JobRestoreFiles(job):
restore_me=session.query(Entry).join(File).filter(Entry.id==jex.value).first()
RestoreFile(job,restore_me)
next_job=NewJob( "checkdups" )
MessageToFE( job.id, "success", "Completed (restore of selected files)" )
MessageToFE( job.id, "success", "Completed (restore of selected files)", False, False )
FinishJob(job, f"Finished restoring selected file(s)")
return

View File

@@ -5,18 +5,18 @@ from shared import PA
# including when duplicates are found on import. These status messages are exposed into
# the html files, and they show once for success/green, and sticky for danger/red
class Status(PA):
alert="success"
level="success"
message=""
def GetAlert(self):
return self.alert
return self.level
def GetMessage(self):
return self.message
def SetMessage(self, msg, al="success"):
def SetMessage(self, msg, l="success"):
self.message=msg
self.alert=al
self.level=l
return
def AppendMessage(self, msg):
@@ -24,7 +24,7 @@ class Status(PA):
return
def ClearStatus(self):
self.alert="success"
self.level="success"
self.message=""
return ""

View File

@@ -160,7 +160,7 @@ create table JOBEXTRA ( ID integer, JOB_ID integer, NAME varchar(32), VALUE varc
create table JOBLOG ( ID integer, JOB_ID integer, LOG_DATE timestamptz, LOG varchar,
constraint PK_JL_ID primary key(ID), constraint FK_JL_JOB_ID foreign key(JOB_ID) references JOB(ID) );
create table PA_JOB_MANAGER_FE_MESSAGE ( ID integer, JOB_ID integer, ALERT varchar(16), MESSAGE varchar(1024),
create table PA_JOB_MANAGER_FE_MESSAGE ( ID integer, JOB_ID integer, ALERT varchar(16), MESSAGE varchar(1024), PERSISTENT boolean, CANT_CLOSE boolean,
constraint PA_JOB_MANAGER_FE_ACKS_ID primary key(ID),
constraint FK_PA_JOB_MANAGER_FE_MESSAGE_JOB_ID foreign key(JOB_ID) references JOB(ID) );

View File

@@ -128,40 +128,14 @@
</div class="collapse navbar-collapse">
</div class="container-fluid">
</nav>
{% if GetJM_Message() != None %}
{% set msg=GetJM_Message() %}
<!-- if we are fixing things dont put up alert -->
{% if request.endpoint != "fix_dups" and request.endpoint != "rm_dups" and request.endpoint != "stale_jobs" %}
{% if msg.alert != "success" %}
<div class="py-2 mx-1 alert alert-{{msg.alert}}">
{% if msg.job.name != "checkdups" %}
<form id="_dismiss" action="{{url_for('clear_jm_msg', id=msg.id)}}" method="POST">
<button type="button" class="close btn border-secondary me-3" aria-label="Close" onClick="$('#_dismiss').submit()">
<span aria-hidden="true">&times;</span>
</button>
{% endif %}
{% if msg.job_id %}
<a href="{{url_for('joblog', id=msg.job_id)}}">Job #{{msg.job_id}}</a>:
{% endif %}
{{msg.message|safe}}
</form>
</div>
{# if a JM is a danger message, allow code to remove the status, it should be 'permanent' until addressed -- e.g. you have duplicates, fix them #}
{% if msg.alert != "danger" %}
{% set dont_print=ClearJM_Message(msg.id) %}
{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endif %} {# not InDBox #}
{% block main_content %}
{% endblock main_content %}
{% if not InDBox %}
{%block script_content %}{% endblock script_content %}
<div id="status_container" class="position-fixed top-1 end-0 p-1" style="z-index: 11">
<div id="status_container" class="position-fixed top-0 end-0 p-1 my-5" "z-index: 11">
<div id="st1" class="toast hide align-items-center text-white bg-success border-0" role="alert" aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div class="toast-body">
@@ -177,7 +151,6 @@
msg = "{{ GetMessage()|safe }}"
msg=msg.replace('href=', 'class=link-light href=')
st=Object; st.message=msg; st.alert='{{GetAlert()}}'; StatusMsg(st)
alert( '{{GetAlert()}}' )
<!-- call ClearStatus: strictly not needed as we are near finished rendering, and any new page will lose it from memory (better to be explicit) -->
{{ ClearStatus() }}
{% endif %}