diff --git a/frappe/core/page/background_jobs/background_jobs.css b/frappe/core/page/background_jobs/background_jobs.css
index 0c77522cb3..7716519113 100644
--- a/frappe/core/page/background_jobs/background_jobs.css
+++ b/frappe/core/page/background_jobs/background_jobs.css
@@ -1,43 +1,32 @@
-.list-jobs {
- font-size: var(--text-base);
-}
-.table {
+.table-background-jobs {
margin-bottom: 0px;
margin-top: 0px;
+ font-size: var(--text-md);
+ table-layout: fixed;
}
-thead {
- background-color: var(--control-bg);
- border-radius: var(--border-radius-sm);
+.table-background-jobs th {
+ font-weight: normal;
+ color: var(--text-muted);
}
-thead > tr {
- border-radius: var(--border-radius-sm);
+.table-background-jobs td {
+ color: var(--text-light);
}
-thead > tr > th:first-child {
- border-radius: var(--border-radius-sm) 0 0 var(--border-radius-sm);
-}
-thead > tr > th:last-child {
- border-radius: 0 var(--border-radius-sm) var(--border-radius-sm) 0;
+.table-background-jobs th, .table-background-jobs td {
+ padding: var(--padding-sm) var(--padding-md);
}
-.worker-name {
- display: flex;
- align-items: center;
+.table-background-jobs tbody tr:hover {
+ background-color: var(--highlight-color);
}
.job-name {
font-size: var(--text-md);
- font-family: "Courier New", Courier, monospace;
- /* background-color: var(--control-bg); */
- /* padding: var(--padding-xs) var(--padding-sm); */
- /* border-radius: var(--border-radius-md); */
-}
-
-.background-job-row:hover {
- background-color: var(--bg-color);
+ font-family: var(--font-family-monospace);
+ word-break: break-word;
}
.no-background-jobs {
@@ -54,7 +43,5 @@ thead > tr > th:last-child {
}
.footer {
- align-items: flex-end;
- margin-top: var(--margin-md);
- font-size: var(--text-base);
+ padding: var(--padding-md);
}
diff --git a/frappe/core/page/background_jobs/background_jobs.html b/frappe/core/page/background_jobs/background_jobs.html
index 1b00ec3106..e0c1a8f633 100644
--- a/frappe/core/page/background_jobs/background_jobs.html
+++ b/frappe/core/page/background_jobs/background_jobs.html
@@ -1,51 +1,58 @@
-
- {% if jobs.length %}
-
-
-
- {{ __("Queue / Worker") }} |
- {{ __("Job") }} |
- {{ __("Created") }} |
-
-
-
- {% for j in jobs %}
-
-
-
- {{ j.queue.split(".").slice(-1)[0] }}
- |
-
-
-
- {{ frappe.utils.encode_tags(j.job_name) }}
-
-
- {% if j.exc_info %}
+{% if jobs.length %}
+
+
+
+ {{ __("Queue") }} |
+ {{ __("Job") }} |
+ {{ __("Status") }} |
+ {{ __("Created") }} |
+
+
+
+ {% for j in jobs %}
+
+
+ {{ toTitle(j.queue.split(":").slice(-1)[0]) }}
+ |
+
+
+
+ {{ frappe.utils.encode_tags(j.job_name) }}
+
+
+ {% if j.exc_info %}
+
+ {{ __("Exception") }}
{{ frappe.utils.encode_tags(j.exc_info) }}
- {% endif %}
- |
- {{ j.creation }} |
-
- {% endfor %}
-
-
- {% else %}
-
- 
- {{ __("No pending or current jobs for this site") }}
-
- {% endif %}
- |
+
+
+ {{ toTitle(j.status) }}
+
+ |
+
+ {{ frappe.datetime.prettyDate(j.creation) }}
+ |
+
+ {% endfor %}
+
+
+{% else %}
+
+

+
{{ __("No jobs found on this site") }}
+
+{% endif %}
+
\ No newline at end of file
+
diff --git a/frappe/core/page/background_jobs/background_jobs.js b/frappe/core/page/background_jobs/background_jobs.js
index 0b4d6792dc..7334bfd5dd 100644
--- a/frappe/core/page/background_jobs/background_jobs.js
+++ b/frappe/core/page/background_jobs/background_jobs.js
@@ -1,7 +1,7 @@
-frappe.pages["background_jobs"].on_page_load = (wrapper) => {
+frappe.pages["background_jobs"].on_page_load = wrapper => {
const background_job = new BackgroundJobs(wrapper);
- $(wrapper).bind('show', () => {
+ $(wrapper).bind("show", () => {
background_job.show();
});
@@ -12,61 +12,135 @@ class BackgroundJobs {
constructor(wrapper) {
this.page = frappe.ui.make_app_page({
parent: wrapper,
- title: __('Background Jobs'),
+ title: __("Background Jobs"),
single_column: true
});
- this.called = false;
- this.show_failed = false;
+ this.page.add_inner_button(__("Remove Failed Jobs"), () => {
+ frappe.confirm(
+ __("Are you sure you want to remove all failed jobs?"),
+ () => {
+ frappe
+ .call(
+ "frappe.core.page.background_jobs.background_jobs.remove_failed_jobs"
+ )
+ .then(() => this.refresh_jobs());
+ }
+ );
+ });
- this.show_failed_button = this.page.add_inner_button(__("Show Failed Jobs"), () => {
- this.show_failed = !this.show_failed;
- if (this.show_failed_button) {
- this.show_failed_button.text(
- this.show_failed ? __("Hide Failed Jobs") : __("Show Failed Jobs")
- );
+ this.page.main.addClass("frappe-card");
+ this.page.body.append('');
+ this.$content = $(this.page.body).find(".table-area");
+
+ this.make_filters();
+ this.refresh_jobs = frappe.utils.throttle(
+ this.refresh_jobs.bind(this),
+ 1000
+ );
+ }
+
+ make_filters() {
+ this.view = this.page.add_field({
+ label: __("View"),
+ fieldname: "view",
+ fieldtype: "Select",
+ options: ["Jobs", "Workers"],
+ default: "Jobs",
+ change: () => {
+ this.queue_timeout.toggle(this.view.get_value() === "Jobs");
+ this.job_status.toggle(this.view.get_value() === "Jobs");
}
});
-
- // add a "Remove Failed Jobs button"
- this.remove_failed_button = this.page.add_inner_button(__("Remove Failed Jobs"), () => {
- frappe.call({
- method: 'frappe.core.page.background_jobs.background_jobs.remove_failed_jobs',
- callback: () => {
+ this.queue_timeout = this.page.add_field({
+ label: __("Queue"),
+ fieldname: "queue_timeout",
+ fieldtype: "Select",
+ options: [
+ { label: "All Queues", value: "all" },
+ { label: "Default", value: "default" },
+ { label: "Short", value: "short" },
+ { label: "Long", value: "long" }
+ ],
+ default: "all"
+ });
+ this.job_status = this.page.add_field({
+ label: __("Job Status"),
+ fieldname: "job_status",
+ fieldtype: "Select",
+ options: [
+ { label: "All Jobs", value: "all" },
+ { label: "Queued", value: "queued" },
+ { label: "Deferred", value: "deferred" },
+ { label: "Started", value: "started" },
+ { label: "Finished", value: "finished" },
+ { label: "Failed", value: "failed" }
+ ],
+ default: "all"
+ });
+ this.auto_refresh = this.page.add_field({
+ label: __("Auto Refresh"),
+ fieldname: "auto_refresh",
+ fieldtype: "Check",
+ default: 1,
+ change: () => {
+ if (this.auto_refresh.get_value()) {
this.refresh_jobs();
}
- });
+ }
});
-
- $(frappe.render_template('background_jobs_outer')).appendTo(this.page.body);
- this.content = $(this.page.body).find('.table-area');
}
show() {
this.refresh_jobs();
+ this.update_scheduler_status();
+ }
+
+ update_scheduler_status() {
frappe.call({
- method: 'frappe.core.page.background_jobs.background_jobs.get_scheduler_status',
- callback: res => {
- this.page.set_indicator(...res.message);
+ method:
+ "frappe.core.page.background_jobs.background_jobs.get_scheduler_status",
+ callback: r => {
+ let { status } = r.message;
+ if (status === "active") {
+ this.page.set_indicator(__("Scheduler: Active"), "green");
+ } else {
+ this.page.set_indicator(__("Scheduler: Inactive"), "red");
+ }
}
});
}
refresh_jobs() {
- if (this.called) return;
- this.called = true;
+ let view = this.view.get_value();
+ let args;
+ let { queue_timeout, job_status } = this.page.get_form_values();
+ if (view === "Jobs") {
+ args = { view, queue_timeout, job_status };
+ } else {
+ args = { view };
+ }
+ this.page.add_inner_message(__("Refreshing..."));
frappe.call({
- method: 'frappe.core.page.background_jobs.background_jobs.get_info',
- args: {
- show_failed: this.show_failed
- },
- callback: (res) => {
- this.called = false;
- this.page.body.find('.list-jobs').remove();
- $(frappe.render_template('background_jobs', { jobs: res.message || [] })).appendTo(this.content);
+ method: "frappe.core.page.background_jobs.background_jobs.get_info",
+ args,
+ callback: res => {
+ this.page.add_inner_message("");
+
+ let template =
+ view === "Jobs" ? "background_jobs" : "background_workers";
+ this.$content.html(
+ frappe.render_template(template, {
+ jobs: res.message || []
+ })
+ );
- if (frappe.get_route()[0] === 'background_jobs') {
+ let auto_refresh = this.auto_refresh.get_value();
+ if (
+ frappe.get_route()[0] === "background_jobs" &&
+ auto_refresh
+ ) {
setTimeout(() => this.refresh_jobs(), 2000);
}
}
diff --git a/frappe/core/page/background_jobs/background_jobs.py b/frappe/core/page/background_jobs/background_jobs.py
index 4d9deca526..960444c349 100644
--- a/frappe/core/page/background_jobs/background_jobs.py
+++ b/frappe/core/page/background_jobs/background_jobs.py
@@ -8,8 +8,8 @@ from rq import Worker
import frappe
from frappe import _
-from frappe.utils import convert_utc_to_user_timezone, format_datetime
-from frappe.utils.background_jobs import get_redis_conn, get_queues
+from frappe.utils import convert_utc_to_user_timezone
+from frappe.utils.background_jobs import get_queues, get_workers
from frappe.utils.scheduler import is_scheduler_inactive
if TYPE_CHECKING:
@@ -24,16 +24,15 @@ JOB_COLORS = {
@frappe.whitelist()
-def get_info(show_failed=False) -> List[Dict]:
- if isinstance(show_failed, str):
- show_failed = json.loads(show_failed)
-
- conn = get_redis_conn()
- queues = get_queues()
- workers = Worker.all(conn)
+def get_info(view=None, queue_timeout=None, job_status=None) -> List[Dict]:
jobs = []
def add_job(job: 'Job', name: str) -> None:
+ if job_status != "all" and job.get_status() != job_status:
+ return
+ if queue_timeout != "all" and not name.endswith(f':{queue_timeout}'):
+ return
+
if job.kwargs.get('site') == frappe.local.site:
job_info = {
'job_name': job.kwargs.get('kwargs', {}).get('playbook_method')
@@ -41,7 +40,7 @@ def get_info(show_failed=False) -> List[Dict]:
or str(job.kwargs.get('job_name')),
'status': job.get_status(),
'queue': name,
- 'creation': format_datetime(convert_utc_to_user_timezone(job.created_at)),
+ 'creation': convert_utc_to_user_timezone(job.created_at),
'color': JOB_COLORS[job.get_status()]
}
@@ -50,32 +49,31 @@ def get_info(show_failed=False) -> List[Dict]:
jobs.append(job_info)
- # show worker jobs
- for worker in workers:
- job = worker.get_current_job()
- if job:
- add_job(job, worker.name)
-
- for queue in queues:
- # show active queued jobs
- if queue.name != 'failed':
+ if view == 'Jobs':
+ queues = get_queues()
+ for queue in queues:
for job in queue.jobs:
add_job(job, queue.name)
- # show failed jobs, if requested
- if show_failed:
- fail_registry = queue.failed_job_registry
- for job_id in fail_registry.get_job_ids():
- job = queue.fetch_job(job_id)
- if job:
- add_job(job, queue.name)
+ elif view == 'Workers':
+ workers = get_workers()
+ for worker in workers:
+ current_job = worker.get_current_job()
+ if current_job and current_job.kwargs.get('site') == frappe.local.site:
+ add_job(current_job, job.origin)
+ else:
+ jobs.append({
+ 'queue': worker.name,
+ 'job_name': 'idle',
+ 'status': '',
+ 'creation': ''
+ })
return jobs
@frappe.whitelist()
def remove_failed_jobs():
- conn = get_redis_conn()
queues = get_queues()
for queue in queues:
fail_registry = queue.failed_job_registry
@@ -87,5 +85,5 @@ def remove_failed_jobs():
@frappe.whitelist()
def get_scheduler_status():
if is_scheduler_inactive():
- return [_("Inactive"), "red"]
- return [_("Active"), "green"]
+ return {'status': 'inactive'}
+ return {'status': 'active'}
diff --git a/frappe/core/page/background_jobs/background_jobs_outer.html b/frappe/core/page/background_jobs/background_jobs_outer.html
deleted file mode 100644
index 4ca3a32906..0000000000
--- a/frappe/core/page/background_jobs/background_jobs_outer.html
+++ /dev/null
@@ -1,5 +0,0 @@
-
\ No newline at end of file
diff --git a/frappe/core/page/background_jobs/background_workers.html b/frappe/core/page/background_jobs/background_workers.html
new file mode 100644
index 0000000000..1647cea4b4
--- /dev/null
+++ b/frappe/core/page/background_jobs/background_workers.html
@@ -0,0 +1,51 @@
+{% if jobs.length %}
+
+
+
+ {{ __("Worker") }} |
+ {{ __("Current Job") }} |
+ {{ __("Status") }} |
+ {{ __("Created") }} |
+
+
+
+ {% for j in jobs %}
+
+
+ {{ j.queue }}
+ |
+
+
+
+ {{ frappe.utils.encode_tags(j.job_name) }}
+
+
+ {% if j.exc_info %}
+
+ {{ __("Exception") }}
+
+ {{ frappe.utils.encode_tags(j.exc_info) }}
+
+
+ {% endif %}
+ |
+
+ {{ toTitle(j.status) }}
+ |
+ {{ frappe.datetime.prettyDate(j.creation) }} |
+
+ {% endfor %}
+
+
+{% else %}
+
+

+
{{ __("No workers online on this site") }}
+
+{% endif %}
+
\ No newline at end of file
diff --git a/frappe/public/js/frappe/ui/page.js b/frappe/public/js/frappe/ui/page.js
index 91a2390cdb..34189ed025 100644
--- a/frappe/public/js/frappe/ui/page.js
+++ b/frappe/public/js/frappe/ui/page.js
@@ -846,9 +846,10 @@ frappe.ui.Page = class Page {
}
get_form_values() {
var values = {};
- this.page_form.fields_dict.forEach(function(field, key) {
- values[key] = field.get_value();
- });
+ for (let fieldname in this.fields_dict) {
+ let field = this.fields_dict[fieldname];
+ values[fieldname] = field.get_value();
+ }
return values;
}
add_view(name, html) {
diff --git a/frappe/public/scss/desk/page.scss b/frappe/public/scss/desk/page.scss
index f0a9152cfb..00b537b919 100644
--- a/frappe/public/scss/desk/page.scss
+++ b/frappe/public/scss/desk/page.scss
@@ -53,6 +53,7 @@
.custom-actions {
display: flex;
+ align-items: center;
}
.page-actions {
diff --git a/frappe/utils/background_jobs.py b/frappe/utils/background_jobs.py
index 58029dbc5f..ac58577c2e 100755
--- a/frappe/utils/background_jobs.py
+++ b/frappe/utils/background_jobs.py
@@ -220,9 +220,12 @@ def get_queue_list(queue_list=None, build_queue_name=False):
queue_list = default_queue_list
return [generate_qname(qtype) for qtype in queue_list] if build_queue_name else queue_list
-def get_workers(queue):
- '''Returns a list of Worker objects tied to a queue object'''
- return Worker.all(queue=queue)
+def get_workers(queue=None):
+ '''Returns a list of Worker objects tied to a queue object if queue is passed, else returns a list of all workers'''
+ if queue:
+ return Worker.all(queue=queue)
+ else:
+ return Worker.all(get_redis_conn())
def get_running_jobs_in_queue(queue):
'''Returns a list of Jobs objects that are tied to a queue object and are currently running'''