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:
15
TODO
15
TODO
@@ -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
|
||||
|
||||
|
||||
2
files.py
2
files.py
@@ -593,7 +593,7 @@ def move_files():
|
||||
return make_response( jsonify(
|
||||
job_id=job.id,
|
||||
message=f"Created <a class='link-light' href=/job/{job.id}>Job #{job.id}</a> 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"])
|
||||
|
||||
@@ -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
9
job.py
@@ -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 ) )
|
||||
|
||||
###############################################################################
|
||||
|
||||
@@ -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 <a href="javascript:document.body.innerHTML+=\'<form id=_fm method=GET action=/stale_jobs></form>\'; document.getElementById(\'_fm\').submit();">here</a> to restart or cancel' )
|
||||
MessageToFE( job.id, "danger", f'Stale job, click <a href="javascript:document.body.innerHTML+=\'<form id=_fm method=GET action=/stale_jobs></form>\'; document.getElementById(\'_fm\').submit();">here</a> 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 <a href="javascript:document.body.innerHTML+=\'<form id=_fm method=POST action=/fix_dups></form>\'; document.getElementById(\'_fm\').submit();">here</a> to finalise import by removing duplicates' )
|
||||
MessageToFE( job.id, "danger", f'Found duplicate(s), click <a href="javascript:document.body.innerHTML+=\'<form id=_fm method=POST action=/fix_dups></form>\'; document.getElementById(\'_fm\').submit();">here</a> 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
|
||||
|
||||
|
||||
10
status.py
10
status.py
@@ -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 ""
|
||||
|
||||
|
||||
@@ -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) );
|
||||
|
||||
|
||||
@@ -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">×</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 %}
|
||||
|
||||
Reference in New Issue
Block a user