history: report user in job history
When possible record the name of the user that requested the job. The klippy_api's component now takes an optional user argument in its "start_print" method. This user is broadcast in an event after a print request successfully returns. Signed-off-by: Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
parent
1dfbffb422
commit
531028ef4f
|
@ -65,10 +65,11 @@ if TYPE_CHECKING:
|
||||||
from io import BufferedReader
|
from io import BufferedReader
|
||||||
from .authorization import Authorization
|
from .authorization import Authorization
|
||||||
from .template import TemplateFactory, JinjaTemplate
|
from .template import TemplateFactory, JinjaTemplate
|
||||||
MessageDelgate = Optional[tornado.httputil.HTTPMessageDelegate]
|
MessageDelgate = Optional[HTTPMessageDelegate]
|
||||||
AuthComp = Optional[Authorization]
|
AuthComp = Optional[Authorization]
|
||||||
APICallback = Callable[[WebRequest], Coroutine]
|
APICallback = Callable[[WebRequest], Coroutine]
|
||||||
|
|
||||||
|
# mypy: disable-error-code="attr-defined,name-defined"
|
||||||
|
|
||||||
# 50 MiB Max Standard Body Size
|
# 50 MiB Max Standard Body Size
|
||||||
MAX_BODY_SIZE = 50 * 1024 * 1024
|
MAX_BODY_SIZE = 50 * 1024 * 1024
|
||||||
|
@ -1010,6 +1011,7 @@ class FileUploadHandler(AuthorizedRequestHandler):
|
||||||
for name, value in form_args.items():
|
for name, value in form_args.items():
|
||||||
debug_msg += f"\n{name}: {value}"
|
debug_msg += f"\n{name}: {value}"
|
||||||
debug_msg += f"\nChecksum: {calc_chksum}"
|
debug_msg += f"\nChecksum: {calc_chksum}"
|
||||||
|
form_args["current_user"] = self.current_user
|
||||||
logging.debug(debug_msg)
|
logging.debug(debug_msg)
|
||||||
logging.info(f"Processing Uploaded File: {self._file.multipart_filename}")
|
logging.info(f"Processing Uploaded File: {self._file.multipart_filename}")
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -880,7 +880,8 @@ class FileManager:
|
||||||
'start_print': start_print,
|
'start_print': start_print,
|
||||||
'unzip_ufp': unzip_ufp,
|
'unzip_ufp': unzip_ufp,
|
||||||
'ext': f_ext,
|
'ext': f_ext,
|
||||||
"is_link": os.path.islink(dest_path)
|
"is_link": os.path.islink(dest_path),
|
||||||
|
"user": upload_args.get("current_user")
|
||||||
}
|
}
|
||||||
|
|
||||||
async def _finish_gcode_upload(
|
async def _finish_gcode_upload(
|
||||||
|
@ -901,10 +902,11 @@ class FileManager:
|
||||||
started: bool = False
|
started: bool = False
|
||||||
queued: bool = False
|
queued: bool = False
|
||||||
if upload_info['start_print']:
|
if upload_info['start_print']:
|
||||||
|
user: Optional[Dict[str, Any]] = upload_info.get("user")
|
||||||
if can_start:
|
if can_start:
|
||||||
kapis: APIComp = self.server.lookup_component('klippy_apis')
|
kapis: APIComp = self.server.lookup_component('klippy_apis')
|
||||||
try:
|
try:
|
||||||
await kapis.start_print(upload_info['filename'])
|
await kapis.start_print(upload_info['filename'], user=user)
|
||||||
except self.server.error:
|
except self.server.error:
|
||||||
# Attempt to start print failed
|
# Attempt to start print failed
|
||||||
pass
|
pass
|
||||||
|
@ -913,7 +915,7 @@ class FileManager:
|
||||||
if self.queue_gcodes and not started:
|
if self.queue_gcodes and not started:
|
||||||
job_queue: JobQueue = self.server.lookup_component('job_queue')
|
job_queue: JobQueue = self.server.lookup_component('job_queue')
|
||||||
await job_queue.queue_job(
|
await job_queue.queue_job(
|
||||||
upload_info['filename'], check_exists=False)
|
upload_info['filename'], check_exists=False, user=user)
|
||||||
queued = True
|
queued = True
|
||||||
self.fs_observer.on_item_create("gcodes", upload_info["dest_path"])
|
self.fs_observer.on_item_create("gcodes", upload_info["dest_path"])
|
||||||
result = dict(self._sched_changed_event(
|
result = dict(self._sched_changed_event(
|
||||||
|
|
|
@ -65,6 +65,8 @@ class History:
|
||||||
"server:klippy_shutdown", self._handle_shutdown)
|
"server:klippy_shutdown", self._handle_shutdown)
|
||||||
self.server.register_event_handler(
|
self.server.register_event_handler(
|
||||||
"job_state:state_changed", self._on_job_state_changed)
|
"job_state:state_changed", self._on_job_state_changed)
|
||||||
|
self.server.register_event_handler(
|
||||||
|
"klippy_apis:job_start_complete", self._on_job_requested)
|
||||||
self.server.register_notification("history:history_changed")
|
self.server.register_notification("history:history_changed")
|
||||||
|
|
||||||
self.server.register_endpoint(
|
self.server.register_endpoint(
|
||||||
|
@ -88,6 +90,7 @@ class History:
|
||||||
|
|
||||||
self.current_job: Optional[PrinterJob] = None
|
self.current_job: Optional[PrinterJob] = None
|
||||||
self.current_job_id: Optional[str] = None
|
self.current_job_id: Optional[str] = None
|
||||||
|
self.job_user: str = "No User"
|
||||||
self.job_paused: bool = False
|
self.job_paused: bool = False
|
||||||
self.next_job_id: int = 0
|
self.next_job_id: int = 0
|
||||||
self.cached_job_ids = self.history_ns.keys().result()
|
self.cached_job_ids = self.history_ns.keys().result()
|
||||||
|
@ -249,6 +252,12 @@ class History:
|
||||||
# `CLEAR_PAUSE/SDCARD_RESET_FILE` workflow
|
# `CLEAR_PAUSE/SDCARD_RESET_FILE` workflow
|
||||||
self.finish_job("cancelled", prev_stats)
|
self.finish_job("cancelled", prev_stats)
|
||||||
|
|
||||||
|
def _on_job_requested(self, user: Optional[Dict[str, Any]]) -> None:
|
||||||
|
username = (user or {}).get("username", "No User")
|
||||||
|
self.job_user = username
|
||||||
|
if self.current_job is not None:
|
||||||
|
self.current_job.user = username
|
||||||
|
|
||||||
def _handle_shutdown(self) -> None:
|
def _handle_shutdown(self) -> None:
|
||||||
jstate: JobState = self.server.lookup_component("job_state")
|
jstate: JobState = self.server.lookup_component("job_state")
|
||||||
last_ps = jstate.get_last_stats()
|
last_ps = jstate.get_last_stats()
|
||||||
|
@ -265,6 +274,7 @@ class History:
|
||||||
job_id = f"{self.next_job_id:06X}"
|
job_id = f"{self.next_job_id:06X}"
|
||||||
self.current_job = job
|
self.current_job = job
|
||||||
self.current_job_id = job_id
|
self.current_job_id = job_id
|
||||||
|
self.current_job.user = self.job_user
|
||||||
self.grab_job_metadata()
|
self.grab_job_metadata()
|
||||||
for field in self.auxiliary_fields:
|
for field in self.auxiliary_fields:
|
||||||
field.tracker.reset()
|
field.tracker.reset()
|
||||||
|
@ -296,6 +306,7 @@ class History:
|
||||||
# Print stats have been reset, do not update this job with them
|
# Print stats have been reset, do not update this job with them
|
||||||
pstats = {}
|
pstats = {}
|
||||||
|
|
||||||
|
self.current_job.user = self.job_user
|
||||||
self.current_job.finish(status, pstats)
|
self.current_job.finish(status, pstats)
|
||||||
# Regrab metadata incase metadata wasn't parsed yet due to file upload
|
# Regrab metadata incase metadata wasn't parsed yet due to file upload
|
||||||
self.grab_job_metadata()
|
self.grab_job_metadata()
|
||||||
|
@ -310,6 +321,7 @@ class History:
|
||||||
self.send_history_event("finished")
|
self.send_history_event("finished")
|
||||||
self.current_job = None
|
self.current_job = None
|
||||||
self.current_job_id = None
|
self.current_job_id = None
|
||||||
|
self.job_user = "No User"
|
||||||
|
|
||||||
async def get_job(self,
|
async def get_job(self,
|
||||||
job_id: Union[int, str]
|
job_id: Union[int, str]
|
||||||
|
@ -425,6 +437,7 @@ class PrinterJob:
|
||||||
self.start_time = time.time()
|
self.start_time = time.time()
|
||||||
self.total_duration: float = 0.
|
self.total_duration: float = 0.
|
||||||
self.auxiliary_data: List[Dict[str, Any]] = []
|
self.auxiliary_data: List[Dict[str, Any]] = []
|
||||||
|
self.user: str = "No User"
|
||||||
self.update_from_ps(data)
|
self.update_from_ps(data)
|
||||||
|
|
||||||
def finish(self,
|
def finish(self,
|
||||||
|
|
|
@ -135,7 +135,9 @@ class JobQueue:
|
||||||
raise self.server.error(
|
raise self.server.error(
|
||||||
"Queue State Changed during Transition Gcode")
|
"Queue State Changed during Transition Gcode")
|
||||||
self._set_queue_state("starting")
|
self._set_queue_state("starting")
|
||||||
await kapis.start_print(filename, wait_klippy_started=True)
|
await kapis.start_print(
|
||||||
|
filename, wait_klippy_started=True, user=job.user
|
||||||
|
)
|
||||||
except self.server.error:
|
except self.server.error:
|
||||||
logging.exception(f"Error Loading print: {filename}")
|
logging.exception(f"Error Loading print: {filename}")
|
||||||
self._set_queue_state("paused")
|
self._set_queue_state("paused")
|
||||||
|
@ -165,7 +167,8 @@ class JobQueue:
|
||||||
async def queue_job(self,
|
async def queue_job(self,
|
||||||
filenames: Union[str, List[str]],
|
filenames: Union[str, List[str]],
|
||||||
check_exists: bool = True,
|
check_exists: bool = True,
|
||||||
reset: bool = False
|
reset: bool = False,
|
||||||
|
user: Optional[Dict[str, Any]] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
async with self.lock:
|
async with self.lock:
|
||||||
# Make sure that the file exists
|
# Make sure that the file exists
|
||||||
|
@ -178,7 +181,7 @@ class JobQueue:
|
||||||
if reset:
|
if reset:
|
||||||
self.queued_jobs.clear()
|
self.queued_jobs.clear()
|
||||||
for fname in filenames:
|
for fname in filenames:
|
||||||
queued_job = QueuedJob(fname)
|
queued_job = QueuedJob(fname, user)
|
||||||
self.queued_jobs[queued_job.job_id] = queued_job
|
self.queued_jobs[queued_job.job_id] = queued_job
|
||||||
self._send_queue_event(action="jobs_added")
|
self._send_queue_event(action="jobs_added")
|
||||||
|
|
||||||
|
@ -224,6 +227,7 @@ class JobQueue:
|
||||||
else:
|
else:
|
||||||
qs = "ready" if self.automatic else "paused"
|
qs = "ready" if self.automatic else "paused"
|
||||||
self._set_queue_state(qs)
|
self._set_queue_state(qs)
|
||||||
|
|
||||||
def _job_map_to_list(self) -> List[Dict[str, Any]]:
|
def _job_map_to_list(self) -> List[Dict[str, Any]]:
|
||||||
cur_time = time.time()
|
cur_time = time.time()
|
||||||
return [job.as_dict(cur_time) for
|
return [job.as_dict(cur_time) for
|
||||||
|
@ -261,7 +265,8 @@ class JobQueue:
|
||||||
files = web_request.get_list('filenames')
|
files = web_request.get_list('filenames')
|
||||||
reset = web_request.get_boolean("reset", False)
|
reset = web_request.get_boolean("reset", False)
|
||||||
# Validate that all files exist before queueing
|
# Validate that all files exist before queueing
|
||||||
await self.queue_job(files, reset=reset)
|
user = web_request.get_current_user()
|
||||||
|
await self.queue_job(files, reset=reset, user=user)
|
||||||
elif req_type == RequestType.DELETE:
|
elif req_type == RequestType.DELETE:
|
||||||
if web_request.get_boolean("all", False):
|
if web_request.get_boolean("all", False):
|
||||||
await self.delete_job([], all=True)
|
await self.delete_job([], all=True)
|
||||||
|
@ -319,14 +324,19 @@ class JobQueue:
|
||||||
await self.pause_queue()
|
await self.pause_queue()
|
||||||
|
|
||||||
class QueuedJob:
|
class QueuedJob:
|
||||||
def __init__(self, filename: str) -> None:
|
def __init__(self, filename: str, user: Optional[Dict[str, Any]] = None) -> None:
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.job_id = f"{id(self):016X}"
|
self.job_id = f"{id(self):016X}"
|
||||||
self.time_added = time.time()
|
self.time_added = time.time()
|
||||||
|
self._user = user
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.filename
|
return self.filename
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user(self) -> Optional[Dict[str, Any]]:
|
||||||
|
return self._user
|
||||||
|
|
||||||
def as_dict(self, cur_time: float) -> Dict[str, Any]:
|
def as_dict(self, cur_time: float) -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
'filename': self.filename,
|
'filename': self.filename,
|
||||||
|
|
|
@ -89,7 +89,8 @@ class KlippyAPI(APITransport):
|
||||||
|
|
||||||
async def _gcode_start_print(self, web_request: WebRequest) -> str:
|
async def _gcode_start_print(self, web_request: WebRequest) -> str:
|
||||||
filename: str = web_request.get_str('filename')
|
filename: str = web_request.get_str('filename')
|
||||||
return await self.start_print(filename)
|
user = web_request.get_current_user()
|
||||||
|
return await self.start_print(filename, user=user)
|
||||||
|
|
||||||
async def _gcode_restart(self, web_request: WebRequest) -> str:
|
async def _gcode_restart(self, web_request: WebRequest) -> str:
|
||||||
return await self.do_restart("RESTART")
|
return await self.do_restart("RESTART")
|
||||||
|
@ -123,7 +124,10 @@ class KlippyAPI(APITransport):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def start_print(
|
async def start_print(
|
||||||
self, filename: str, wait_klippy_started: bool = False
|
self,
|
||||||
|
filename: str,
|
||||||
|
wait_klippy_started: bool = False,
|
||||||
|
user: Optional[Dict[str, Any]] = None
|
||||||
) -> str:
|
) -> str:
|
||||||
# WARNING: Do not call this method from within the following
|
# WARNING: Do not call this method from within the following
|
||||||
# event handlers when "wait_klippy_started" is set to True:
|
# event handlers when "wait_klippy_started" is set to True:
|
||||||
|
@ -139,7 +143,9 @@ class KlippyAPI(APITransport):
|
||||||
if wait_klippy_started:
|
if wait_klippy_started:
|
||||||
await self.klippy.wait_started()
|
await self.klippy.wait_started()
|
||||||
logging.info(f"Requesting Job Start, filename = {filename}")
|
logging.info(f"Requesting Job Start, filename = {filename}")
|
||||||
return await self.run_gcode(script)
|
ret = await self.run_gcode(script)
|
||||||
|
self.server.send_event("klippy_apis:job_start_complete", user)
|
||||||
|
return ret
|
||||||
|
|
||||||
async def pause_print(
|
async def pause_print(
|
||||||
self, default: Union[Sentinel, _T] = Sentinel.MISSING
|
self, default: Union[Sentinel, _T] = Sentinel.MISSING
|
||||||
|
|
|
@ -388,9 +388,10 @@ class OctoPrintCompat:
|
||||||
except self.server.error:
|
except self.server.error:
|
||||||
pstate = "not_avail"
|
pstate = "not_avail"
|
||||||
started: bool = False
|
started: bool = False
|
||||||
|
user = web_request.get_current_user()
|
||||||
if pstate not in ["printing", "paused", "not_avail"]:
|
if pstate not in ["printing", "paused", "not_avail"]:
|
||||||
try:
|
try:
|
||||||
await self.klippy_apis.start_print(filename)
|
await self.klippy_apis.start_print(filename, user=user)
|
||||||
except self.server.error:
|
except self.server.error:
|
||||||
started = False
|
started = False
|
||||||
else:
|
else:
|
||||||
|
@ -400,7 +401,7 @@ class OctoPrintCompat:
|
||||||
if fmgr.upload_queue_enabled():
|
if fmgr.upload_queue_enabled():
|
||||||
job_queue: JobQueue = self.server.lookup_component(
|
job_queue: JobQueue = self.server.lookup_component(
|
||||||
'job_queue')
|
'job_queue')
|
||||||
await job_queue.queue_job(filename, check_exists=False)
|
await job_queue.queue_job(filename, check_exists=False, user=user)
|
||||||
logging.debug(f"Job '{filename}' queued via OctoPrint API")
|
logging.debug(f"Job '{filename}' queued via OctoPrint API")
|
||||||
else:
|
else:
|
||||||
raise self.server.error("Conflict", 409)
|
raise self.server.error("Conflict", 409)
|
||||||
|
|
|
@ -1598,7 +1598,7 @@ class PrintHandler:
|
||||||
kapi: KlippyAPI = self.server.lookup_component("klippy_apis")
|
kapi: KlippyAPI = self.server.lookup_component("klippy_apis")
|
||||||
data = {"state": "started"}
|
data = {"state": "started"}
|
||||||
try:
|
try:
|
||||||
await kapi.start_print(pending)
|
await kapi.start_print(pending, user={"username": "SimplyPrint"})
|
||||||
except Exception:
|
except Exception:
|
||||||
logging.exception("Print Failed to start")
|
logging.exception("Print Failed to start")
|
||||||
data["state"] = "error"
|
data["state"] = "error"
|
||||||
|
|
Loading…
Reference in New Issue