Skip to content

Commit

Permalink
refactoring job-manage view => ajax
Browse files Browse the repository at this point in the history
  • Loading branch information
ansibleguy committed Feb 10, 2024
1 parent bf22d75 commit 7adfc6d
Show file tree
Hide file tree
Showing 18 changed files with 259 additions and 244 deletions.
11 changes: 11 additions & 0 deletions src/ansible-webui/aw/api_endpoints/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django.core.exceptions import ObjectDoesNotExist
from django.contrib.auth.models import User, Group
from rest_framework import serializers
from rest_framework.permissions import IsAuthenticated
from rest_framework_api_key.permissions import BaseHasAPIKey
Expand Down Expand Up @@ -40,3 +41,13 @@ def update(self, instance, validated_data):

class GenericResponse(BaseResponse):
msg = serializers.CharField()


class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group


class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
7 changes: 0 additions & 7 deletions src/ansible-webui/aw/api_endpoints/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,6 @@ class APIJobExecution(APIView):
request=None,
responses={
200: OpenApiResponse(JobExecutionReadResponse, description='Return job-execution information'),
404: OpenApiResponse(JobExecutionReadResponse, description='No viewable jobs or executions found'),
},
summary='Return list of job-executions the current user is privileged to view.',
operation_id='job_exec_list',
Expand All @@ -461,9 +460,6 @@ class APIJobExecution(APIView):
def get(self, request):
# pylint: disable=E1101
jobs = get_viewable_jobs(get_api_user(request))
if len(jobs) == 0:
return Response(data={'msg': 'No viewable jobs found'}, status=404)

exec_count = _job_execution_count(request)
if exec_count is None:
exec_count = JOB_EXECUTION_LIMIT
Expand All @@ -472,7 +468,4 @@ def get(self, request):
for execution in JobExecution.objects.filter(job__in=jobs).order_by('updated')[:exec_count]:
serialized.append(get_job_execution_serialized(execution))

if len(serialized) == 0:
return Response(data={'msg': 'No viewable job-executions found'}, status=404)

return Response(data=serialized, status=200)
13 changes: 11 additions & 2 deletions src/ansible-webui/aw/api_endpoints/job_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
from aw.config.hardcoded import SHORT_TIME_FORMAT, JOB_EXECUTION_LIMIT
from aw.model.job import Job, CHOICES_JOB_EXEC_STATUS, JobExecution
from aw.utils.permission import get_viewable_jobs
from aw.utils.util import datetime_from_db
from aw.utils.util import datetime_from_db, get_next_cron_execution_str


class JobReadResponse(serializers.ModelSerializer):
class Meta:
model = Job
fields = Job.api_fields_read

next_run = serializers.CharField(required=False)


class JobExecutionReadResponse(serializers.ModelSerializer):
class Meta:
Expand Down Expand Up @@ -40,7 +42,7 @@ def get_job_execution_serialized(execution: JobExecution) -> dict:
'job': execution.job.id,
'job_name': execution.job.name,
'job_comment': execution.job.comment,
'user': execution.user.id,
'user': execution.user.id if execution.user is not None else None,
'user_name': execution.user.username if execution.user is not None else 'Scheduled',
'status': execution.status,
'status_name': CHOICES_JOB_EXEC_STATUS[execution.status][1],
Expand Down Expand Up @@ -81,6 +83,13 @@ def get_viewable_jobs_serialized(

for job in get_viewable_jobs(user):
job_serialized = JobReadResponse(instance=job).data

try:
job_serialized['next_run'] = get_next_cron_execution_str(job.schedule)

except ValueError:
job_serialized['next_run'] = None

if executions:
job_serialized['executions'] = get_job_executions_serialized(job=job, execution_count=execution_count)

Expand Down
26 changes: 14 additions & 12 deletions src/ansible-webui/aw/api_endpoints/permission.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,25 @@ class Meta:
jobs_name = serializers.ListSerializer(child=serializers.CharField(), required=False)


class JobSerializer(serializers.ModelSerializer):
class Meta:
model = Job


class PermissionWriteRequest(serializers.ModelSerializer):
class Meta:
model = JobPermission
fields = JobPermission.api_fields_write

jobs = serializers.MultipleChoiceField(
choices=[job.id for job in Job.objects.all()],
allow_blank=True,
)
users = serializers.MultipleChoiceField(
choices=[user.id for user in User.objects.all()],
allow_blank=True,
)
groups = serializers.MultipleChoiceField(
choices=[group.id for group in Group.objects.all()],
allow_blank=True,
)
jobs = serializers.MultipleChoiceField(allow_blank=True, choices=[])
users = serializers.MultipleChoiceField(allow_blank=True, choices=[])
groups = serializers.MultipleChoiceField(allow_blank=True, choices=[])

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['jobs'] = serializers.MultipleChoiceField(choices=[job.id for job in Job.objects.all()])
self.fields['users'] = serializers.MultipleChoiceField(choices=[user.id for user in User.objects.all()])
self.fields['groups'] = serializers.MultipleChoiceField(choices=[group.id for group in Group.objects.all()])

@staticmethod
def create_or_update(validated_data: dict, perm: JobPermission = None):
Expand Down
3 changes: 2 additions & 1 deletion src/ansible-webui/aw/model/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ class Job(BaseJob):
api_fields_read = ['id']
api_fields_read.extend(CHANGE_FIELDS)
api_fields_write = api_fields_read.copy()
api_fields_read.append('next_run')
api_fields_write.extend(['vault_pass', 'become_pass', 'connect_pass'])

name = models.CharField(max_length=150)
Expand Down Expand Up @@ -330,7 +331,7 @@ def __str__(self) -> str:
class JobExecution(BaseJob):
api_fields_read = [
'id', 'job', 'job_name', 'user', 'user_name', 'result', 'status', 'status_name', 'time_start', 'time_fin',
'failed', 'error_s', 'error_m', 'log_stdout', 'log_stdout_url', 'log_stderr', 'log_stderr_url',
'failed', 'error_s', 'error_m', 'log_stdout', 'log_stdout_url', 'log_stderr', 'log_stderr_url', 'job_comment',
]

# NOTE: scheduled execution will have no user
Expand Down
35 changes: 32 additions & 3 deletions src/ansible-webui/aw/static/js/aw.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ function toggleHidden(elementID) {
}
}

function shortExecutionStatus(execution) {
return execution.time_start + '<br>' + execution.user_name +
'<br><div class="aw-job-status aw-job-status-' + execution.status_name.toLowerCase() + '">' +
execution.status_name + '</div>';
}

// API CALLS
const CSRF_TOKEN = getCookie('csrftoken');

Expand Down Expand Up @@ -174,10 +180,21 @@ function apiBrowseDir(inputElement, choicesElement, selector, base, searchType)
});
}

function fetchApiTableData(apiEndpoint, updateFunction, secondRow = false) {
function fetchApiTableDataPlaceholder(dataTable, placeholderId) {
tableHead = dataTable.rows[0];
tmpRow = dataTable.insertRow(1);
tmpRow.setAttribute("aw-api-entry", placeholderId);
for (i = 0, len = tableHead.cells.length; i < len; i++) {
tmpRow.insertCell(i).innerText = '-';
}
}

function fetchApiTableData(apiEndpoint, updateFunction, secondRow = false, placeholderFunction = null) {
// NOTE: data needs to be list of dict and include an 'id' attribute
dataTable = document.getElementById("aw-api-data-table");
secondRowAppendix = '_2';
placeholderExists = false;
placeholderId = 'placeholder';

$.get(apiEndpoint, function(data) {
existingEntryIds = [];
Expand Down Expand Up @@ -242,7 +259,12 @@ function fetchApiTableData(apiEndpoint, updateFunction, secondRow = false) {
if (typeof(existingRowId) == 'undefined' || existingRowId == null) {
continue
}
if (!existingEntryIds.includes(String(existingRowId))) {
if (existingRowId == placeholderId) {
placeholderExists = true;
if (data.length > 0) {
rowsToDelete.push(rowIdx);
}
} else if (!existingEntryIds.includes(String(existingRowId))) {
rowsToDelete.push(rowIdx);
}
}
Expand All @@ -251,6 +273,14 @@ function fetchApiTableData(apiEndpoint, updateFunction, secondRow = false) {
console.log("Removing entry row", rowIdx);
dataTable.deleteRow(rowIdx);
}
// add placeholder row if empty
if (data.length == 0 && !placeholderExists) {
if (placeholderFunction == null) {
fetchApiTableDataPlaceholder(dataTable, placeholderId);
} else {
placeholderFunction(dataTable, placeholderId);
}
}
});
}

Expand Down Expand Up @@ -331,7 +361,6 @@ $( document ).ready(function() {
}

if (this.checkValidity() == false) {

let userInput = $(this).val();
if (typeof(userInput) == 'undefined' || userInput == null) {
userInput = "";
Expand Down
5 changes: 1 addition & 4 deletions src/ansible-webui/aw/static/js/jobs/logs.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,7 @@ function addLogLines($this) {
}

function updateApiTableDataJobLogs(row, row2, entry) {
console.log(entry);
row.insertCell(0).innerHTML = entry.time_start + '<br>' + entry.user_name +
'<br><div class="aw-job-status aw-job-status-' + entry.status_name.toLowerCase() + '">' +
entry.status_name + '</div>';
row.insertCell(0).innerHTML = shortExecutionStatus(entry);
row.insertCell(1).innerText = entry.job_name;
if (entry.job_comment == "") {
row.insertCell(2).innerText = "-";
Expand Down
110 changes: 110 additions & 0 deletions src/ansible-webui/aw/static/js/jobs/manage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
function updateApiTableDataJob(row, row2, entry) {
// job
row.insertCell(0).innerText = entry.name;
c2 = row.insertCell(1);
c2.setAttribute("class", "aw-responsive-lg");
c2.innerText = entry.inventory_file;
c3 = row.insertCell(2);
c3.setAttribute("class", "aw-responsive-lg");
c3.innerText = entry.playbook_file;

c4 = row.insertCell(3);
c4.setAttribute("class", "aw-responsive-lg");
if (entry.comment == "") {
c4.innerText = '-';
} else {
c4.innerText = entry.comment;
}
if (entry.schedule == "") {
row.insertCell(4).innerText = '-';
} else {
scheduleHtml = entry.schedule;
if (!entry.enabled) {
scheduleHtml += '<br><i>(disabled)</i>';
}
row.insertCell(4).innerHTML = scheduleHtml;
}

if (entry.executions.length == 0) {
lastExecution = null;
row.insertCell(5).innerText = '-';

c7 = row.insertCell(6);
c7.setAttribute("class", "aw-responsive-med");
c7.innerHTML = '-';
} else {
lastExecution = entry.executions[0];
c6 = row.insertCell(5);
c6.innerHTML = shortExecutionStatus(lastExecution);

c7 = row.insertCell(6);
c7.setAttribute("class", "aw-responsive-med");
if (entry.next_run == null) {
c7.innerText = '-';
} else {
c7.innerText = entry.next_run;
}
}

actionsTemplate = document.getElementById("aw-api-data-tmpl-actions").innerHTML;
actionsTemplate = actionsTemplate.replaceAll('${ID}', entry.id);
if (lastExecution != null) {
actionsTemplate = actionsTemplate.replaceAll('${EXEC_ID_1}', lastExecution.id);
}
row.insertCell(7).innerHTML = actionsTemplate;

// execution stati
executionsTemplate = document.getElementById("aw-api-data-tmpl-executions").innerHTML;
executionsTemplate = executionsTemplate.replaceAll('${ID}', entry.id);
row2.setAttribute("hidden", "hidden");
row2.setAttribute("id", "aw-spoiler-" + entry.id);
row2Col = row2.insertCell(0);
row2Col.setAttribute("colspan", "100%");
execs = '<div>';
for (i = 0, len = entry.executions.length; i < len; i++) {
exec = entry.executions[i];
execs += ('<hr><b>Start time</b>: ' + exec.time_start)
execs += ('<br><b>Finish time</b>: ' + exec.time_fin)
execs += ('<br><b>Executed by</b>: ' + exec.user_name)
execs += ('<br><b>Status</b>: <span class="aw-job-status aw-job-status-' + exec.status_name.toLowerCase() + '">' + exec.status_name + '</span>')
execs += ('<br><b>Logs</b>: <a href="' + exec.log_stdout_url + '" title="' + exec.log_stdout + '" download>Output</a>, ')
execs += ('<a href="' + exec.log_stderr_url + '" title="' + exec.log_stderr + '" download>Error</a>')
if (exec.error_s != null) {
execs += ('<br><br><b>Error</b>: <code>' + exec.error_s + '</code>')
if (exec.error_m != null) {
execs += ('<br><b>Error full</b>:<div class="code">' + exec.error_m + '</div>')
}
}
}
execs += '</div>';
executionsTemplate = executionsTemplate.replaceAll('${EXECS}', execs);
row2Col.innerHTML = executionsTemplate;
}

function updateApiTableDataJobPlaceholder(dataTable, placeholderId) {
tableHead = dataTable.rows[0];
tmpRow = dataTable.insertRow(1);
tmpRow.setAttribute("aw-api-entry", placeholderId);
tmpRow.insertCell(0).innerText = '-';
c2 = tmpRow.insertCell(1);
c2.innerText = '-';
c2.setAttribute("class", "aw-responsive-lg");
c3 = tmpRow.insertCell(2);
c3.innerText = '-';
c3.setAttribute("class", "aw-responsive-lg");
c4 = tmpRow.insertCell(3);
c4.innerText = '-';
c4.setAttribute("class", "aw-responsive-lg");
tmpRow.insertCell(4).innerText = '-';
c6 = tmpRow.insertCell(5);
c6.innerText = '-';
c6.setAttribute("class", "aw-responsive-med");
tmpRow.insertCell(6).innerText = '-';
}


$( document ).ready(function() {
apiEndpoint = "/api/job?executions=true";
fetchApiTableData(apiEndpoint, updateApiTableDataJob, true, updateApiTableDataJobPlaceholder);
setInterval('fetchApiTableData(apiEndpoint, updateApiTableDataJob, true, updateApiTableDataJobPlaceholder)', (DATA_REFRESH_SEC * 1000));
});
4 changes: 2 additions & 2 deletions src/ansible-webui/aw/static/js/settings/api_key.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
function updateApiTableDataKey(row, entry) {
entryRow.insertCell(0).innerText = entry.token;
row.insertCell(0).innerText = entry.token;
actionsTemplate = document.getElementById("aw-api-data-tmpl-actions").innerHTML;
entryRow.insertCell(1).innerHTML = actionsTemplate.replaceAll('${TOKEN}', entry.token);
row.insertCell(1).innerHTML = actionsTemplate.replaceAll('${TOKEN}', entry.token);
}

$( document ).ready(function() {
Expand Down
18 changes: 9 additions & 9 deletions src/ansible-webui/aw/static/js/settings/permission.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
function updateApiTableDataPermission(row, entry) {
entryRow.insertCell(0).innerText = entry.name;
entryRow.insertCell(1).innerText = entry.permission_name;
row.insertCell(0).innerText = entry.name;
row.insertCell(1).innerText = entry.permission_name;
if (entry.jobs_name.length == 0) {
entryRow.insertCell(2).innerText = '-';
row.insertCell(2).innerText = '-';
} else {
entryRow.insertCell(2).innerText = entry.jobs_name.join(', ');
row.insertCell(2).innerText = entry.jobs_name.join(', ');
}
if (entry.users_name.length == 0) {
entryRow.insertCell(3).innerText = '-';
row.insertCell(3).innerText = '-';
} else {
entryRow.insertCell(3).innerText = entry.users_name.join(', ');
row.insertCell(3).innerText = entry.users_name.join(', ');
}
if (entry.groups_name.length == 0) {
entryRow.insertCell(4).innerText = '-';
row.insertCell(4).innerText = '-';
} else {
entryRow.insertCell(4).innerText = entry.groups_name.join(', ');
row.insertCell(4).innerText = entry.groups_name.join(', ');
}

actionsTemplate = document.getElementById("aw-api-data-tmpl-actions").innerHTML;
entryRow.insertCell(5).innerHTML = actionsTemplate.replaceAll('${ID}', entry.id);
row.insertCell(5).innerHTML = actionsTemplate.replaceAll('${ID}', entry.id);
}

$( document ).ready(function() {
Expand Down
2 changes: 0 additions & 2 deletions src/ansible-webui/aw/templates/jobs/logs.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
{% extends "../body.html" %}
{% load static %}
{% load util %}
{% load job_util %}
{% block content %}
<script src="{% static 'js/jobs/logs.js' %}"></script>
{# todo: build table using ajax to allow dynamic search #}
<a href="/ui/jobs/manage">
<button class="btn btn-info aw-btn-action" title="Manage">
{% include "../button/icon/return.html" %}
Expand Down
Loading

0 comments on commit 7adfc6d

Please sign in to comment.