aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rwxr-xr-xlib/acme/reports.py2
-rwxr-xr-xlib/acme/tables.py2
-rwxr-xr-xlib/acme/templates/acme_hello.html2
-rwxr-xr-xlib/acme/templates/acme_product.html2
-rwxr-xr-xlib/acme/templates/base.html3
-rwxr-xr-xlib/acme/urls.py2
-rwxr-xr-xlib/cve_checker/__init__.py0
-rwxr-xr-xlib/cve_checker/admin.py3
-rwxr-xr-xlib/cve_checker/apps.py5
-rw-r--r--lib/cve_checker/migrations/0001_initial.py71
-rw-r--r--lib/cve_checker/migrations/0002_ckpackage2cve_ck_audit.py19
-rw-r--r--lib/cve_checker/migrations/0003_alter_ckpackage2cve_ck_package.py19
-rw-r--r--lib/cve_checker/migrations/0004_ck_package_ignored_cnt_ck_package_patched_cnt_and_more.py28
-rw-r--r--lib/cve_checker/migrations/0005_ckuploadmanager.py27
-rw-r--r--lib/cve_checker/migrations/0006_rename_mode_ckuploadmanager_import_mode.py18
-rw-r--r--lib/cve_checker/migrations/0007_ckuploadmanager_select_list_and_more.py23
-rwxr-xr-xlib/cve_checker/migrations/__init__.py0
-rwxr-xr-xlib/cve_checker/models.py165
-rwxr-xr-xlib/cve_checker/reports.py511
-rwxr-xr-xlib/cve_checker/tables.py695
-rwxr-xr-xlib/cve_checker/templates/ck-audit-toastertable.html223
-rwxr-xr-xlib/cve_checker/templates/ck-auditcve-toastertable.html431
-rwxr-xr-xlib/cve_checker/templates/ck-audits-toastertable.html425
-rwxr-xr-xlib/cve_checker/templates/ck-import_manager-toastertable.html266
-rwxr-xr-xlib/cve_checker/templates/ck-issue-toastertable.html347
-rwxr-xr-xlib/cve_checker/templates/ck-product-toastertable.html309
-rwxr-xr-xlib/cve_checker/tests.py3
-rwxr-xr-xlib/cve_checker/urls.py47
-rwxr-xr-xlib/cve_checker/views.py325
-rw-r--r--lib/orm/management/commands/checksettings.py14
-rw-r--r--lib/orm/management/commands/lsupdates.py23
-rwxr-xr-xlib/orm/migrations/0007_components_errorlog.py39
-rw-r--r--lib/orm/migrations/0008_cveaccess.py24
-rw-r--r--lib/orm/migrations/0009_recipetable.py20
-rw-r--r--lib/orm/migrations/0010_job.py35
-rw-r--r--lib/orm/migrations/0011_extend_field_sizes.py33
-rwxr-xr-xlib/orm/migrations/0012_job_user.py21
-rwxr-xr-xlib/orm/migrations/0013_update_preinit.py18
-rw-r--r--lib/orm/migrations/0014_alter_packagetocve_applicable.py18
-rw-r--r--lib/orm/models.py295
-rw-r--r--lib/srtgui/api.py153
-rw-r--r--lib/srtgui/reports.py407
-rw-r--r--lib/srtgui/static/js/libtoaster.js133
-rwxr-xr-xlib/srtgui/static/js/mrjsection.js131
-rw-r--r--lib/srtgui/static/js/table.js107
-rwxr-xr-xlib/srtgui/static/js/typeahead_affected_components.js9
-rw-r--r--lib/srtgui/tables.py410
-rw-r--r--lib/srtgui/templates/base.html21
-rw-r--r--lib/srtgui/templates/basetable_top.html2
-rw-r--r--lib/srtgui/templates/create_vulnerability.html2
-rwxr-xr-xlib/srtgui/templates/cve-edit-local.html2
-rwxr-xr-xlib/srtgui/templates/cve-nist-local.html5
-rwxr-xr-xlib/srtgui/templates/cve-nist.html8
-rw-r--r--lib/srtgui/templates/cve.html188
-rwxr-xr-xlib/srtgui/templates/cve.html_orig2
-rw-r--r--lib/srtgui/templates/cves-select-toastertable.html35
-rw-r--r--lib/srtgui/templates/cves-toastertable.html2
-rwxr-xr-xlib/srtgui/templates/date-time-test.html88
-rw-r--r--lib/srtgui/templates/defect.html2
-rw-r--r--lib/srtgui/templates/detail_sorted_header.html2
-rwxr-xr-xlib/srtgui/templates/email_admin.html70
-rwxr-xr-xlib/srtgui/templates/email_success.html49
-rwxr-xr-xlib/srtgui/templates/errorlog-toastertable.html142
-rw-r--r--lib/srtgui/templates/export.html2
-rw-r--r--lib/srtgui/templates/filtersnippet.html2
-rw-r--r--lib/srtgui/templates/generic-toastertable-page.html2
-rw-r--r--lib/srtgui/templates/guided_tour.html2
-rw-r--r--lib/srtgui/templates/investigation.html53
-rwxr-xr-xlib/srtgui/templates/joblog.html39
-rw-r--r--lib/srtgui/templates/js-unit-tests.html2
-rw-r--r--lib/srtgui/templates/landing.html11
-rw-r--r--lib/srtgui/templates/landing_not_managed.html2
-rw-r--r--lib/srtgui/templates/login.html2
-rwxr-xr-xlib/srtgui/templates/maintenance.html280
-rwxr-xr-xlib/srtgui/templates/manage-jobs-toastertable.html126
-rw-r--r--lib/srtgui/templates/management.html334
-rwxr-xr-xlib/srtgui/templates/mrj_section.html194
-rw-r--r--lib/srtgui/templates/product.html2
-rw-r--r--lib/srtgui/templates/publish.html2
-rw-r--r--lib/srtgui/templates/publish_diff_snapshot.html42
-rw-r--r--lib/srtgui/templates/report.html4
-rw-r--r--lib/srtgui/templates/snippets/gitrev_popover.html2
-rw-r--r--lib/srtgui/templates/snippets/investigations_popover.html2
-rw-r--r--lib/srtgui/templates/snippets/pkg_dependencies_popover.html2
-rw-r--r--lib/srtgui/templates/snippets/pkg_revdependencies_popover.html2
-rw-r--r--lib/srtgui/templates/sources-toastertable.html81
-rw-r--r--lib/srtgui/templates/sources.html2
-rwxr-xr-xlib/srtgui/templates/srtool_metadata_include.html27
-rw-r--r--lib/srtgui/templates/tablesort.html2
-rw-r--r--lib/srtgui/templates/tbd.html2
-rw-r--r--lib/srtgui/templates/toastertable-simple.html2
-rw-r--r--lib/srtgui/templates/toastertable.html29
-rw-r--r--lib/srtgui/templates/triage_cves.html2
-rw-r--r--lib/srtgui/templates/unavailable_artifact.html2
-rw-r--r--lib/srtgui/templates/users.html203
-rw-r--r--lib/srtgui/templates/vulnerability.html134
-rwxr-xr-x[-rw-r--r--]lib/srtgui/templatetags/jobtags.py (renamed from lib/srtgui/templatetags/projecttags.py)11
-rwxr-xr-xlib/srtgui/templatetags/multi_tags.py22
-rw-r--r--lib/srtgui/templatetags/project_url_tag.py34
-rw-r--r--lib/srtgui/typeaheads.py26
-rw-r--r--lib/srtgui/urls.py43
-rw-r--r--lib/srtgui/views.py864
-rw-r--r--lib/srtgui/widgets.py297
-rw-r--r--lib/srtmain/management/commands/checksocket.py4
-rw-r--r--lib/srtmain/settings.py48
-rw-r--r--lib/srtmain/urls.py32
-rw-r--r--lib/srtmain/wsgi.py17
-rw-r--r--lib/users/migrations/0002_last_name.py18
-rw-r--r--lib/users/migrations/0003_srtuser_timezone.py18
-rwxr-xr-xlib/users/migrations/0004_timezone_default.py18
-rw-r--r--lib/users/migrations/0005_alter_srtuser_first_name.py18
-rwxr-xr-xlib/users/models.py76
-rwxr-xr-xlib/users/templates/user_edit.html16
-rwxr-xr-xlib/users/urls.py4
-rwxr-xr-xlib/users/views.py71
-rwxr-xr-xlib/yp/reports.py4
-rwxr-xr-xlib/yp/templates/landing.html93
-rwxr-xr-xlib/yp/templates/management.html199
-rwxr-xr-xlib/yp/templates/yp_hello.html2
-rwxr-xr-xlib/yp/urls.py10
-rwxr-xr-xlib/yp/views.py30
121 files changed, 9259 insertions, 794 deletions
diff --git a/lib/acme/reports.py b/lib/acme/reports.py
index 682852ad..92c5693e 100755
--- a/lib/acme/reports.py
+++ b/lib/acme/reports.py
@@ -34,7 +34,7 @@ from srtgui.reports import Report, ReportManager, ProductsReport
from django.db.models import Q, F
from django.db import Error
-from srtgui.templatetags.projecttags import filtered_filesizeformat
+from srtgui.templatetags.jobtags import filtered_filesizeformat
logger = logging.getLogger("srt")
diff --git a/lib/acme/tables.py b/lib/acme/tables.py
index ee14136b..0e39dc78 100755
--- a/lib/acme/tables.py
+++ b/lib/acme/tables.py
@@ -29,7 +29,7 @@ from orm.models import Notify, NotifyAccess, NotifyCategories
from users.models import SrtUser, UserSafe
from django.db.models import Q, Max, Sum, Count, When, Case, Value, IntegerField
-from django.conf.urls import url
+from django.urls import re_path as url
from django.urls import reverse, resolve
from django.http import HttpResponse
from django.views.generic import TemplateView
diff --git a/lib/acme/templates/acme_hello.html b/lib/acme/templates/acme_hello.html
index 679f45a2..dac5c66c 100755
--- a/lib/acme/templates/acme_hello.html
+++ b/lib/acme/templates/acme_hello.html
@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% load static %}
-{% load projecttags %}
+{% load jobtags %}
{% load humanize %}
{% block title %} ACME {% endblock %}
diff --git a/lib/acme/templates/acme_product.html b/lib/acme/templates/acme_product.html
index f1fb1a8b..0e519acd 100755
--- a/lib/acme/templates/acme_product.html
+++ b/lib/acme/templates/acme_product.html
@@ -1,6 +1,6 @@
{% extends "base.html" %}
-{% load projecttags %}
+{% load jobtags %}
{% block title %} {{object.name}} - ACME Style {% endblock %}
diff --git a/lib/acme/templates/base.html b/lib/acme/templates/base.html
index 5e1f847f..978f73bd 100755
--- a/lib/acme/templates/base.html
+++ b/lib/acme/templates/base.html
@@ -1,7 +1,6 @@
<!DOCTYPE html>
{% load static %}
-{% load projecttags %}
-{% load project_url_tag %}
+{% load jobtags %}
<html lang="en">
<head>
<title>
diff --git a/lib/acme/urls.py b/lib/acme/urls.py
index be10ef3e..9ce5d43a 100755
--- a/lib/acme/urls.py
+++ b/lib/acme/urls.py
@@ -1,4 +1,4 @@
-from django.conf.urls import include, url
+from django.urls import re_path as url,include
from . import views, tables
urlpatterns = [
diff --git a/lib/cve_checker/__init__.py b/lib/cve_checker/__init__.py
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/lib/cve_checker/__init__.py
diff --git a/lib/cve_checker/admin.py b/lib/cve_checker/admin.py
new file mode 100755
index 00000000..8c38f3f3
--- /dev/null
+++ b/lib/cve_checker/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/lib/cve_checker/apps.py b/lib/cve_checker/apps.py
new file mode 100755
index 00000000..0f8bc069
--- /dev/null
+++ b/lib/cve_checker/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class Cve_CheckerConfig(AppConfig):
+ name = 'cve_checker'
diff --git a/lib/cve_checker/migrations/0001_initial.py b/lib/cve_checker/migrations/0001_initial.py
new file mode 100644
index 00000000..29cf266c
--- /dev/null
+++ b/lib/cve_checker/migrations/0001_initial.py
@@ -0,0 +1,71 @@
+# Generated by Django 4.0 on 2023-11-15 08:56
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ('orm', '0014_alter_packagetocve_applicable'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Ck_Audit',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=80)),
+ ('create_time', models.DateTimeField(auto_now_add=True, null=True)),
+ ('orm_product', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='orm.product')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Ck_Layer',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=80)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Ck_Package',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=80)),
+ ('version', models.CharField(max_length=80)),
+ ('unpatched_cnt', models.IntegerField(default=0)),
+ ('ignored_cnt', models.IntegerField(default=0)),
+ ('patched_cnt', models.IntegerField(default=0)),
+ ('ck_audit', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='cve_checker.ck_audit')),
+ ('ck_layer', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='cve_checker.ck_layer')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Ck_Product',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=80)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='CkPackage2Cve',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('ck_status', models.IntegerField(choices=[(0, 'Undefined'), (1, 'Unpatched'), (2, 'Ignored'), (3, 'Patched')], default=0)),
+ ('ck_audit', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='cve_checker.ck_audit')),
+ ('ck_package', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='issue2pk_package', to='cve_checker.ck_package')),
+ ('orm_cve', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='orm.cve')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='CkPackage2CkProduct',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('cvesInRecord', models.BooleanField(default=True)),
+ ('ck_package', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='cve_checker.ck_package')),
+ ('ck_product', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='cve_checker.ck_product')),
+ ],
+ ),
+ ]
diff --git a/lib/cve_checker/migrations/0002_ckpackage2cve_ck_audit.py b/lib/cve_checker/migrations/0002_ckpackage2cve_ck_audit.py
new file mode 100644
index 00000000..9caf7520
--- /dev/null
+++ b/lib/cve_checker/migrations/0002_ckpackage2cve_ck_audit.py
@@ -0,0 +1,19 @@
+# Generated by Django 4.0 on 2023-11-12 18:32
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cve_checker', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='ckpackage2cve',
+ name='ck_audit',
+ field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='cve_checker.ck_audit'),
+ ),
+ ]
diff --git a/lib/cve_checker/migrations/0003_alter_ckpackage2cve_ck_package.py b/lib/cve_checker/migrations/0003_alter_ckpackage2cve_ck_package.py
new file mode 100644
index 00000000..3e6fa9c2
--- /dev/null
+++ b/lib/cve_checker/migrations/0003_alter_ckpackage2cve_ck_package.py
@@ -0,0 +1,19 @@
+# Generated by Django 4.0 on 2023-11-12 20:46
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cve_checker', '0002_ckpackage2cve_ck_audit'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='ckpackage2cve',
+ name='ck_package',
+ field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='issue2pk_package', to='cve_checker.ck_package'),
+ ),
+ ]
diff --git a/lib/cve_checker/migrations/0004_ck_package_ignored_cnt_ck_package_patched_cnt_and_more.py b/lib/cve_checker/migrations/0004_ck_package_ignored_cnt_ck_package_patched_cnt_and_more.py
new file mode 100644
index 00000000..6f36579c
--- /dev/null
+++ b/lib/cve_checker/migrations/0004_ck_package_ignored_cnt_ck_package_patched_cnt_and_more.py
@@ -0,0 +1,28 @@
+# Generated by Django 4.0 on 2023-11-15 02:23
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cve_checker', '0003_alter_ckpackage2cve_ck_package'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='ck_package',
+ name='ignored_cnt',
+ field=models.IntegerField(default=0),
+ ),
+ migrations.AddField(
+ model_name='ck_package',
+ name='patched_cnt',
+ field=models.IntegerField(default=0),
+ ),
+ migrations.AddField(
+ model_name='ck_package',
+ name='unpatched_cnt',
+ field=models.IntegerField(default=0),
+ ),
+ ]
diff --git a/lib/cve_checker/migrations/0005_ckuploadmanager.py b/lib/cve_checker/migrations/0005_ckuploadmanager.py
new file mode 100644
index 00000000..bb211c58
--- /dev/null
+++ b/lib/cve_checker/migrations/0005_ckuploadmanager.py
@@ -0,0 +1,27 @@
+# Generated by Django 4.0 on 2023-11-19 21:03
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cve_checker', '0004_ck_package_ignored_cnt_ck_package_patched_cnt_and_more'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='CkUploadManager',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('order', models.IntegerField(default=0)),
+ ('name', models.CharField(max_length=80)),
+ ('mode', models.CharField(max_length=20)),
+ ('path', models.TextField(blank=True)),
+ ('pem', models.TextField(blank=True)),
+ ('repo', models.TextField(blank=True)),
+ ('branch', models.TextField(blank=True)),
+ ('auto_refresh', models.BooleanField(default=True)),
+ ],
+ ),
+ ]
diff --git a/lib/cve_checker/migrations/0006_rename_mode_ckuploadmanager_import_mode.py b/lib/cve_checker/migrations/0006_rename_mode_ckuploadmanager_import_mode.py
new file mode 100644
index 00000000..46785880
--- /dev/null
+++ b/lib/cve_checker/migrations/0006_rename_mode_ckuploadmanager_import_mode.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.0 on 2023-11-19 21:23
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cve_checker', '0005_ckuploadmanager'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='ckuploadmanager',
+ old_name='mode',
+ new_name='import_mode',
+ ),
+ ]
diff --git a/lib/cve_checker/migrations/0007_ckuploadmanager_select_list_and_more.py b/lib/cve_checker/migrations/0007_ckuploadmanager_select_list_and_more.py
new file mode 100644
index 00000000..121dc9e6
--- /dev/null
+++ b/lib/cve_checker/migrations/0007_ckuploadmanager_select_list_and_more.py
@@ -0,0 +1,23 @@
+# Generated by Django 4.0 on 2023-11-20 07:19
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('cve_checker', '0006_rename_mode_ckuploadmanager_import_mode'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='ckuploadmanager',
+ name='select_list',
+ field=models.TextField(blank=True),
+ ),
+ migrations.AddField(
+ model_name='ckuploadmanager',
+ name='select_refresh',
+ field=models.DateTimeField(auto_now_add=True, null=True),
+ ),
+ ]
diff --git a/lib/cve_checker/migrations/__init__.py b/lib/cve_checker/migrations/__init__.py
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/lib/cve_checker/migrations/__init__.py
diff --git a/lib/cve_checker/models.py b/lib/cve_checker/models.py
new file mode 100755
index 00000000..8ed61a7a
--- /dev/null
+++ b/lib/cve_checker/models.py
@@ -0,0 +1,165 @@
+#
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# Security Response Tool Implementation
+#
+# Copyright (C) 2017-2023 Wind River Systems
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from __future__ import unicode_literals
+
+import sys
+import os
+import re
+import itertools
+from signal import SIGUSR1
+from datetime import datetime
+
+from django.db import models, IntegrityError, DataError
+from django.db import transaction
+from django.core import validators
+from django.conf import settings
+import django.db.models.signals
+from django.db.models import F, Q, Sum, Count
+from django.contrib.auth.models import AbstractUser, Group, AnonymousUser
+
+from orm.models import Cve, Product
+from srtgui.api import execute_process, execute_process_close_fds
+
+import logging
+logger = logging.getLogger("srt")
+
+# quick development/debugging support
+from srtgui.api import _log
+
+#######################################################################
+# Models
+#
+
+# CVE Checker Audit
+class Ck_Audit(models.Model):
+ search_allowed_fields = ['name', ]
+ name = models.CharField(max_length=80)
+ orm_product = models.ForeignKey(default=None, to='orm.product', null=True, on_delete=models.CASCADE,)
+ create_time = models.DateTimeField(auto_now_add=True, null=True)
+ @property
+ def get_package_count(self):
+ return (Ck_Package.objects.filter(ck_audit=self).count())
+ @property
+ def get_issue_count(self):
+ return (CkPackage2Cve.objects.filter(ck_audit=self).count())
+ @property
+ def get_unpatched_count(self):
+ return (CkPackage2Cve.objects.filter(ck_audit=self).filter(ck_status=CkPackage2Cve.UNPATCHED).count())
+ @property
+ def get_ignored_count(self):
+ return (CkPackage2Cve.objects.filter(ck_audit=self).filter(ck_status=CkPackage2Cve.IGNORED).count())
+ @property
+ def get_patched_count(self):
+ return (CkPackage2Cve.objects.filter(ck_audit=self).filter(ck_status=CkPackage2Cve.PATCHED).count())
+ @property
+ def get_undefined_count(self):
+ return (CkPackage2Cve.objects.filter(ck_audit=self).filter(ck_status=CkPackage2Cve.UNDEFINED).count())
+
+# Generated YP package
+class Ck_Package(models.Model):
+ search_allowed_fields = ['name', ]
+ name = models.CharField(max_length=80)
+ version = models.CharField(max_length=80)
+ ck_layer = models.ForeignKey(default=None, to='cve_checker.ck_layer', null=True, on_delete=models.CASCADE,)
+ ck_audit = models.ForeignKey(default=None, to='cve_checker.ck_audit', null=True, on_delete=models.CASCADE,)
+ # These values are here for filtering support, given limitations of Django's distinct() and table filters
+ unpatched_cnt = models.IntegerField(default=0)
+ ignored_cnt = models.IntegerField(default=0)
+ patched_cnt = models.IntegerField(default=0)
+ @property
+ def get_issue_count(self):
+ return (CkPackage2Cve.objects.filter(ck_package=self).count())
+ @property
+ def get_product_count(self):
+ return (CkPackage2CkProduct.objects.filter(ck_package=self).count())
+ @property
+ def get_product_names(self):
+ id_list = []
+ for pk2pr in CkPackage2CkProduct.objects.filter(ck_package=self):
+ id_list.append(f"{pk2pr.ck_product.name} ({pk2pr.cvesInRecord})")
+ return(','.join(id_list))
+
+# Representation of NVD "CPE"
+class Ck_Product(models.Model):
+ search_allowed_fields = ['name', ]
+ name = models.CharField(max_length=80)
+
+# YP Layer
+class Ck_Layer(models.Model):
+ search_allowed_fields = ['name', ]
+ name = models.CharField(max_length=80)
+
+# CVEs of a Package
+# Unpatched = "Not Fixed" and is (assumed) "Vulnerable"
+# Ignored = "Not Vulnerable" or "Won't Fix" or "Fixed"
+# Patched = "Fixed" or "Not Vulnerable"
+class CkPackage2Cve(models.Model):
+ search_allowed_fields = ['orm_cve__name', 'orm_cve__description']
+ # CveCheck Issue Status
+ UNDEFINED = 0
+ UNPATCHED = 1
+ IGNORED = 2
+ PATCHED = 3
+ CK_STATUS = (
+ (UNDEFINED , 'Undefined'),
+ (UNPATCHED, 'Unpatched'),
+ (IGNORED, 'Ignored'),
+ (PATCHED, 'Patched'),
+ )
+ ck_package = models.ForeignKey(default=None, to='cve_checker.ck_package', related_name="issue2pk_package", null=True, on_delete=models.CASCADE,)
+ orm_cve = models.ForeignKey(default=None, to='orm.cve', null=True, on_delete=models.CASCADE,)
+ ck_status = models.IntegerField(choices=CK_STATUS, default=UNDEFINED)
+ # Link to grandparent audit is included for instanct caounts in the GUI
+ ck_audit = models.ForeignKey(default=None, to='cve_checker.ck_audit', null=True, on_delete=models.CASCADE,)
+ @property
+ def get_status_text(self):
+ if (0 > self.ck_status) or (self.ck_status >= len(CkPackage2Cve.CK_STATUS)):
+ return 'Undefined'
+ return CkPackage2Cve.CK_STATUS[self.ck_status][1]
+
+# Products of a Package
+class CkPackage2CkProduct(models.Model):
+ ck_package = models.ForeignKey(default=None, to='cve_checker.ck_package', null=True, on_delete=models.CASCADE,)
+ ck_product = models.ForeignKey(default=None, to='cve_checker.ck_product', null=True, on_delete=models.CASCADE,)
+ cvesInRecord = models.BooleanField(default=True)
+
+# Products of a Package
+class CkUploadManager(models.Model):
+ order = models.IntegerField(default=0) # Display order
+ name = models.CharField(max_length=80) # Name of this import manager
+ import_mode = models.CharField(max_length=20) # Repo|SSL|File
+ path = models.TextField(blank=True) # Source path, path within repo
+ pem = models.TextField(blank=True) # PEM file for SSH
+ repo = models.TextField(blank=True) # Repository URL
+ branch = models.TextField(blank=True) # Branch in repo if any, for repo
+ auto_refresh = models.BooleanField(default=True) # if wild card, refresh when "Create Audit" is selected
+ select_refresh = models.DateTimeField(auto_now_add=True, null=True) # Last time select list was updated
+ select_list = models.TextField(blank=True) # List (if any) for pull down list, '|' delimited
+ @property
+ def is_select_list(self):
+ return (self.select_list and (0 < len(self.select_list)))
+ @property
+ def get_select_list(self):
+ return self.select_list.split('|')
+ @property
+ def get_path_filename(self):
+ return self.path.split('/')[-1]
diff --git a/lib/cve_checker/reports.py b/lib/cve_checker/reports.py
new file mode 100755
index 00000000..3735bcc3
--- /dev/null
+++ b/lib/cve_checker/reports.py
@@ -0,0 +1,511 @@
+#
+# Security Response Tool Implementation
+#
+# Copyright (C) 2023 Wind River Systems
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# Please run flake8 on this file before sending patches
+
+import os
+import re
+import logging
+from datetime import datetime, date
+import csv
+from openpyxl import Workbook
+from openpyxl import load_workbook
+from openpyxl.styles import Border, Side, PatternFill, Font, GradientFill, Alignment
+from openpyxl.utils import get_column_letter
+import shlex
+
+from srtgui.reports import Report, ReportManager, ProductsReport
+from cve_checker.models import Ck_Audit, Ck_Package, Ck_Product, Ck_Layer, CkPackage2CkProduct, CkPackage2Cve
+from srtgui.api import execute_process
+
+from django.db.models import Q, F
+from django.db import Error
+from srtgui.templatetags.jobtags import filtered_filesizeformat
+
+logger = logging.getLogger("srt")
+
+SRT_BASE_DIR = os.environ['SRT_BASE_DIR']
+SRT_REPORT_DIR = '%s/reports' % SRT_BASE_DIR
+
+# quick development/debugging support
+from srtgui.api import _log
+
+###############################################################################
+# Helper Routines
+#
+
+def _log_args(msg, *args, **kwargs):
+ s = '%s:(' % msg
+ if args:
+ for a in args:
+ s += '%s,' % a
+ s += '),('
+ if kwargs:
+ for key, value in kwargs.items():
+ s += '(%s=%s),' % (key,value)
+ s += ')'
+ _log(s)
+
+def dict_get_value(dict,name,default):
+ return dict[name] if name in dict else default
+
+###############################################################################
+# Excel/openpyxl common look and feel formatting objects
+#
+
+#pyxl_border_all = Border(left=thin, right=thin, top=thin, bottom=thin) # , outline=True)
+pyxl_thin = Side(border_style="thin")
+pyxl_double = Side(border_style="double")
+pyxl_border_left = Border(left=pyxl_thin)
+pyxl_border_bottom = Border(bottom=pyxl_thin)
+pyxl_border_bottom_left = Border(bottom=pyxl_thin, left=pyxl_thin)
+pyxl_alignment_left = Alignment(horizontal='left')
+pyxl_alignment_right = Alignment(horizontal='right')
+pyxl_alignment_wrap = Alignment(wrap_text=True)
+pyxl_alignment_top_wrap = Alignment(vertical="top",wrap_text=True)
+pyxl_font_bold = Font(bold=True)
+pyxl_font_red = Font(color="A00000",bold=True,size = "13")
+pyxl_font_grn = Font(color="00A000",bold=True,size = "13")
+pyxl_font_blu = Font(color="0000A0",bold=True,size = "13")
+pyxl_font_orn = Font(color="FF6600",bold=True,size = "13")
+pyxl_fill_green = PatternFill(start_color="E0FFF0", end_color="E0FFF0", fill_type = "solid")
+# Warning: the form "PatternFill(bgColor="xxxxxx", fill_type = "solid")" returns black cells
+pyxl_backcolor_red = PatternFill(start_color='FCCDBA', end_color='FCCDBA', fill_type = "solid")
+pyxl_backcolor_orn = PatternFill(start_color='FBEAAB', end_color='FBEAAB', fill_type = "solid")
+pyxl_backcolor_yel = PatternFill(start_color='FCFDC7', end_color='FCFDC7', fill_type = "solid")
+pyxl_backcolor_blu = PatternFill(start_color='C5E2FF', end_color='C5E2FF', fill_type = "solid")
+pyxl_backcolor_grn = PatternFill(start_color='D6EDBD', end_color='D6EDBD', fill_type = "solid")
+pyxl_cve_fills = [pyxl_backcolor_red,pyxl_backcolor_orn,pyxl_backcolor_yel,pyxl_backcolor_blu,None,None,None]
+
+def pyxl_write_cell(ws,row_num,column_num,value,border=None,font=None,fill=None,alignment=None):
+ cell = ws.cell(row=row_num, column=column_num)
+ try:
+ cell.value = value
+ if fill:
+ cell.fill = fill
+ if alignment:
+ cell.alignment = alignment
+ if border:
+ cell.border = border
+ if font:
+ cell.font = font
+ except Exception as e:
+ print("ERROR:(%d,%d):%s" % (row_num,column_num,e))
+ # Optional next column return value
+ return(column_num+1)
+
+
+###############################################################################
+# Report Manage for cvecheckerRecord
+#
+
+def doCveCheckerAuditSummaryExcel(ck_audit,options):
+ _log_args("doCveCheckerAuditSummaryExcel", options)
+
+ report_page = dict_get_value(options,'report_page', '')
+ search = dict_get_value(options,'search', '')
+ filter = dict_get_value(options,'filter', '')
+ filter_value = dict_get_value(options,'filter_value', '')
+ orderby = dict_get_value(options,'orderby', '')
+ default_orderby = dict_get_value(options,'default_orderby', '')
+ audit_id = dict_get_value(options,'audit_id', 1)
+
+ do_local_job = False
+ job_local_cnt = 0
+
+ audit_name_fixed = ck_audit.name
+ for ch in (' ','/',':','<','>','$','(',')','\\'):
+ audit_name_fixed = audit_name_fixed.replace(ch,'_')
+
+ report_path = '.'
+ report_name = f"summary_report_{audit_name_fixed}.xlsx"
+ report_full_path = os.path.join(SRT_REPORT_DIR,report_path,report_name)
+ wb = Workbook()
+ primary_sheet_used = False
+
+ #
+ # audit-summary Critical High Medium Low P1 P2 P3 P4 Repos
+ #
+
+ if 'audit-summary' in options:
+ job_local_cnt += 1
+ if do_local_job: job_local.update(job_local_cnt,job_local_max,'audit-summary')
+
+ if not primary_sheet_used:
+ ws = wb.active
+ ws.title = "Audit Summary"
+ primary_sheet_used = True
+ else:
+ ws = wb.create_sheet("Audit Summary")
+
+ ws.column_dimensions[get_column_letter(1)].width = 30
+ ws.column_dimensions[get_column_letter(2)].width = 40
+ row = 1
+
+ col = pyxl_write_cell(ws,row, 1,'CVE Checker Audit Report',font=pyxl_font_bold,border=pyxl_border_bottom)
+ row += 1
+
+ row += 1
+ col = pyxl_write_cell(ws,row, 1,'Audit name')
+ col = pyxl_write_cell(ws,row,col,ck_audit.name,font=pyxl_font_bold)
+ row += 1
+
+ col = pyxl_write_cell(ws,row, 1,'Release')
+ col = pyxl_write_cell(ws,row,col,ck_audit.orm_product.long_name)
+ row += 1
+
+ col = pyxl_write_cell(ws,row, 1,'Date')
+ col = pyxl_write_cell(ws,row,col,str(ck_audit.create_time))
+ row += 1
+
+ # Compute products and layers
+ product_count = 0
+ layers = {}
+ for ck_package in Ck_Package.objects.filter(ck_audit=ck_audit):
+ product_count += CkPackage2CkProduct.objects.filter(ck_package=ck_package).count()
+ layers[ck_package.ck_layer.name] = 1
+ layer_count = len(layers)
+
+ # Compute CVEs
+ severity_table = []
+ # Critical, High, Medium, Low, Unknown
+ severity_table.append([0,0,0,0,0]) # UNDEFINED
+ severity_table.append([0,0,0,0,0]) # UNPATCHED
+ severity_table.append([0,0,0,0,0]) # IGNORED
+ severity_table.append([0,0,0,0,0]) # PATCHED
+ s2i = {}
+ s2i['CRITICAL'] = 0
+ s2i['HIGH'] = 1
+ s2i['MEDIUM'] = 2
+ s2i['LOW'] = 3
+ s2i[''] = 4
+ unique_cves = {}
+ for issue in CkPackage2Cve.objects.filter(ck_audit=ck_audit):
+ unique_cves[issue.orm_cve.name] = 1
+ severity = issue.orm_cve.cvssV3_baseSeverity if issue.orm_cve.cvssV3_baseSeverity else issue.orm_cve.cvssV2_severity
+ severity = severity.upper()
+ try:
+ col = s2i[severity.upper()]
+ except:
+ col = 4
+ severity_table[issue.ck_status][col] += 1
+
+ row += 1
+ col = pyxl_write_cell(ws,row, 1,'Package Count')
+ col = pyxl_write_cell(ws,row,col,ck_audit.get_package_count)
+ row += 1
+
+ col = pyxl_write_cell(ws,row, 1,'Product Count')
+ col = pyxl_write_cell(ws,row,col, product_count)
+ row += 1
+
+ col = pyxl_write_cell(ws,row, 1,'Layer Count')
+ col = pyxl_write_cell(ws,row,col,layer_count)
+ row += 1
+
+ col = pyxl_write_cell(ws,row, 1,'Issue Count')
+ col = pyxl_write_cell(ws,row,col,ck_audit.get_issue_count)
+ row += 1
+
+ col = pyxl_write_cell(ws,row, 1,'Unique Issue Count')
+ col = pyxl_write_cell(ws,row,col,len(unique_cves))
+ row += 1
+
+ col = pyxl_write_cell(ws,row, 4,'Critical',border=pyxl_border_bottom)
+ col = pyxl_write_cell(ws,row,col,'High',border=pyxl_border_bottom)
+ col = pyxl_write_cell(ws,row,col,'Medium',border=pyxl_border_bottom)
+ col = pyxl_write_cell(ws,row,col,'Low',border=pyxl_border_bottom)
+ col = pyxl_write_cell(ws,row,col,'Undefined',border=pyxl_border_bottom)
+ row += 1
+
+ def append_severity(status_id,row):
+ col = pyxl_write_cell(ws,row, 4,severity_table[status_id][0],fill=pyxl_backcolor_red)
+ col = pyxl_write_cell(ws,row,col,severity_table[status_id][1],fill=pyxl_backcolor_orn)
+ col = pyxl_write_cell(ws,row,col,severity_table[status_id][2],fill=pyxl_backcolor_blu)
+ col = pyxl_write_cell(ws,row,col,severity_table[status_id][3],fill=pyxl_backcolor_grn)
+ col = pyxl_write_cell(ws,row,col,severity_table[status_id][4])
+
+ col = pyxl_write_cell(ws,row, 1,'Unpatched_Count')
+ col = pyxl_write_cell(ws,row,col,ck_audit.get_unpatched_count,fill=pyxl_backcolor_red)
+ append_severity(CkPackage2Cve.UNPATCHED,row)
+ row += 1
+
+ col = pyxl_write_cell(ws,row, 1,'Ignored_Count')
+ col = pyxl_write_cell(ws,row,col,ck_audit.get_ignored_count)
+ append_severity(CkPackage2Cve.IGNORED,row)
+ row += 1
+
+ col = pyxl_write_cell(ws,row, 1,'Patched_Count')
+ col = pyxl_write_cell(ws,row,col,ck_audit.get_patched_count)
+ append_severity(CkPackage2Cve.PATCHED,row)
+ row += 1
+
+ #
+ # package-summary
+ #
+
+ if 'package-summary' in options:
+ job_local_cnt += 1
+ if do_local_job: job_local.update(job_local_cnt,job_local_max,'package-summary')
+
+ if not primary_sheet_used:
+ ws = wb.active
+ ws.title = "Package Summary"
+ primary_sheet_used = True
+ else:
+ ws = wb.create_sheet("Package Summary")
+
+ ws.column_dimensions[get_column_letter(1)].width = 30
+ ws.column_dimensions[get_column_letter(5)].width = 30
+ ws.column_dimensions[get_column_letter(6)].width = 60
+
+ row = 1
+ first_row = 2
+
+ col = 1
+ for header in ('Package','Version','Layer','Issues','Unpatched CVE','Products (cvesInRecord)'):
+# border = pyxl_border_bottom_left if (col in (3,7,12)) else pyxl_border_bottom
+ border = pyxl_border_bottom
+ pyxl_write_cell(ws,row,col,header,border=border)
+ if (col >= 2) and (col <= 10):
+ ws.column_dimensions[get_column_letter(col)].width = 11
+ col += 1
+ row += 1
+
+ # Sort packages by severity,package.name
+ package_list = Ck_Package.objects.filter(ck_audit=ck_audit).order_by('name')
+ for package in package_list:
+ col = pyxl_write_cell(ws,row, 1,package.name)
+ col = pyxl_write_cell(ws,row,col,package.version)
+ col = pyxl_write_cell(ws,row,col,package.ck_layer.name)
+ col = pyxl_write_cell(ws,row,col,package.get_issue_count)
+ col = pyxl_write_cell(ws,row,col,package.unpatched_cnt)
+ col = pyxl_write_cell(ws,row,col,package.get_product_names)
+ row += 1
+
+ #
+ # unpatched-summary
+ #
+
+ if ('unpatched-summary' in options) or ('unpatched-summary-compare' in options):
+ job_local_cnt += 1
+ if do_local_job: job_local.update(job_local_cnt,job_local_max,'unpatched-summary')
+
+ if not primary_sheet_used:
+ ws = wb.active
+ ws.title = "Unpatched Summary"
+ primary_sheet_used = True
+ else:
+ ws = wb.create_sheet("Unpatched Summary")
+
+ # Comparables
+ comparable_list = ['wr_trivy','ubuntu_trivy']
+
+ ws.column_dimensions[get_column_letter(1)].width = 20
+ ws.column_dimensions[get_column_letter(2)].width = 14
+ ws.column_dimensions[get_column_letter(7)].width = 14
+ ws.column_dimensions[get_column_letter(8)].width = 14
+ for i,comparable in enumerate(comparable_list):
+ ws.column_dimensions[get_column_letter(9+i)].width = 60
+
+ row = 1
+ first_row = 2
+ col = 1
+ header_list = ['Issue','Status','V3 Severity','V3 Score','V2 Severity','V2 Score','Published','Package']
+ if 'unpatched-summary-compare' in options:
+ header_list.extend(comparable_list)
+ for header in header_list:
+ border = pyxl_border_bottom
+ pyxl_write_cell(ws,row,col,header,border=border)
+ col += 1
+ row += 1
+
+ # Sort packages by severity,package.name
+ issues_list = CkPackage2Cve.objects.filter(ck_audit=ck_audit).filter(ck_status=CkPackage2Cve.UNPATCHED).order_by('orm_cve__name')
+
+ # Merge comparibles?
+ comparibles = {}
+ if 'unpatched-summary-compare' in options:
+ issue_list = {}
+ for issue in issues_list:
+ issue_list[issue.orm_cve.name] = 1
+ filename = ('.cve_list.txt')
+ with open(filename, 'w') as outfile:
+ outfile.write('\n'.join(str(cve) for cve in issue_list))
+
+ for i,comparable in enumerate(comparable_list):
+ comparibles[comparable] = {}
+ exec_returncode,exec_stdout,exec_stderr = execute_process(f"bin/{comparable}/srtool_{comparable}.py",'--comparibles',filename)
+ for i,line in enumerate(exec_stdout.splitlines()):
+ cve,status = line.split('||')
+ comparibles[comparable][cve] = status.replace('[EOL]','\n')
+
+ # Generate output
+ for issue in issues_list:
+ col = pyxl_write_cell(ws,row, 1,issue.orm_cve.name,alignment=pyxl_alignment_top_wrap)
+ col = pyxl_write_cell(ws,row,col,issue.get_status_text,alignment=pyxl_alignment_top_wrap)
+ if (not issue.orm_cve.cvssV3_baseScore) or (0.1 > float(issue.orm_cve.cvssV3_baseScore)):
+ col = pyxl_write_cell(ws,row,col,'',alignment=pyxl_alignment_top_wrap)
+ col = pyxl_write_cell(ws,row,col,'',alignment=pyxl_alignment_top_wrap)
+ else:
+ col = pyxl_write_cell(ws,row,col,issue.orm_cve.cvssV3_baseSeverity,alignment=pyxl_alignment_top_wrap)
+ col = pyxl_write_cell(ws,row,col,issue.orm_cve.cvssV3_baseScore,alignment=pyxl_alignment_top_wrap)
+ if (not issue.orm_cve.cvssV2_baseScore) or (0.1 > float(issue.orm_cve.cvssV2_baseScore)):
+ col = pyxl_write_cell(ws,row,col,issue.orm_cve.cvssV2_severity,alignment=pyxl_alignment_top_wrap)
+ col = pyxl_write_cell(ws,row,col,issue.orm_cve.cvssV2_baseScore,alignment=pyxl_alignment_top_wrap)
+ else:
+ col = pyxl_write_cell(ws,row,col,'',alignment=pyxl_alignment_top_wrap)
+ col = pyxl_write_cell(ws,row,col,'',alignment=pyxl_alignment_top_wrap)
+ col = pyxl_write_cell(ws,row,col,issue.orm_cve.publishedDate,alignment=pyxl_alignment_top_wrap)
+ col = pyxl_write_cell(ws,row,col,issue.ck_package.name,alignment=pyxl_alignment_top_wrap)
+
+ if 'unpatched-summary-compare' in options:
+ # Extend the height of the row to show the comparible data
+ ws.row_dimensions[row].height = 70
+ for i,comparable in enumerate(comparable_list):
+ if issue.orm_cve.name in comparibles[comparable]:
+ col = pyxl_write_cell(ws,row,col,comparibles[comparable][issue.orm_cve.name],alignment=pyxl_alignment_top_wrap)
+ row += 1
+
+ wb.save(report_name)
+ return(report_name)
+
+###############################################################################
+#
+# Audit Difference Report
+#
+# db_audit_1 is older
+# db_audit_2 is newer
+
+#
+# TBD
+#
+
+def do_audit_cvechecker_diff_report(db_audit_1, db_audit_2, options):
+ _log_args("DO_AUDIT_DIFF_REPORT", db_audit_1.name, db_audit_1.id, db_audit_2.name, db_audit_2.id, options)
+ global audit_summary
+
+ records = dict_get_value(options,'records','')
+ format = dict_get_value(options,'format', '')
+ title = dict_get_value(options,'title', '')
+ report_type = dict_get_value(options,'report_type', '')
+ record_list = dict_get_value(options,'record_list', '')
+
+ audit_scope_criticals = ('0' == dict_get_value(options,'audit_scope', '0'))
+ delimiter = ','
+
+ #
+ # Audits load
+ #
+ db_table_1 = {}
+ for db_rec in cvecheckerRecord.objects.filter(cvecheckeraudit=db_audit_1):
+ key= f"{db_rec.plugin_id}"
+ db_table_1[key] = db_rec.id
+
+ db_table_2 = {}
+ for db_rec in cvecheckerRecord.objects.filter(cvecheckeraudit=db_audit_2):
+ key= f"{db_rec.plugin_id}"
+ db_table_2[key] = db_rec.id
+ _log(f"FOO:DB_TABLE_1:{len(db_table_1)}")
+ _log(f"FOO:DB_TABLE_2:{len(db_table_2)}")
+
+
+ # Audits compare
+ #
+ db_add = []
+ db_remove = []
+ for key in db_table_1: # Is in Older
+ if not key in db_table_2: # Not in Newer (removed)
+ db_remove.append(db_table_1[key])
+ for key in db_table_2: # Is in Newer
+ if not key in db_table_1: # Not in Older (added)
+ db_add.append(db_table_2[key])
+
+ def update_ws(ws,msg,audit,table):
+ row = 1
+ col = 1
+ for header in ('name', 'port','protocol','product'):
+ col = pyxl_write_cell(ws,row,col,header,border = pyxl_border_bottom)
+ row += 1
+ ws.column_dimensions[get_column_letter(1)].width = 40
+ ws.column_dimensions[get_column_letter(2)].width = 14
+ ws.column_dimensions[get_column_letter(3)].width = 40
+ ws.column_dimensions[get_column_letter(5)].width = 40
+
+ count = 0
+ cvechecker_obj=cvecheckerRecord.objects.filter(cvecheckeraudit=audit)
+ for db_rec in cvechecker_obj :
+ if db_rec.id in table:
+ count += 1
+ col = 1
+ col = pyxl_write_cell(ws,row,col,db_rec.name)
+ col = pyxl_write_cell(ws,row,col,db_rec.port)
+ col = pyxl_write_cell(ws,row,col,db_rec.protocol)
+ col = pyxl_write_cell(ws,row,col,db_rec.cvecheckeraudit.product)
+ row += 1
+ row -= 1
+ for i in range(1,5):
+ ws.cell(row=row,column=i).border=pyxl_border_bottom
+ row += 1
+ pyxl_write_cell(ws,row,1,msg)
+ pyxl_write_cell(ws,row,2,count)
+
+ row += 2
+ pyxl_write_cell(ws,row,1,f"cvechecker ({audit.id})")
+ pyxl_write_cell(ws,row,2,audit.name)
+ #pyxl_write_cell(ws,row,4,audit.audit_date)
+
+ report_name = f"cvecheckerbench_diff_report_{db_audit_1.id}_{db_audit_2.id}.xlsx"
+ report_path = '.'
+ report_full_path = os.path.join(SRT_REPORT_DIR,report_path,report_name)
+ wb = Workbook()
+ ws = wb.active
+ ws.title = 'Added'
+ update_ws(ws,'Added',db_audit_2,db_add)
+ ws = wb.create_sheet('Removed')
+ update_ws(ws,'Removed',db_audit_1,db_remove)
+
+ wb.save(report_full_path)
+ return(report_full_path)
+
+###############################################################################
+# Report Manage for cvechecker
+#
+
+class cvecheckerRecordReportManager():
+ @staticmethod
+ def get_report_class(parent_page, *args, **kwargs):
+ _log("CVECHECKERREPORTMANAGER:%s:" % parent_page)
+ if 'gitleaks' == parent_page:
+ # Extend the Products report
+ return cvecheckerRecordSummaryReport(parent_page, *args, **kwargs)
+ else:
+ # Return the default for all other reports
+ return ReportManager.get_report_class(parent_page, *args, **kwargs)
+
+ @staticmethod
+ def get_context_data(parent_page, *args, **kwargs):
+ _log_args("CVECHECKER_REPORTMANAGER_CONTEXT", *args, **kwargs)
+ reporter = cvecheckerRecordReportManager.get_report_class(parent_page, *args, **kwargs)
+ return reporter.get_context_data(*args, **kwargs)
+
+ @staticmethod
+ def exec_report(parent_page, *args, **kwargs):
+ _log_args("CVECHECKER_REPORTMANAGER_EXEC", *args, **kwargs)
+ reporter = cvecheckerRecordReportManager.get_report_class(parent_page, *args, **kwargs)
+ return reporter.exec_report(*args, **kwargs)
diff --git a/lib/cve_checker/tables.py b/lib/cve_checker/tables.py
new file mode 100755
index 00000000..252d109f
--- /dev/null
+++ b/lib/cve_checker/tables.py
@@ -0,0 +1,695 @@
+#
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# Security Response Tool Implementation
+#
+# Copyright (C) 2023 Wind River Systems
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+#
+# NOTICE: Important ToasterTable implementation concepts and limitations
+#
+# 1) The order of table method execution. This implies that data added
+# to the table object in "get_context_data" is NOT persistent.
+#
+# a) __init__
+# b) get_context_data
+# c) __init__ (second call reason unknown)
+# d) setup_queryset
+# e) setup_filters (if present)
+# f) setup_columns
+# g) apply_row_customization (if present)
+#
+# 2) Named URL path arguments from "urls.py" are accessible via kwargs
+# WARNING: these values not NOT available in "__init__"
+#
+# Example:
+# urls.ps : url(r'^foo/(?P<my_value>\d+)$',
+# tables.py: my_value = int(kwargs['my_value'])
+#
+# 3) Named URL query arguments the table's url are accessible via the request
+#
+# Example:
+# url : http://.../foo/bar/42605?my_value=25
+# tables.py: my_value = self.request.GET.get('my_value','0')
+#
+# 4) The context[] values are NOT present in the individual "setup_columns" context
+# They must be explicitly implemented into the individual column data without Django translation
+#
+# 5) The HTML page's templatetags are NOT present in the "setup_columns" context
+# They must be explicitly added into the template code
+#
+# Example:
+# static_data_template = '''
+# {% load jobtags %}<span onclick="toggle_select(\'box_{{data.id}}\');">{{data.recommend|recommend_display}}</span>
+# '''
+#
+# WARNING: because there is no context (#4), you cannot for example use dictionary lookup filters
+# use apply_row_customization() method instead, and set the self.dict_name in setup_columns()
+#
+
+import os
+import re
+import json
+from datetime import timedelta, datetime, date
+import pytz
+import traceback
+
+from django.db.models import Q, Max, Sum, Count, When, Case, Value, IntegerField
+from django.urls import re_path as url
+from django.urls import reverse, resolve
+from django.http import HttpResponse
+from django.views.generic import TemplateView
+
+from srtgui.widgets import ToasterTable
+from cve_checker.models import Ck_Audit, Ck_Package, Ck_Product, Ck_Layer, CkPackage2CkProduct, CkPackage2Cve, CkUploadManager
+from orm.models import Cve, Product
+from orm.models import Notify, NotifyAccess, NotifyCategories
+from orm.models import DataSource, SrtSetting, Job
+from users.models import SrtUser, UserSafe
+from srtgui.api import execute_process
+
+from srtgui.tablefilter import TableFilter
+from srtgui.tablefilter import TableFilterActionToggle
+from srtgui.tablefilter import TableFilterActionDateRange
+from srtgui.tablefilter import TableFilterActionDay
+
+# quick development/debugging support
+from srtgui.api import _log
+
+class CveCheckerAuditsTable(ToasterTable):
+ """Table of All CvecheckerRecord audits"""
+
+ def __init__(self, *args, **kwargs):
+ super(CveCheckerAuditsTable, self).__init__(*args, **kwargs)
+ self.default_orderby = "-id"
+
+ def get_context_data(self, **kwargs):
+ create_time = datetime.now(pytz.utc)
+ context = super(CveCheckerAuditsTable, self).get_context_data(**kwargs)
+ context['orm_products'] = Product.objects.all().order_by('name')
+ context['ab_sets'] = ("master","nanbield","mickledore","langdale","kirkstone","dunfell")
+ context['new_audit_name'] = 'audit_%s' % (create_time.strftime('%Y%m%d'))
+ context['default_product'] = 'Yocto Project master'
+ context['srt_cvechecker_update'] = SrtSetting.get_setting('SRT_CVECHECKER_UPDATE','')
+ context['mru'] = Job.get_recent()
+ context['mrj_type'] = 'all'
+ # Enforce at least the "Upload" import
+ ck_import_obj,created = CkUploadManager.objects.get_or_create(name='Upload')
+ if created:
+ ck_import_obj.order = 1
+ ck_import_obj.import_mode = 'Upload'
+ ck_import_obj.path = ''
+ ck_import_obj.pem = ''
+ ck_import_obj.repo = ''
+ ck_import_obj.branch = ''
+ ck_import_obj.auto_refresh = False
+ ck_import_obj.select_refresh = datetime.now(pytz.utc)
+ ck_import_obj.select_list = "master|nanbield|mickledore|langdale|kirkstone|dunfell"
+ ck_import_obj.save()
+ ck_import_obj,created = CkUploadManager.objects.get_or_create(name='Import from Auto Builder scan')
+ if created:
+ ck_import_obj.order = 2
+ ck_import_obj.import_mode = 'Repo'
+ ck_import_obj.path = 'yocto-metrics/cve-check'
+ ck_import_obj.pem = ''
+ ck_import_obj.repo = 'git://git.yoctoproject.org/yocto-metrics'
+ ck_import_obj.branch = ''
+ ck_import_obj.auto_refresh = True
+ ck_import_obj.select_refresh = datetime.now(pytz.utc)
+ ck_import_obj.select_list = "master|nanbield|mickledore|langdale|kirkstone|dunfell"
+ ck_import_obj.save()
+ context['ckuploadmanager'] = CkUploadManager.objects.all().order_by('order')
+ # Update the Import select tables
+ cmnd = ["bin/cve_checker/srtool_cvechecker.py","--update-imports","-f"]
+ result_returncode,result_stdout,result_stderr = execute_process(*cmnd)
+ if 0 != result_returncode:
+ _log(f"ERROR:{cmnd}: {result_stderr}:{result_stdout}:")
+ return context
+
+ def setup_queryset(self, *args, **kwargs):
+ self.queryset = Ck_Audit.objects.all()
+ self.queryset = self.queryset.order_by('-id')
+
+ def setup_filters(self, *args, **kwargs):
+ pass
+
+ def setup_columns(self, *args, **kwargs):
+
+ self.add_column(title="Id",
+ field_name="id",
+ hideable=False,
+ orderable=True,
+ )
+
+ name_template = '''
+ <span id="audit_name-disp-{{data.id}}"><td><a href="{% url 'cvechecker_audit' data.id %}">{{data.name}}</a></td></span>
+ <span id="audit_name-edit-{{data.id}}" style="display:none;">
+ <input type="text" id="audit_name-text-{{data.id}}" value="{{data.name}}" size="50">
+ </span>
+ '''
+ self.add_column(title="Name",
+ orderable=True,
+ static_data_name="name",
+ static_data_template=name_template,
+ )
+
+ self.add_column(title="Create Time",
+ field_name="create_time",
+ hideable=True,
+ hidden=True,
+ orderable=True,
+ )
+
+ ck_package_link_template = '''
+ <td><a href="{% url 'cvechecker_audit' data.id %}">{{data.get_package_count}}</a></td>
+ '''
+ self.add_column(title="Package Count",
+ static_data_name="count",
+ static_data_template=ck_package_link_template,
+ )
+
+ self.add_column(title="Unpatched CVE",
+ static_data_name="unpatched_count",
+ static_data_template='<b><label style="color:DarkRed">{{data.get_unpatched_count}}</label></b>',
+ )
+ self.add_column(title="Ignored CVE",
+ static_data_name="ignored_count",
+ static_data_template='<label style="color:green">{{data.get_ignored_count}}</label>',
+ )
+ self.add_column(title="Patched CVE",
+ static_data_name="patched_count",
+ static_data_template='<label style="color:green">{{data.get_patched_count}}</label>',
+ )
+ self.add_column(title="Undefined CVE",
+ static_data_name="undefined_count",
+ static_data_template='<label style="color:DarkRed">{{data.get_undefined_count}}</label>',
+ hideable=True,
+ hidden=True,
+ )
+
+ self.add_column(title="YP Release",
+ static_data_name="orm_product__profile",
+ static_data_template='{{data.orm_product.long_name}}',
+ orderable=True,
+ )
+
+ if UserSafe.is_contributor(self.request.user):
+ manage_link_template = '''
+ <span class="glyphicon glyphicon-edit edit-ck-entry" id="edit-entry-{{data.id}}" x-data="{{data.id}}"></span>
+ <span class="glyphicon glyphicon glyphicon glyphicon-ok save-ck-entry" id="save-entry-{{data.id}}" x-data="{{data.id}}" style="display:none;color: Chartreuse;"></span>
+ &nbsp;&nbsp;&nbsp;&nbsp;
+ <span class="glyphicon glyphicon glyphicon glyphicon-remove cancel-ck-entry" id="cancel-entry-{{data.id}}" x-data="{{data.id}}" style="display:none;color: Crimson;"></span>
+ <span class="glyphicon glyphicon-trash trash-audit" x-data="{{data.create_time}}|{{data.id}}"></span>
+ '''
+ self.add_column(title="Manage",
+ hideable=True,
+ static_data_name="manage",
+ static_data_template=manage_link_template,
+ )
+
+
+class CveCheckerAuditTable(ToasterTable):
+ """Table of All entries in CvecheckerRecord"""
+
+ def __init__(self, *args, **kwargs):
+ super(CveCheckerAuditTable, self).__init__(*args, **kwargs)
+ self.default_orderby = "name"
+
+ def get_context_data(self, **kwargs):
+ context = super(CveCheckerAuditTable, self).get_context_data(**kwargs)
+ audit_id = int(kwargs['audit_id'])
+ context['Ck_Audit'] = Ck_Audit.objects.get(id=audit_id)
+ context['mru'] = Job.get_recent()
+ context['mrj_type'] = 'all'
+ return context
+
+ def setup_queryset(self, *args, **kwargs):
+ audit_id = int(kwargs['audit_id'])
+ self.queryset = Ck_Package.objects.filter(ck_audit_id=audit_id)
+ self.queryset = self.queryset.order_by(self.default_orderby)
+
+ def setup_filters(self, *args, **kwargs):
+ # Status filter
+ is_status = TableFilter(name="is_status", title="Status")
+ audit_id = int(kwargs['audit_id'])
+ status_filter = TableFilterActionToggle(
+ "unpatched",
+ "Unpatched",
+ Q(unpatched_cnt__gt=0))
+ is_status.add_action(status_filter)
+ status_filter = TableFilterActionToggle(
+ "patched/ignored",
+ "Patched/Ignored",
+ Q(unpatched_cnt=0))
+ is_status.add_action(status_filter)
+ self.add_filter(is_status)
+
+ def setup_columns(self, *args, **kwargs):
+
+ self.add_column(title="Id",
+ field_name="id",
+ hideable=True,
+ hidden=True,
+ )
+
+ self.add_column(title="Name",
+ field_name="name",
+ hideable=False,
+ orderable=True,
+ )
+
+ self.add_column(title="Version",
+ field_name="version",
+ hideable=False,
+ orderable=True,
+ )
+
+ self.add_column(title="Layer",
+ field_name="ck_layer",
+ hideable=False,
+ orderable=True,
+ static_data_name="ck_layer",
+ static_data_template="{{data.ck_layer.name}}",
+ )
+
+ issue_link_template = '''
+ <a href="{% url 'cvechecker_issue' data.id %}">{{data.get_issue_count}}</a>
+ '''
+ self.add_column(title="Issues",
+ static_data_name="issue_count",
+ static_data_template=issue_link_template,
+ )
+
+ unpatched_link_template = '''
+ <label style="color:{% if data.unpatched_cnt %}DarkRed{% else %}green{% endif %}">{{data.unpatched_cnt}}</label>
+ '''
+ self.add_column(title="Unpatched CVE",
+ filter_name="is_status",
+ static_data_name="unpatched_count",
+ static_data_template=unpatched_link_template,
+ )
+
+ product_link_template = '''
+ <td><a href="{% url 'cvechecker_product' data.id %}">{{data.get_product_names}}</a></td>
+ '''
+ self.add_column(title="Products (cvesInRecord)",
+ static_data_name="product_count",
+ static_data_template=product_link_template,
+ )
+
+
+class CveCheckerAuditCveTable(ToasterTable):
+ """Table of All entries in CvecheckerRecord"""
+
+ def __init__(self, *args, **kwargs):
+ super(CveCheckerAuditCveTable, self).__init__(*args, **kwargs)
+ self.default_orderby = "orm_cve__name"
+
+ def get_context_data(self, **kwargs):
+ context = super(CveCheckerAuditCveTable, self).get_context_data(**kwargs)
+ audit_id = int(kwargs['audit_id'])
+ context['Ck_Audit'] = Ck_Audit.objects.get(id=audit_id)
+ context['mru'] = Job.get_recent()
+ context['mrj_type'] = 'all'
+ return context
+
+ def setup_queryset(self, *args, **kwargs):
+ audit_id = int(kwargs['audit_id'])
+ self.queryset = CkPackage2Cve.objects.filter(ck_audit_id=audit_id)
+ self.queryset = self.queryset.order_by(self.default_orderby)
+
+ def setup_filters(self, *args, **kwargs):
+ # Status filter
+ is_status = TableFilter(name="is_status", title="Status")
+ for status_id in range(CkPackage2Cve.UNPATCHED,CkPackage2Cve.PATCHED+1):
+ status_filter = TableFilterActionToggle(
+ CkPackage2Cve.CK_STATUS[status_id][1],
+ CkPackage2Cve.CK_STATUS[status_id][1],
+ Q(ck_status=status_id))
+ is_status.add_action(status_filter)
+ self.add_filter(is_status)
+
+ def setup_columns(self, *args, **kwargs):
+
+ self.add_column(title="Id",
+ field_name="id",
+ hideable=True,
+ hidden=True,
+ )
+
+ cve_link_template = '''
+ <a href="{% url 'cve' data.orm_cve.name %}" target="_blank">{{data.orm_cve.name}}</a>
+ '''
+ self.add_column(title="Name",
+ static_data_name="orm_cve__name",
+ static_data_template=cve_link_template,
+ hideable=False,
+ orderable=True,
+ )
+
+ self.add_column(title="Status",
+ filter_name="is_status",
+ static_data_name="status",
+ static_data_template="{{data.get_status_text}}",
+ )
+
+ self.add_column(title="V3 Severity",
+ orderable=True,
+ static_data_name="orm_cve__cvssV3_baseSeverity",
+ static_data_template="{{data.orm_cve.cvssV3_baseSeverity}}",
+ )
+
+ self.add_column(title="V3 Score",
+ orderable=True,
+ static_data_name="orm_cve__cvssV3_baseScore",
+ static_data_template="{{data.orm_cve.cvssV3_baseScore}}",
+ )
+
+ self.add_column(title="V2 Severity",
+ orderable=True,
+ static_data_name="data.orm_cve__cvssV2_severity",
+ static_data_template="{{data.orm_cve.cvssV2_severity}}",
+ )
+
+ self.add_column(title="V2 Score",
+ orderable=True,
+ static_data_name="data.orm_cve__cvssV2_baseScore",
+ static_data_template="{{data.orm_cve.cvssV2_baseScore}}",
+ )
+
+ self.add_column(title="Published",
+ static_data_name="data.orm_cve__publishedDate",
+ static_data_template="{{data.orm_cve.publishedDate}}",
+ )
+
+ self.add_column(title="Package",
+ orderable=True,
+ static_data_name="ck_package__name",
+ static_data_template="{{data.ck_package.name}}",
+ )
+
+
+class CveCheckerIssueTable(ToasterTable):
+ """Table of Issues in CvecheckerRecord"""
+
+ def __init__(self, *args, **kwargs):
+ super(CveCheckerIssueTable, self).__init__(*args, **kwargs)
+ self.default_orderby = "orm_cve__name"
+
+ def get_context_data(self, **kwargs):
+ context = super(CveCheckerIssueTable, self).get_context_data(**kwargs)
+ package_id = int(kwargs['package_id'])
+ context['Ck_Package'] = Ck_Package.objects.get(id=package_id)
+ context['mru'] = Job.get_recent()
+ context['mrj_type'] = 'all'
+ return context
+
+ def setup_queryset(self, *args, **kwargs):
+ package_id = int(kwargs['package_id'])
+ self.queryset = CkPackage2Cve.objects.filter(ck_package_id=package_id)
+ self.queryset = self.queryset.order_by(self.default_orderby)
+
+ def setup_filters(self, *args, **kwargs):
+ # Status filter
+ is_status = TableFilter(name="is_status", title="Status")
+ for status_id in range(CkPackage2Cve.UNPATCHED,CkPackage2Cve.PATCHED+1):
+ status_filter = TableFilterActionToggle(
+ CkPackage2Cve.CK_STATUS[status_id][1],
+ CkPackage2Cve.CK_STATUS[status_id][1],
+ Q(ck_status=status_id))
+ is_status.add_action(status_filter)
+ self.add_filter(is_status)
+
+ def setup_columns(self, *args, **kwargs):
+
+ cve_link_template = '''
+ <a href="{% url 'cve' data.orm_cve.name %}" target="_blank">{{data.orm_cve.name}}</a>
+ '''
+ self.add_column(title="Issue",
+ static_data_name="orm_cve__name",
+ static_data_template=cve_link_template,
+ hideable=False,
+ orderable=True,
+ )
+
+ self.add_column(title="CK Status",
+ filter_name="is_status",
+ static_data_name="ck_status",
+ static_data_template="{{data.get_status_text}}",
+ hideable=False,
+ orderable=True,
+ )
+
+ self.add_column(title="description",
+ static_data_name="orm_cve__description",
+ static_data_template="{{data.orm_cve.description}}",
+ hideable=False,
+ orderable=True,
+ )
+
+ self.add_column(title="V3 Score",
+ static_data_name="orm_cve__cvssV3_baseScore",
+ static_data_template="{{data.orm_cve.cvssV3_baseScore}}",
+ hideable=False,
+ orderable=True,
+ )
+
+ self.add_column(title="V3 Severity",
+ static_data_name="orm_cve__cvssV3_baseSeverity",
+ static_data_template="{{data.orm_cve.cvssV3_baseSeverity}}",
+ hideable=False,
+ )
+
+ self.add_column(title="V2 Score",
+ static_data_name="orm_cve__cvssV2_baseScore",
+ static_data_template="{{data.orm_cve.cvssV2_baseScore}}",
+ hideable=True,
+ )
+
+ self.add_column(title="V2 Severity",
+ static_data_name="orm_cve__cvssV2_severity",
+ static_data_template="{{data.orm_cve.cvssV2_severity}}",
+ hideable=True,
+ )
+
+ self.add_column(title="Publish Date",
+ static_data_name="orm_cve__publishedDate",
+ static_data_template="{{data.orm_cve.publishedDate}}",
+ hideable=True,
+ )
+
+ self.add_column(title="Last Modified Date",
+ static_data_name="orm_cve__lastModifiedDate",
+ static_data_template="{{data.orm_cve.lastModifiedDate}}",
+ hideable=True,
+ )
+
+
+class CveCheckerProductTable(ToasterTable):
+ """Table of All entries in CvecheckerRecord"""
+
+ def __init__(self, *args, **kwargs):
+ super(CveCheckerProductTable, self).__init__(*args, **kwargs)
+ self.default_orderby = "ck_product__name"
+
+ def get_context_data(self, **kwargs):
+ context = super(CveCheckerProductTable, self).get_context_data(**kwargs)
+ package_id = int(kwargs['package_id'])
+ context['Ck_Package'] = Ck_Package.objects.get(id=package_id)
+ context['mru'] = Job.get_recent()
+ context['mrj_type'] = 'all'
+ return context
+
+ def setup_queryset(self, *args, **kwargs):
+ package_id = int(kwargs['package_id'])
+ self.queryset = CkPackage2CkProduct.objects.filter(ck_package_id=package_id)
+ self.queryset = self.queryset.order_by(self.default_orderby)
+
+ def setup_filters(self, *args, **kwargs):
+ pass
+
+ def setup_columns(self, *args, **kwargs):
+
+ self.add_column(title="Product",
+ static_data_name="ck_product__name",
+ static_data_template="{{data.ck_product.name}}",
+ hideable=False,
+ orderable=True,
+ )
+
+ self.add_column(title="CvesInRecord",
+ static_data_name="cvesInRecord",
+ static_data_template="{{data.cvesInRecord}}",
+ hideable=False,
+ orderable=True,
+ )
+
+
+class CveCheckerImportManagementTable(ToasterTable):
+ """Table of Audit import meta-management """
+
+ def __init__(self, *args, **kwargs):
+ super(CveCheckerImportManagementTable, self).__init__(*args, **kwargs)
+ self.default_orderby = "order"
+
+ def get_context_data(self, **kwargs):
+ context = super(CveCheckerImportManagementTable, self).get_context_data(**kwargs)
+ return context
+
+ def setup_queryset(self, *args, **kwargs):
+ self.queryset = CkUploadManager.objects.all()
+ self.queryset = self.queryset.order_by(self.default_orderby)
+
+ def setup_filters(self, *args, **kwargs):
+ pass
+
+ def setup_columns(self, *args, **kwargs):
+
+ if UserSafe.is_admin(self.request.user):
+ self.add_column(title="ID",
+ field_name="id",
+ hideable=True,
+ hidden = True,
+ )
+
+ order_template = '''
+ <span id="audit_order-disp-{{data.id}}"><td><a href="{% url 'cvechecker_audit' data.id %}">{{data.order}}</a></td></span>
+ <span id="audit_order-edit-{{data.id}}" style="display:none;">
+ <input type="text" id="audit_order-text-{{data.id}}" value="{{data.order}}" size="10">
+ </span>
+ '''
+ self.add_column(title="Order",
+ static_data_name="order",
+ static_data_template=order_template,
+ orderable=True,
+ )
+
+ name_template = '''
+ <span id="audit_name-disp-{{data.id}}">{{data.name}}</span>
+ <span id="audit_name-edit-{{data.id}}" style="display:none;">
+ <input type="text" id="audit_name-text-{{data.id}}" value="{{data.name}}" size="20">
+ </span>
+ '''
+ self.add_column(title="Title",
+ static_data_name="name",
+ static_data_template=name_template,
+ )
+
+ mode_template = '''
+ <span id="audit_mode-disp-{{data.id}}">{{data.import_mode}}</span>
+ <span id="audit_mode-edit-{{data.id}}" style="display:none;">
+ <select id="audit_mode-text-{{data.id}}" name="audit_mode-text-{{data.id}}">
+ <option value="Repo" {% if "Repo" == data.import_mode %}selected{% endif %} >Repo</option>
+ <option value="SSL" {% if "SSL" == data.import_mode %}selected{% endif %} >SSL</option>
+ <option value="File" {% if "File" == data.import_mode %}selected{% endif %} >File</option>
+ </select>
+ </span>
+ '''
+ self.add_column(title="Mode",
+ static_data_name="import_mode",
+ static_data_template=mode_template,
+ )
+
+ repo_template = '''
+ <span id="audit_repo-disp-{{data.id}}">{{data.repo}}</span>
+ <span id="audit_repo-edit-{{data.id}}" style="display:none;">
+ <input type="text" id="audit_repo-text-{{data.id}}" value="{{data.repo}}" size="30">
+ </span>
+ '''
+ self.add_column(title="Repo URL",
+ static_data_name="repo",
+ static_data_template=repo_template,
+ )
+
+ path_template = '''
+ <span id="audit_path-disp-{{data.id}}">{{data.path}}</span>
+ <span id="audit_path-edit-{{data.id}}" style="display:none;">
+ <input type="text" id="audit_path-text-{{data.id}}" value="{{data.path}}" size="30">
+ </span>
+ '''
+ self.add_column(title="Path",
+ static_data_name="path",
+ static_data_template=path_template,
+ )
+
+ pem_template = '''
+ <span id="audit_pem-disp-{{data.id}}">{{data.pem}}</span>
+ <span id="audit_pem-edit-{{data.id}}" style="display:none;">
+ <input type="text" id="audit_pem-text-{{data.id}}" value="{{data.pem}}" size="20">
+ </span>
+ '''
+ self.add_column(title="Pem File",
+ static_data_name="pem_file",
+ static_data_template=pem_template,
+ )
+
+ branch_template = '''
+ <span id="audit_branch-disp-{{data.id}}">{{data.branch}}</span>
+ <span id="audit_branch-edit-{{data.id}}" style="display:none;">
+ <input type="text" id="audit_branch-text-{{data.id}}" value="{{data.branch}}" size="10">
+ </span>
+ '''
+ self.add_column(title="Branch",
+ static_data_name="branch",
+ static_data_template=branch_template,
+ )
+
+ if False:
+ refresh_template = '''
+ {% if "Upload" == data.name %}{% else %}
+ <span id="audit_refresh-disp-{{data.id}}">{{data.auto_refresh}}</span>
+ <span id="audit_refresh-edit-{{data.id}}" style="display:none;">
+ <select id="audit_refresh-text-{{data.id}}" name="audit_mode-text-{{data.id}}">
+ <option value="False" {% if False == data.auto_refresh %}selected{% endif %} >Absolute choice</option>
+ <option value="True" {% if True == data.auto_refresh %}selected{% endif %} >Automatic refresh choices</option>
+ </select>
+
+ </span>
+ {% endif %}
+ '''
+ self.add_column(title="Auto Refresh",
+ static_data_name="auto_refresh",
+ static_data_template=refresh_template,
+ )
+
+ self.add_column(title="Select Refresh",
+ field_name="select_refresh",
+ hideable=True,
+ hidden = True,
+ )
+ self.add_column(title="Select List",
+ field_name="select_list",
+ hideable=True,
+ hidden = True,
+ )
+
+ if UserSafe.is_contributor(self.request.user):
+ manage_link_template = '''
+ {% if "Upload" == data.name %}Built-in{% else %}
+ <span class="glyphicon glyphicon-edit edit-ck-entry" id="edit-entry-{{data.id}}" x-data="{{data.id}}"></span>
+ <span class="glyphicon glyphicon glyphicon glyphicon-ok save-ck-entry" id="save-entry-{{data.id}}" x-data="{{data.id}}" style="display:none;color: Chartreuse;"></span>
+ &nbsp;&nbsp;&nbsp;&nbsp;
+ <span class="glyphicon glyphicon glyphicon glyphicon-remove cancel-ck-entry" id="cancel-entry-{{data.id}}" x-data="{{data.id}}" style="display:none;color: Crimson;"></span>
+ &nbsp;&nbsp;&nbsp;&nbsp;
+ <span class="glyphicon glyphicon-trash trash-import" x-data="{{data.name}}|{{data.id}}"></span>
+ {% endif %}
+ '''
+ self.add_column(title="Manage",
+ hideable=True,
+ static_data_name="manage",
+ static_data_template=manage_link_template,
+ )
diff --git a/lib/cve_checker/templates/ck-audit-toastertable.html b/lib/cve_checker/templates/ck-audit-toastertable.html
new file mode 100755
index 00000000..a9d4d227
--- /dev/null
+++ b/lib/cve_checker/templates/ck-audit-toastertable.html
@@ -0,0 +1,223 @@
+{% extends 'base.html' %}
+{% load static %}
+
+{% block extraheadcontent %}
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.min.css' %}" type='text/css'>
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.structure.min.css' %}" type='text/css'>
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.theme.min.css' %}" type='text/css'>
+ <script src="{% static 'js/jquery-ui.min.js' %}">
+ </script>
+<style>
+.column1 {
+ float: left;
+ width: 40%;
+}
+.column2 {
+ float: left;
+ width: 60%;
+}
+/* Clear floats after the columns */
+.row:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+</style>
+{% endblock %}
+
+{% load jobtags %}
+
+{% block title %} CVE Check Packages {% endblock %}
+
+{% block pagecontent %}
+
+<div class="row">
+ <!-- Breadcrumbs -->
+ <div class="col-md-12">
+ <ul class="breadcrumb" id="breadcrumb">
+ <li><a href="{% url 'landing' %}">Home</a></li><span class="divider">&rarr;</span>
+ <li><a href="{% url 'cvechecker_audits' %}">Audits</a></li><span class="divider">&rarr;</span>
+ <li>Packages for "{{Ck_Audit.name}}"</li>
+ </ul>
+ </div>
+</div>
+
+<div class="row">
+ <div class="col-md-12">
+ {% with mru=mru mrj_type=mrj_type %}
+ {% include 'mrj_section.html' %}
+ {% endwith %}
+ </div>
+</div>
+
+<div class="row" style="margin-left:10px;" id="show-settings">
+ <div class="column1">
+ <h2> Audit Name: <b>"{{Ck_Audit.name}}"</b> </h2>
+ </div>
+ <div class="column2">
+ <h2> YP Release: <b>"{{Ck_Audit.orm_product.long_name}}"</b> </h2>
+ </div>
+</div>
+
+<!-- <p><b><big>Actions: </big></b> -->
+
+<p><b><big>Actions: </big></b>
+ <a class="btn btn-default navbar-btn " id="summary-report" >Summary report</a>
+ <a class="btn btn-default navbar-btn " id="vex-report" disabled>VEX</a>
+ <a class="btn btn-default navbar-btn " id="edit-cancel" style="display:none" >Cancel</a>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+ <a class="btn btn-default btn-info" disabled>Package View</a>
+ <a class="btn btn-default " href="{% url 'cvechecker_audit_cve' Ck_Audit.id %}">CVE View</a>
+
+<!-- Combination javascript plus redirected download for this ToasterTable (no-POST) page -->
+<form id="summary-report-form" action="/cve_checker/gen_download_cvechecker_summary/" enctype="multipart/form-data" method="post" >{% csrf_token %}
+ <input type="hidden" name="action" value="download">
+ <input type="hidden" name="options" value="" id="summary_report_options">
+ <input type="hidden" name="audit_id" value="{{Ck_Audit.id}}">
+ <button type="submit" form="summary-report-form" value="Submit2" style="display:none" id="download-summary-report">Generate the diff report</button>
+</form>
+
+<div id="summary-report-options" style="display:none;padding-left:25px;color:DarkCyan;">
+ <br>
+ <h4> Select the pages to be included in the report:</h4>
+ <input type="checkbox" id="audit-summary" name="audit-summary" value="base-severity" checked>
+ <label for="audit-summary">Audit summary</label><br>
+ <input type="checkbox" id="package-summary" name="package-summary" value="package-summary" checked>
+ <label for="package-summary">Package summary</label><br>
+ <input type="checkbox" id="unpatched-summary" name="unpatched-summary" value="unpatched-summary" checked>
+ <label for="unpatched-summary">Unpatched summary</label><br>
+ <input type="checkbox" id="unpatched-summary-compare" name="unpatched-summary-compare" value="unpatched-summary-compare">
+ <label for="unpatched-summary-compare">Unpatched summary with comparibles</label><br>
+</div>
+
+<div class="row">
+ <div class="col-md-12">
+ <div class="page-header">
+ <h1 class="top-air" data-role="page-title"></h1>
+ </div>
+
+ {% url '' as xhr_table_url %}
+ {% include 'toastertable.html' %}
+ </div>
+</div>
+
+<!-- Javascript support -->
+<script type="text/javascript">
+ $(document).ready(function () {
+ var selected_editsettings=false;
+ var selected_summary=false;
+
+ var tableElt = $("#{{table_name}}");
+ var titleElt = $("[data-role='page-title']");
+
+ tableElt.on("table-done", function (e, total, tableParams) {
+ var title = "Audit packages (" + total + ")";
+
+ if (tableParams.search || tableParams.filter) {
+ if (total === 0) {
+ title = "No packages found";
+ }
+ else if (total > 0) {
+ title = total + " Packages" + (total > 1 ? "s" : '') + " found";
+ }
+ }
+
+ titleElt.text(title);
+
+ $('.remove-repo-audit').click(function() {
+ var result = confirm("Are you sure you want to remove artifact '" + $(this).attr('x-data').split('|')[0] + "'?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'remove-artifact-audit',
+ "record_id" : $(this).attr('x-data').split('|')[1],
+ });
+ }
+ });
+
+ });
+
+ function onCommitAjaxSuccess(data, textstatus) {
+ //alert("AJAX RETURN");
+ $("#run-audit-analysis").removeAttr("disabled");
+ if (window.console && window.console.log) {
+ console.log("XHR returned:", data, "(" + textstatus + ")");
+ } else {
+ alert("NO CONSOLE:\n");
+ return;
+ }
+
+ if (data.error != "ok") {
+ alert("error on request:\n" + data.error);
+ return;
+ }
+ // reload the page with the updated tables
+// alert("PAGE REFRESH");
+ location.reload(true);
+ }
+
+ function onCommitAjaxError(jqXHR, textstatus, error) {
+ console.log("ERROR:"+error+"|"+textstatus);
+ alert("XHR errored1:\n" + error + "\n(" + textstatus + ")");
+ }
+
+ /* ensure cookie exists {% csrf_token %} */
+ function postCommitAjaxRequest(reqdata,url) {
+ reqdata["Ck_Audit_id"] = {{ Ck_Audit.id }};
+ url = url || "{% url 'xhr_cvechecker_commit' %}";
+ var ajax = $.ajax({
+ type:"POST",
+ data: reqdata,
+ url:url,
+ headers: { 'X-CSRFToken': $.cookie("csrftoken")},
+ success: onCommitAjaxSuccess,
+ error: onCommitAjaxError,
+ });
+ }
+
+ $('#summary-report').click(function() {
+ if (selected_summary) {
+ document.getElementById("summary-report").innerText = "Summary Report";
+ document.getElementById('vex-report').style.display = 'inline';
+ document.getElementById('edit-cancel').style.display = 'none';
+ $("#summary-report-options").slideUp();
+ selected_summary=false;
+ var options = "";
+ if (document.getElementById('audit-summary').checked) {
+ options = options + "audit-summary,";
+ }
+ if (document.getElementById('package-summary').checked) {
+ options = options + "package-summary,";
+ }
+ if (document.getElementById('unpatched-summary').checked) {
+ options = options + "unpatched-summary,";
+ }
+ if (document.getElementById('unpatched-summary-compare').checked) {
+ options = options + "unpatched-summary-compare,";
+ }
+ document.getElementById("summary_report_options").value=options;
+ document.getElementById("download-summary-report").click();
+ } else {
+ document.getElementById("summary-report").innerText = "Generate Summary Report";
+ document.getElementById("summary-report").style.display = 'inline';
+ document.getElementById('vex-report').style.display = 'none';
+ document.getElementById('edit-cancel').style.display = 'inline';
+ $("#summary-report-options").slideDown();
+ selected_summary=true;
+ }
+ });
+
+ $('#edit-cancel').click(function() {
+ document.getElementById("summary-report").innerText = "Summary Report";
+ document.getElementById('vex-report').style.display = 'inline';
+ document.getElementById('edit-cancel').style.display = 'none';
+ $("#summary-report-options").slideUp();
+ selected_summary=false;
+ });
+
+ $('.submit-downloadattachment').click(function() {
+ $("#downloadbanner-"+this.getAttribute("x-data")).submit();
+ });
+
+ });
+ </script>
+{% endblock %}
diff --git a/lib/cve_checker/templates/ck-auditcve-toastertable.html b/lib/cve_checker/templates/ck-auditcve-toastertable.html
new file mode 100755
index 00000000..a7648c8b
--- /dev/null
+++ b/lib/cve_checker/templates/ck-auditcve-toastertable.html
@@ -0,0 +1,431 @@
+{% extends 'base.html' %}
+{% load static %}
+
+{% block extraheadcontent %}
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.min.css' %}" type='text/css'>
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.structure.min.css' %}" type='text/css'>
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.theme.min.css' %}" type='text/css'>
+ <script src="{% static 'js/jquery-ui.min.js' %}">
+ </script>
+<style>
+.column1 {
+ float: left;
+ width: 40%;
+}
+.column2 {
+ float: left;
+ width: 60%;
+}
+/* Clear floats after the columns */
+.row:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+</style>
+{% endblock %}
+
+{% load jobtags %}
+
+{% block title %} CVE Check CVEs {% endblock %}
+
+{% block pagecontent %}
+
+<div class="row">
+ <!-- Breadcrumbs -->
+ <div class="col-md-12">
+ <ul class="breadcrumb" id="breadcrumb">
+ <li><a href="{% url 'landing' %}">Home</a></li><span class="divider">&rarr;</span>
+ <li><a href="{% url 'cvechecker_audits' %}">Audits</a></li><span class="divider">&rarr;</span>
+ <li>Packages for "{{Ck_Audit.name}}"</li>
+ </ul>
+ </div>
+</div>
+
+<div class="row">
+ <div class="col-md-12">
+ {% with mru=mru mrj_type=mrj_type %}
+ {% include 'mrj_section.html' %}
+ {% endwith %}
+ </div>
+</div>
+
+<div class="row" style="margin-left:10px;" id="show-settings">
+ <div class="column1">
+ <h2> Audit Name: <b>"{{Ck_Audit.name}}"</b> </h2>
+ </div>
+ <div class="column2">
+ <h2> YP Release: <b>"{{Ck_Audit.orm_product.long_name}}"</b> </h2>
+ </div>
+</div>
+
+<!-- <p><b><big>Actions: </big></b> -->
+
+<p><b><big>Actions: </big></b>
+ <!-- <a class="btn btn-default navbar-btn " id="summary-report" disabled>Summary report</a> -->
+ <a class="btn btn-default navbar-btn " id="edit-cancel" style="display:none" >Cancel</a>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+ <a class="btn btn-default" href="{% url 'cvechecker_audit' Ck_Audit.id %}">Package View</a>
+ <a class="btn btn-default btn-info" disabled>CVE View</a>
+
+<!-- Combination javascript plus redirected download for this ToasterTable (no-POST) page -->
+<form id="summary-report-form" action="/cve_check/gen_download_audit_summary/" enctype="multipart/form-data" method="post" >{% csrf_token %}
+ <input type="hidden" name="action" value="download">
+ <input type="hidden" name="options" value="" id="summary_report_options">
+ <input type="hidden" name="audit_id" value="{{Ck_Audit.id}}">
+ <button type="submit" form="summary-report-form" value="Submit2" style="display:none" id="download-summary-report">Generate the diff report</button>
+</form>
+
+<div id="summary-report-options" style="display:none;padding-left:25px;color:DarkCyan;">
+ <br>
+ <h4> Select the pages to be included in the report:</h4>
+ <input type="checkbox" id="repo-severity" name="repo-severity" value="repo-severity" checked>
+ <label for="repo-severity">Severity by repository</label><br>
+ <input type="checkbox" id="package-severity" name="package-severity" value="package-severity" checked>
+ <label for="package-severity">Severity by package</label><br>
+ <input type="checkbox" id="base-severity" name="base-severity" value="base-severity" checked>
+ <label for="base-severity">Severity by base image</label><br>
+ <input type="checkbox" id="cve-summary" name="cve-summary" value="cve-summary">
+ <label for="cve-summary">CVE summary</label><br>
+ <input type="checkbox" id="package-summary" name="package-summary" value="package-summary">
+ <label for="package-summary">Package summary</label><br>
+ <input type="checkbox" id="artifact-labels" name="artifact-labels" value="artifact-labels">
+ <label for="artifact-labels">Artifact Labels</label><br>
+ <input type="checkbox" id="summary_baseline" name="summary_baseline" value="summary_baseline">
+ <label for="summary_baseline">Audit versus Baseline by repository</label><br>
+</div>
+
+<div class="row">
+ <div class="col-md-12">
+ <div class="page-header">
+ <h1 class="top-air" data-role="page-title"></h1>
+ </div>
+
+ {% url '' as xhr_table_url %}
+ {% include 'toastertable.html' %}
+ </div>
+</div>
+
+<!-- Javascript support -->
+<script type="text/javascript">
+ $(document).ready(function () {
+ var selected_editsettings=false;
+ var selected_summary=false;
+
+ var tableElt = $("#{{table_name}}");
+ var titleElt = $("[data-role='page-title']");
+
+ tableElt.on("table-done", function (e, total, tableParams) {
+ var title = "Audit CVEs (" + total + ")";
+
+ if (tableParams.search || tableParams.filter) {
+ if (total === 0) {
+ title = "No CVEs found";
+ }
+ else if (total > 0) {
+ title = total + " CVE" + (total > 1 ? "s" : '') + " found";
+ }
+ }
+
+ titleElt.text(title);
+
+ $('.change-repo-type').change(function() {
+ var result = confirm("Are you sure you want to change to type '" + $(this).val().split('|')[1] + "'?");
+ postCommitAjaxRequest({
+ "action" : 'update-artifact-type',
+ "is_update": result,
+ "value" : $(this).val(),
+ });
+ });
+
+ $('.remove-repo-audit').click(function() {
+ var result = confirm("Are you sure you want to remove artifact '" + $(this).attr('x-data').split('|')[0] + "'?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'remove-artifact-audit',
+ "record_id" : $(this).attr('x-data').split('|')[1],
+ });
+ }
+ });
+
+ });
+
+ function onCommitAjaxSuccess(data, textstatus) {
+ //alert("AJAX RETURN");
+ $("#run-audit-analysis").removeAttr("disabled");
+ if (window.console && window.console.log) {
+ console.log("XHR returned:", data, "(" + textstatus + ")");
+ } else {
+ alert("NO CONSOLE:\n");
+ return;
+ }
+
+ if (data.error.startsWith('UPDATE_TYPE:')) {
+ var data = data.error.replace("UPDATE_TYPE:","");
+ var data_id=data.split('|')[0];
+ var data_value=data.replace('type_','');
+ $('#'+data_id).val(data_value);
+ $('#'+data_id).css({ "color": "blue" });
+ return;
+ }
+
+ if (data.error != "ok") {
+ alert("error on request:\n" + data.error);
+ return;
+ }
+ // reload the page with the updated tables
+// alert("PAGE REFRESH");
+ location.reload(true);
+ }
+
+ function onCommitAjaxError(jqXHR, textstatus, error) {
+ console.log("ERROR:"+error+"|"+textstatus);
+ alert("XHR errored1:\n" + error + "\n(" + textstatus + ")");
+ }
+
+ /* ensure cookie exists {% csrf_token %} */
+ function postCommitAjaxRequest(reqdata,url) {
+ reqdata["Ck_Audit_id"] = {{ Ck_Audit.id }};
+ url = url || "{% url 'xhr_cvechecker_commit' %}";
+ var ajax = $.ajax({
+ type:"POST",
+ data: reqdata,
+ url:url,
+ headers: { 'X-CSRFToken': $.cookie("csrftoken")},
+ success: onCommitAjaxSuccess,
+ error: onCommitAjaxError,
+ });
+ }
+
+ $('#analyze_artifacts').click(function(){
+ var result = confirm("The will analyze every CVE in this audit and will take some time. Proceed?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-analyze-artifacts',
+ "Ck_Audit_id" : '{{Ck_Audit.id}}',
+ },"");
+ }
+ });
+
+ $('#load_artifacts').click(function(){
+ var result = confirm("The will load all CVEs of registered artifacts, and will take some time. Proceed?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-load-artifacts',
+ "Ck_Audit_id" : '{{Ck_Audit.id}}',
+ },"");
+ }
+ });
+
+ $('#load_backfill').click(function(){
+ var result = confirm("Backfill missing vulnerabilities using selected audit?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-backfill-vulnerabilities',
+ "backfill_id" : $("#backfill_vulnerabilities").val(),
+ "Ck_Audit_id" : '{{Ck_Audit.id}}',
+ },"");
+ }
+ });
+
+
+ function setDefaultDisplay(is_default) {
+ var default_style = 'none';
+ if (is_default) {
+ default_style = 'inline';
+ {% if request.user.is_creator %}
+ document.getElementById("edit-settings").innerText = "Settings";
+ {% endif %}
+ document.getElementById("summary-report").innerText = "Summary Report";
+ selected_editsettings=false;
+ selected_summary=false;
+ } else {
+ document.getElementById('show-settings').style.display = 'none';
+ };
+ document.getElementById('browse-content').style.display = default_style;
+ {% if request.user.is_creator %}
+ document.getElementById("edit-settings").style.display = default_style;
+ {% endif %}
+ document.getElementById('load_artifacts').style.display = default_style;
+ document.getElementById('audit-import-tern').style.display = default_style;
+ document.getElementById('summary-report').style.display = default_style;
+ document.getElementById('cve-summary-report').style.display = default_style;
+ document.getElementById('prisma-merge-report').style.display = default_style;
+ document.getElementById('audit-package-versions').style.display = default_style;
+ document.getElementById('audit-artifacts').style.display = default_style;
+ /* Always pre-hide the pop-ups */
+ document.getElementById('summary-report-options').style.display = 'none';
+ {% if request.user.is_creator %}
+ document.getElementById('show-edit-settings').style.display = 'none';
+ {% endif %}
+ document.getElementById('edit-cancel').style.display = 'none';
+ if (is_default) {
+ $("#show-settings").slideDown();
+ };
+ };
+
+ {% if request.user.is_creator %}
+ $('#edit-settings').click(function() {
+ if (selected_editsettings) {
+ setDefaultDisplay(true);
+ postCommitAjaxRequest({
+ "action" : 'submit-editaudit',
+ "product_id" : $("#audit_product_id").val(),
+ "name" : $("#audit-name").val(),
+ "content" : $("#audit-content").val(),
+ "date" : $("#audit-date").val(),
+ "description" : $("#audit-desc").val(),
+ "tree_lock" : $("#tree_lock").val(),
+ "save_lock" : $("#save_lock").val(),
+ });
+ } else {
+ setDefaultDisplay(false);
+ document.getElementById("edit-settings").innerText = "Save Settings";
+ document.getElementById("edit-settings").style.display = 'inline';
+ document.getElementById('edit-cancel').style.display = 'inline';
+ $("#show-edit-settings").slideDown();
+ selected_editsettings=true;
+ };
+ });
+ {% endif %}
+
+ $('#summary-report').click(function() {
+ if (selected_summary) {
+ setDefaultDisplay(true);
+ selected_summary=false;
+ var options = "";
+ if (document.getElementById('repo-severity').checked) {
+ options = options + "repo-severity,";
+ }
+ if (document.getElementById('package-severity').checked) {
+ options = options + "package-severity,";
+ }
+ if (document.getElementById('base-severity').checked) {
+ options = options + "base-severity,";
+ }
+ if (document.getElementById('cve-summary').checked) {
+ options = options + "cve-summary,";
+ }
+ if (document.getElementById('package-summary').checked) {
+ options = options + "package-summary,";
+ }
+ if (document.getElementById('artifact-labels').checked) {
+ options = options + "artifact-labels,";
+ }
+ if (document.getElementById('summary_baseline').checked) {
+ options = options + "summary_baseline,";
+ }
+ document.getElementById("summary_report_options").value=options;
+ document.getElementById("download-summary-report").click();
+ } else {
+ document.getElementById("summary-report").innerText = "Generate Summary Report";
+ setDefaultDisplay(false);
+ document.getElementById("summary-report").style.display = 'inline';
+ document.getElementById('edit-cancel').style.display = 'inline';
+ $("#summary-report-options").slideDown();
+ selected_summary=true;
+ }
+ });
+
+ $('#edit-cancel').click(function() {
+ setDefaultDisplay(true);
+ });
+
+ $('#audit-import-tern').click(function() {
+ postCommitAjaxRequest({
+ "action" : 'submit-import-tern',
+ },"");
+ });
+
+ $('.submit-downloadattachment').click(function() {
+ $("#downloadbanner-"+this.getAttribute("x-data")).submit();
+ });
+
+ $('#audit-package-versions').click(function() {
+ document.getElementById("download-package-versions").click();
+ });
+
+ $('#audit-artifacts').click(function() {
+ document.getElementById("download-artifacts-summary").click();
+ });
+
+ $('#x_summary-report').click(function() {
+ document.getElementById("download-summary-report").click();
+ });
+
+ $('#submit-refresh-tops').click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-refresh-tops',
+ "Ck_Audit_id" : '{{Ck_Audit.id}}',
+ },"");
+ });
+
+ $('#submit-grafana-add').click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-grafana-add',
+ "Ck_Audit_id" : '{{Ck_Audit.id}}',
+ },"");
+ });
+
+ $('#submit-grafana-remove').click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-grafana-remove',
+ "Ck_Audit_id" : '{{Ck_Audit.id}}',
+ },"");
+ });
+
+ $('#submit-ingest-update').click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-ingest-update',
+ "Ck_Audit_id" : '{{Ck_Audit.id}}',
+ },"");
+ });
+
+
+ $('#submit-newowner').click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-audit-addowner',
+ "owner_id" : $("#user-list").val(),
+ });
+ });
+
+ $('#submit-newgroup').click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-audit-addgroup',
+ "group_id" : $("#group-list").val(),
+ });
+ });
+
+ $('.detach-owner').click(function(){
+ var result = confirm("Are you sure?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-audit-detachowner',
+ "owner_id" : $(this).attr('x-data'),
+ });
+ }
+ });
+
+ $('.detach-group').click(function(){
+ var result = confirm("Are you sure?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-audit-detachgroup',
+ "group_id" : $(this).attr('x-data'),
+ });
+ }
+ });
+
+ $('#focus_select').on('change', function() {
+ postCommitAjaxRequest({
+ "action" : 'submit-audit-focus',
+ "focus_select" : $("#focus_select").val(),
+ });
+ });
+
+ // Turn on the default controls
+ setDefaultDisplay(true);
+
+ });
+ </script>
+{% endblock %}
diff --git a/lib/cve_checker/templates/ck-audits-toastertable.html b/lib/cve_checker/templates/ck-audits-toastertable.html
new file mode 100755
index 00000000..f9a5c0e4
--- /dev/null
+++ b/lib/cve_checker/templates/ck-audits-toastertable.html
@@ -0,0 +1,425 @@
+{% extends 'base.html' %}
+{% load static %}
+
+{% block extraheadcontent %}
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.min.css' %}" type='text/css'>
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.structure.min.css' %}" type='text/css'>
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.theme.min.css' %}" type='text/css'>
+ <script src="{% static 'js/jquery-ui.min.js' %}"></script>
+ <script>
+ // Update product (per import's select string)
+ function reset_product(description) {
+ var version;
+ var product_elem_default;
+ var product_elem_matched = "0";
+ // This selector is used to see the x-data and y-data attributes
+ var products = document.querySelectorAll('[id^=product_id]');
+ for (var i in products) {
+ // Only include integer iterated indexes (hack due to selector extra data)
+ if (!isNaN(i)) {
+ product = products[i];
+ if ("default" == product.getAttribute('z-data')) {
+ product_elem_default = product.getAttribute('value');
+ }
+ version = product.getAttribute('y-data');
+ if (description.toUpperCase().includes(version.toUpperCase())) {
+ //alert("MATCH:"+ version + ":" + description + ":")
+ product_elem_matched = product.getAttribute('value');
+ }
+ }
+ }
+ // Update product select
+ const $select = document.querySelector('#audit_product_id');
+ if (product_elem_matched != "0") {
+ $select.value = product_elem_matched;
+ } else {
+ $select.value = product_elem_default;
+ }
+ }
+
+ // Use the select as the audit name extension
+ function new_import_set() {
+ var import_value = document.querySelector('input[name="content"]:checked').value;
+ var import_select = $("#"+import_value+"_list").val();
+ var audit_name = '';
+ if ("import_upload" == import_value) {
+ audit_name = 'download';
+ } else {
+ audit_name = import_select;
+ };
+ $("#audit-name").val("{{new_audit_name}}_" + audit_name);
+ reset_product(audit_name);
+ }
+ </script>
+{% endblock %}
+
+{% block title %} CVE Checker Audits {% endblock %}
+
+{% block pagecontent %}
+
+<div class="row">
+ <!-- Breadcrumbs -->
+ <div class="col-md-12">
+ <ul class="breadcrumb" id="breadcrumb">
+ <li><a href="{% url 'landing' %}"/>Home</a></li><span class="divider">&rarr;</span>
+ <li>Audits</li>
+ </ul>
+ </div>
+</div>
+
+<div class="row">
+ <div class="col-md-12">
+ {% with mru=mru mrj_type=mrj_type %}
+ {% include 'mrj_section.html' %}
+ {% endwith %}
+ </div>
+</div>
+
+<div class="container-fluid">
+ <h3><span>
+ Actions:
+ {% if request.user.is_creator %}
+ <a class="btn btn-default navbar-btn " id="new-audit-add" >Add an audit</a>
+ {% endif %}
+ <a class="btn btn-default navbar-btn " id="audit-diff" disabled>Audit Diff</a>
+ <a class="btn btn-default navbar-btn " id="new-audit-cancel" style="display:none" >Cancel</a>
+ {% if request.user.is_admin %}
+ &nbsp;&nbsp;&nbsp;&nbsp;
+ <!--<button id="purge-selected" class="btn btn-default" type="button">Purge selected</button> -->
+ {% endif %}
+ </span>
+ </h3>
+</div>
+
+<div id="show-new-audit" style="display:none;padding-left:25px;color:DarkCyan;">
+ <div style="margin-top: 10px;">
+ <label>Name:</label>
+ <input type="text" placeholder="name of audit" id="audit-name" size="50" value="{{new_audit_name}}_master">
+ </div>
+
+ <div>
+ <label style="margin-top: 10px;">Product:</label>
+ <select id="audit_product_id" name="audit_product_id">
+ {% for product in orm_products %}
+ {% with product.id as product_id %}
+ <option id="product_id_{{forloop.counter}}" value="{{product.id}}" x-data="{{product.name}}" y-data="{{product.version}}" z-data="{% if product.long_name == default_product %}default{% endif %}" {% if product.long_name == default_product %}selected{% endif %} >{{product.long_name}}</option>
+ {% endwith %}
+ {% endfor %}
+ </select>
+ <br>
+ </div>
+
+ <div style="margin-top: 10px;">
+ <label>Import:</label>
+ <br>
+ {% for import in ckuploadmanager %}
+ <div style="padding-left: 25px;">
+ {% if "Upload" == import.name %}
+ <input type="radio" name="content" value="import_upload" onclick="new_import_set()" checked>
+ <label for="import_{{import.id}}">&nbsp;&nbsp;{{import.name}}</label>
+
+ <form id="uploadbanner" enctype="multipart/form-data" method="post" action="{% url 'gen_upload_cvechecker' %}">{% csrf_token %}
+ <input id="fileUpload" name="fileUpload" type="file" />
+ <input type="hidden" id="action" name="action" value="upload" >
+ <input type="hidden" id="upload_product_id" name="orm_product_id" value="" >
+ <input type="hidden" id="upload_audit_name" name="audit_name" value="" >
+ <br>
+ <input type="submit" value="submit file" id="submit-upload-ck" />
+ </form>
+
+ {% else %}
+ <input type="radio" name="content" value="import_{{import.id}}" onclick="new_import_set()" ><label for="import_{{import.id}}">&nbsp;&nbsp;{{import.name}}</label>
+ {% if import.is_select_list %}
+ <select id="import_{{import.id}}_list" name="import_{{import.id}}_list" onclick="new_import_set()">
+ {% for item in import.get_select_list %}
+ <option value="{{item}}">{{item}}</option>
+ {% endfor %}
+ </select>
+ {% elif import.path %}
+ ({{import.get_path_filename}})
+ {% endif %}
+ {% endif %}
+ </div>
+ {% endfor %}
+
+ </div>
+</div>
+
+<!-- Combination javascript plus redirected download for this ToasterTable (no-POST) page -->
+<form id="audit-diff-form" action="/wr_studio/gen_download_audit_diff/" enctype="multipart/form-data" method="post" >{% csrf_token %}
+ <input type="hidden" id="action" name="action" value="download">
+ <input type="hidden" id="form_audit_1_id" name="audit_1_id" value="0">
+ <input type="hidden" id="form_audit_2_id" name="audit_2_id" value="0">
+ <input type="hidden" id="form_audit_scope_id" name="audit_scope" value="0">
+ <input type="hidden" id="form_audit_sort_id" name="audit_sort" value="0">
+ <button type="submit" form="audit-diff-form" value="Submit" style="display:none" id="download-audit-diff">Generate the diff report</button>
+</form>
+
+<div id="show-diff-audit" style="display:none;padding-left:25px;">
+ <span id="inherit-audit" style="color:DarkCyan;">Audit #1:
+ <span class="glyphicon glyphicon-question-sign get-help" title="Audit #1 for difference"></span>
+ <br>
+ <select id="audit_1_id" >
+ {% for audit in audits %}
+ {% with audit.id as audit_id %}
+ <option value="{{audit.id}}" {%if forloop.counter == 2%}selected{% endif %}>{{audit.id}}: {{audit.name}}</option>
+ {% endwith %}
+ {% endfor %}
+ </select></span>
+ <br>
+ <span id="inherit-audit" style="color:DarkCyan;">Audit #2:</span>
+ <span class="glyphicon glyphicon-question-sign get-help" title="Audit #2 for difference"></span>
+ <br>
+ <select id="audit_2_id" >
+ {% for audit in audits %}
+ {% with audit.id as audit_id %}
+ <option value="{{audit.id}}">{{audit.id}}: {{audit.name}}</option>
+ {% endwith %}
+ {% endfor %}
+ </select>
+ <br>
+ <br>Report Scope:&nbsp;&nbsp;
+ <select id="audit_diff_scope">
+ <option value="0" >Criticals Diff Report</option>
+ <option value="1" >Full Diff Report</option>
+ <option value="2" >Cross-product Diff Report</option>
+ </select>
+ <br><br>
+ <input type="checkbox" id="audit-diff-order" name="audit-diff-order" value="1" checked></input>
+ <label for="audit-diff-order">Auto-sort audit order</label>
+ <hr>
+</div>
+
+<div class="row">
+ <div class="col-md-12">
+ <div class="page-header">
+ <h1 class="top-air" data-role="page-title"></h1>
+ </div>
+
+ {# xhr_table_url is just the current url so leave it blank #}
+ {% url '' as xhr_table_url %}
+ {% include 'toastertable.html' %}
+ </div>
+</div>
+
+ <script type="text/javascript">
+ selected_addaudit=false;
+ selected_diffaudit=false;
+
+ $(document).ready(function () {
+
+ // Hide the upload submit button and use our own
+ document.getElementById('submit-upload-ck').style.visibility = 'hidden';
+
+ var tableElt = $("#{{table_name}}");
+ var titleElt = $("[data-role='page-title']");
+
+ tableElt.on("table-done", function (e, total, tableParams) {
+ var title = "All CVE Check Audits (" + total + ")";
+
+ if (tableParams.search || tableParams.filter) {
+ if (total === 0) {
+ title = "No CVE Check Audits found";
+ }
+ else if (total > 0) {
+ title = total + " CVE Check Audit" + (total > 1 ? 's' : '') + " found";
+ }
+ }
+
+ $('.edit-ck-entry').click(function() {
+ const ck_id=$(this).attr('x-data');
+ document.getElementById('audit_name-disp-'+ck_id).style.display = 'none';
+ document.getElementById('audit_name-edit-'+ck_id).style.display = 'inline';
+ document.getElementById('edit-entry-'+ck_id).style.display = 'none';
+ document.getElementById('save-entry-'+ck_id).style.display = 'inline';
+ document.getElementById('cancel-entry-'+ck_id).style.display = 'inline';
+ });
+ $('.save-ck-entry').click(function() {
+ const ck_id=$(this).attr('x-data');
+ document.getElementById('audit_name-disp-'+ck_id).style.display = 'inline';
+ document.getElementById('audit_name-edit-'+ck_id).style.display = 'none';
+ document.getElementById('edit-entry-'+ck_id).style.display = 'inline';
+ document.getElementById('save-entry-'+ck_id).style.display = 'none';
+ document.getElementById('cancel-entry-'+ck_id).style.display = 'none';
+
+ postCommitAjaxRequest({
+ "action" : 'submit-update-ck',
+ "ck_id" : ck_id,
+ "audit_name" : $("#audit_name-text-"+ck_id).val(),
+ });
+ });
+
+ $('.cancel-ck-entry').click(function() {
+ const ck_id=$(this).attr('x-data');
+ document.getElementById('audit_name-disp-'+ck_id).style.display = 'inline';
+ document.getElementById('audit_name-edit-'+ck_id).style.display = 'none';
+ document.getElementById('edit-entry-'+ck_id).style.display = 'inline';
+ document.getElementById('save-entry-'+ck_id).style.display = 'none';
+ document.getElementById('cancel-entry-'+ck_id).style.display = 'none';
+ });
+
+ /* Add handler into the Toaster Table context */
+ $('.trash-audit').click(function() {
+ var result = confirm("Are you sure you want to remove '" + $(this).attr('x-data').split('|')[0] + "'?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-trash-audit',
+ "record_id" : $(this).attr('x-data').split('|')[1],
+ });
+ }
+ });
+
+ titleElt.text(title);
+ });
+
+
+ function onCommitAjaxSuccess(data, textstatus) {
+ if (window.console && window.console.log) {
+ console.log("XHR returned:", data, "(" + textstatus + ")");
+ } else {
+ alert("NO CONSOLE:\n");
+ return;
+ }
+
+ if (data.error == "refresh_new") {
+ window.location.replace("{% url 'cvechecker_audits' %}");
+ return;
+ }
+
+ if (data.error != "ok") {
+ alert("error on request:\n" + data.error);
+ return;
+ }
+ // reload the page with the updated tables
+ location.reload(true);
+ }
+
+ function onCommitAjaxError(jqXHR, textstatus, error) {
+ console.log("ERROR:"+error+"|"+textstatus);
+ alert("XHR errored1:\n" + error + "\n(" + textstatus + ")");
+ }
+
+ /* ensure cookie exists {% csrf_token %} */
+ function postCommitAjaxRequest(reqdata,url="") {
+ if ("" == url) {
+ url = "{% url 'xhr_cvechecker_commit' %}";
+ };
+ var ajax = $.ajax({
+ type:"POST",
+ data: reqdata,
+ url: url,
+ headers: { 'X-CSRFToken': $.cookie("csrftoken")},
+ success: onCommitAjaxSuccess,
+ error: onCommitAjaxError,
+ });
+ }
+
+ $('#new-audit-add').click(function(e) {
+ if (selected_addaudit) {
+ document.getElementById("new-audit-add").innerText = "Add an audit";
+ $("#show-new-audit").slideUp();
+ selected_addaudit=false;
+ document.getElementById('new-audit-cancel').style.display = 'none';
+ document.getElementById('audit-diff').style.display = 'inline';
+
+ audit_name = $("#audit-name").val().trim();;
+ if ( "" == audit_name) {
+ alert("Error: an audit name is required");
+ return;
+ }
+
+ import_value = document.querySelector('input[name="content"]:checked').value;
+ import_select = $("#"+import_value+"_list").val();
+ if ("import_upload" == import_value) {
+ var $select = document.querySelector('#upload_product_id');
+ $select.value = $("#audit_product_id").val();
+ $select = document.querySelector('#upload_audit_name');
+ $select.value = $("#audit-name").val();
+ // Click the submit for the upload form
+ document.getElementById("submit-upload-ck").click();
+ } else {
+ postCommitAjaxRequest({
+ "action" : 'submit-createaudit',
+ "name" : $("#audit-name").val(),
+ "product_id" : $("#audit_product_id").val(),
+ "import_id" : import_value.replace("import_",""),
+ "import_select" : import_select,
+ "is-shift" : e.shiftKey,
+ });
+ }
+ } else {
+ document.getElementById("new-audit-add").innerText = "Create this Audit";
+ document.getElementById('audit-diff').style.display = 'none';
+ document.getElementById('new-audit-cancel').style.display = 'inline';
+ $("#show-new-audit").slideDown();
+ selected_addaudit=true;
+ }
+ });
+
+ $('#new-audit-cancel').click(function() {
+ document.getElementById("new-audit-add").innerText = "Add an Audit";
+ document.getElementById('audit-diff').style.display = 'inline';
+ document.getElementById('new-audit-add').style.display = 'inline';
+ document.getElementById('new-audit-cancel').style.display = 'none';
+ $("#show-new-audit").slideUp();
+ $("#show-diff-audit").slideUp();
+ selected_addaudit=false;
+ selected_diffaudit=false;
+ });
+
+ $('#audit-diff').click(function() {
+ if (selected_diffaudit) {
+ selected_diffaudit=false;
+ document.getElementById("audit-diff").innerText = "Audit Diff";
+ document.getElementById('new-audit-add').style.display = 'inline';
+ document.getElementById('new-audit-cancel').style.display = 'none';
+ $("#show-diff-audit").slideUp();
+ /* Trigger the computation and auto download */
+ audit_1_id = $("#audit_1_id").val();
+ audit_2_id = $("#audit_2_id").val();
+ if (audit_1_id == audit_2_id) {
+ alert("You have selected the same two audits for the difference.");
+ return
+ };
+ $("#form_audit_1_id").val(audit_1_id);
+ $("#form_audit_2_id").val(audit_2_id);
+ $("#form_audit_scope_id").val($("#audit_diff_scope").val());
+ if (document.getElementById('audit-diff-order').checked) {
+ $("#form_audit_sort_id").val('1');
+ } else {
+ $("#form_audit_sort_id").val('0');
+ }
+ document.getElementById("download-audit-diff").click();
+ } else {
+ document.getElementById("audit-diff").innerText = "Generate diff report";
+ document.getElementById('new-audit-cancel').style.display = 'inline';
+ document.getElementById('new-audit-add').style.display = 'none';
+ $("#show-diff-audit").slideDown();
+ selected_diffaudit=true;
+ }
+ });
+
+ $('#purge-selected').click(function(){
+ var audit_list=[];
+ $('#harborauditstable input').each(function(){
+ if ($(this).is(':checked')) {
+ audit_list.push($(this).prop('id'));
+ }
+ });
+ if (0 == audit_list.length) {
+ alert("No Audits were selected");
+ return;
+ }
+ var result = confirm("Are you sure you want to purge these " + audit_list.length + " audits (~9 secs/audit)?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-purge-audits',
+ "audit_list" : audit_list.join(","),
+ });
+ }
+ });
+
+ });
+
+ </script>
+{% endblock %}
diff --git a/lib/cve_checker/templates/ck-import_manager-toastertable.html b/lib/cve_checker/templates/ck-import_manager-toastertable.html
new file mode 100755
index 00000000..5661e732
--- /dev/null
+++ b/lib/cve_checker/templates/ck-import_manager-toastertable.html
@@ -0,0 +1,266 @@
+{% extends 'base.html' %}
+{% load static %}
+
+{% block extraheadcontent %}
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.min.css' %}" type='text/css'>
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.structure.min.css' %}" type='text/css'>
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.theme.min.css' %}" type='text/css'>
+ <script src="{% static 'js/jquery-ui.min.js' %}">
+ </script>
+<style>
+.column1 {
+ float: left;
+ width: 30%;
+}
+.column2 {
+ float: left;
+ width: 60%;
+}
+/* Clear floats after the columns */
+.row:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+</style>
+{% endblock %}
+
+{% load jobtags %}
+
+{% block title %} Cve Check Import Manager {% endblock %}
+
+{% block pagecontent %}
+
+<div class="row">
+ <!-- Breadcrumbs -->
+ <div class="col-md-12">
+ <ul class="breadcrumb" id="breadcrumb">
+ <li><a href="{% url 'landing' %}">Home</a></li><span class="divider">&rarr;</span>
+ <li><a href="{% url 'manage' %}">Management</a></li><span class="divider">&rarr;</span>
+ <li>Import Manager"</li>
+ </ul>
+ </div>
+</div>
+
+<p><b><big>Actions: </big></b>
+ <a class="btn btn-default navbar-btn " id="new-import" >New Import</a>
+ <a class="btn btn-default navbar-btn " id="refresh-imports" >Refresh select lists</a>
+ &nbsp;&nbsp;&nbsp;&nbsp;
+ <a class="btn btn-default navbar-btn " id="summary-report" disabled>Summary report</a>
+ &nbsp;&nbsp;&nbsp;&nbsp;
+ <a class="btn btn-default navbar-btn " id="show-help" >Help</a>
+
+<div id="import_help" class="well" style="padding-left:25px;display:none;">
+ <h4>Using import management:</h4>
+ This page is used to drive the table of import sources for CVE Checker audits.<br>
+ <ol>
+ <li> If the import points to a file, it will be used. </li>
+ <li> If the import points to a directory of files, they will be offered in a select list</li>
+ <li> If the import points to a directory of directories, they will be offered in a select list, and the child file(s) will be imported</li>
+ </ol>
+ Fields:<br>
+ <div style="padding-left: 30px;">
+ <b>Title:</b> Displayed title for the import channel<br>
+ <b>Mode:</b> "Repo" is for repositories, "SSL" is for scp, "File" is for direct local or NFS<br>
+ <b>Repo URL:</b> The URL to use to clone git repositories<br>
+ <b>Path:</b><br>
+ </div>
+ <div style="padding-left: 50px;">
+ <b>Repo:</b> Relative path to the target directory or file within the repo tree<br>
+ <b>SSL:</b> The user@ip:/path" to the remote target directory/file<br>
+ <b>File:</b> Absolute path to the local target directory/file<br>
+ </div>
+ <div style="padding-left: 30px;">
+ <b>Pem file:</b> Permissions file for SSH/scp access to the target directory/file<br>
+ <b>Branch:</b> optional branch for the git repo<br>
+ <b>Select List:</b> Extracted directories/files for the respective import's select list<br>
+ </div>
+</div>
+
+<div class="row">
+ <div class="col-md-12">
+ <div class="page-header">
+ <h1 class="top-air" data-role="page-title"></h1>
+ </div>
+
+ {% url '' as xhr_table_url %}
+ {% include 'toastertable.html' %}
+ </div>
+</div>
+
+<!-- Javascript support -->
+<script type="text/javascript">
+ $(document).ready(function () {
+ var selected_editsettings=false;
+ var selected_summary=false;
+ var selected_showhelp=false;
+
+ var tableElt = $("#{{table_name}}");
+ var titleElt = $("[data-role='page-title']");
+
+ tableElt.on("table-done", function (e, total, tableParams) {
+ var title = "Import Manager (" + total + ")";
+
+ if (tableParams.search || tableParams.filter) {
+ if (total === 0) {
+ title = "No Import Managers found";
+ }
+ else if (total > 0) {
+ title = total + " Import Manager" + (total > 1 ? "s" : '') + " found";
+ }
+ }
+
+ $('.edit-ck-entry').click(function() {
+ const ck_id=$(this).attr('x-data');
+ document.getElementById('audit_order-disp-'+ck_id).style.display = 'none';
+ document.getElementById('audit_order-edit-'+ck_id).style.display = 'inline';
+ document.getElementById('audit_name-disp-'+ck_id).style.display = 'none';
+ document.getElementById('audit_name-edit-'+ck_id).style.display = 'inline';
+ document.getElementById('audit_mode-disp-'+ck_id).style.display = 'none';
+ document.getElementById('audit_mode-edit-'+ck_id).style.display = 'inline';
+ document.getElementById('audit_path-disp-'+ck_id).style.display = 'none';
+ document.getElementById('audit_path-edit-'+ck_id).style.display = 'inline';
+ document.getElementById('audit_pem-disp-'+ck_id).style.display = 'none';
+ document.getElementById('audit_pem-edit-'+ck_id).style.display = 'inline';
+ document.getElementById('audit_repo-disp-'+ck_id).style.display = 'none';
+ document.getElementById('audit_repo-edit-'+ck_id).style.display = 'inline';
+ document.getElementById('audit_branch-disp-'+ck_id).style.display = 'none';
+ document.getElementById('audit_branch-edit-'+ck_id).style.display = 'inline';
+ //document.getElementById('audit_refresh-disp-'+ck_id).style.display = 'none';
+ //document.getElementById('audit_refresh-edit-'+ck_id).style.display = 'inline';
+
+ document.getElementById('edit-entry-'+ck_id).style.display = 'none';
+ document.getElementById('save-entry-'+ck_id).style.display = 'inline';
+ document.getElementById('cancel-entry-'+ck_id).style.display = 'inline';
+ });
+
+ function close_ck_edit(ck_id) {
+ document.getElementById('audit_order-disp-'+ck_id).style.display = 'inline';
+ document.getElementById('audit_order-edit-'+ck_id).style.display = 'none';
+ document.getElementById('audit_name-disp-'+ck_id).style.display = 'inline';
+ document.getElementById('audit_name-edit-'+ck_id).style.display = 'none';
+ document.getElementById('audit_mode-disp-'+ck_id).style.display = 'inline';
+ document.getElementById('audit_mode-edit-'+ck_id).style.display = 'none';
+ document.getElementById('audit_path-disp-'+ck_id).style.display = 'inline';
+ document.getElementById('audit_path-edit-'+ck_id).style.display = 'none';
+ document.getElementById('audit_pem-disp-'+ck_id).style.display = 'inline';
+ document.getElementById('audit_pem-edit-'+ck_id).style.display = 'none';
+ document.getElementById('audit_repo-disp-'+ck_id).style.display = 'inline';
+ document.getElementById('audit_repo-edit-'+ck_id).style.display = 'none';
+ document.getElementById('audit_branch-disp-'+ck_id).style.display = 'inline';
+ document.getElementById('audit_branch-edit-'+ck_id).style.display = 'none';
+ //document.getElementById('audit_refresh-disp-'+ck_id).style.display = 'inline';
+ //document.getElementById('audit_refresh-edit-'+ck_id).style.display = 'none';
+
+ document.getElementById('edit-entry-'+ck_id).style.display = 'inline';
+ document.getElementById('save-entry-'+ck_id).style.display = 'none';
+ document.getElementById('cancel-entry-'+ck_id).style.display = 'none';
+ };
+
+ $('.cancel-ck-entry').click(function() {
+ const ck_id=$(this).attr('x-data');
+ close_ck_edit(ck_id);
+ });
+
+ $('.save-ck-entry').click(function() {
+ const ck_id=$(this).attr('x-data');
+ close_ck_edit(ck_id);
+ postCommitAjaxRequest({
+ "action" : 'submit-update-import-ck',
+ "ck_id" : ck_id,
+ "audit_order" : $("#audit_order-text-"+ck_id).val(),
+ "audit_name" : $("#audit_name-text-"+ck_id).val(),
+ "audit_mode" : $("#audit_mode-text-"+ck_id).val(),
+ "audit_path" : $("#audit_path-text-"+ck_id).val(),
+ "audit_pem" : $("#audit_pem-text-"+ck_id).val(),
+ "audit_repo" : $("#audit_repo-text-"+ck_id).val(),
+ "audit_branch" : $("#audit_branch-text-"+ck_id).val(),
+ });
+ });
+
+ $('.trash-import').click(function() {
+ var result = confirm("Are you sure you want to remove import '" + $(this).attr('x-data').split('|')[0] + "'?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-remove-import-ck',
+ "record_id" : $(this).attr('x-data').split('|')[1],
+ });
+ }
+ });
+
+ titleElt.text(title);
+ });
+
+ function onCommitAjaxSuccess(data, textstatus) {
+ //alert("AJAX RETURN");
+ $("#run-audit-analysis").removeAttr("disabled");
+ if (window.console && window.console.log) {
+ console.log("XHR returned:", data, "(" + textstatus + ")");
+ } else {
+ alert("NO CONSOLE:\n");
+ return;
+ }
+
+ if (data.error.startsWith('UPDATE_TYPE:')) {
+ var data = data.error.replace("UPDATE_TYPE:","");
+ var data_id=data.split('|')[0];
+ var data_value=data.replace('type_','');
+ $('#'+data_id).val(data_value);
+ $('#'+data_id).css({ "color": "blue" });
+ return;
+ }
+
+ if (data.error != "ok") {
+ alert("error on request:\n" + data.error);
+ return;
+ }
+
+ // reload the page with the updated tables
+ location.reload(true);
+ }
+
+ function onCommitAjaxError(jqXHR, textstatus, error) {
+ console.log("ERROR:"+error+"|"+textstatus);
+ alert("XHR errored1:\n" + error + "\n(" + textstatus + ")");
+ }
+
+ /* ensure cookie exists {% csrf_token %} */
+ function postCommitAjaxRequest(reqdata,url) {
+ url = url || "{% url 'xhr_cvechecker_commit' %}";
+ var ajax = $.ajax({
+ type:"POST",
+ data: reqdata,
+ url:url,
+ headers: { 'X-CSRFToken': $.cookie("csrftoken")},
+ success: onCommitAjaxSuccess,
+ error: onCommitAjaxError,
+ });
+ }
+
+ $('#new-import').click(function() {
+ postCommitAjaxRequest({
+ "action" : 'submit-new-import-ck',
+ });
+ });
+
+ $('#refresh-imports').click(function() {
+ postCommitAjaxRequest({
+ "action" : 'submit-import-refresh',
+ });
+ });
+
+ $('#show-help').click(function() {
+ if (selected_showhelp) {
+ document.getElementById("show-help").innerText = "Help";
+ $("#import_help").slideUp();
+ selected_showhelp = false;
+ } else {
+ document.getElementById("show-help").innerText = "Close Help";
+ $("#import_help").slideDown();
+ selected_showhelp = true;
+ }
+ });
+
+ });
+ </script>
+{% endblock %}
diff --git a/lib/cve_checker/templates/ck-issue-toastertable.html b/lib/cve_checker/templates/ck-issue-toastertable.html
new file mode 100755
index 00000000..4768d21e
--- /dev/null
+++ b/lib/cve_checker/templates/ck-issue-toastertable.html
@@ -0,0 +1,347 @@
+{% extends 'base.html' %}
+{% load static %}
+
+{% block extraheadcontent %}
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.min.css' %}" type='text/css'>
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.structure.min.css' %}" type='text/css'>
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.theme.min.css' %}" type='text/css'>
+ <script src="{% static 'js/jquery-ui.min.js' %}">
+ </script>
+<style>
+.column1 {
+ float: left;
+ width: 30%;
+}
+.column2 {
+ float: left;
+ width: 60%;
+}
+/* Clear floats after the columns */
+.row:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+</style>
+{% endblock %}
+
+{% load jobtags %}
+
+{% block title %} Cve Checker Package Issues {% endblock %}
+
+{% block pagecontent %}
+
+<div class="row">
+ <!-- Breadcrumbs -->
+ <div class="col-md-12">
+ <ul class="breadcrumb" id="breadcrumb">
+ <li><a href="{% url 'landing' %}">Home</a></li><span class="divider">&rarr;</span>
+ <li><a href="{% url 'cvechecker_audits' %}">Audits</a></li><span class="divider">&rarr;</span>
+ <li><a href="{% url 'cvechecker_audit' Ck_Package.ck_audit.id %}">Audit packages</a></li><span class="divider">&rarr;</span>
+ <li>Package Issues for {{Ck_Package.name}} from audit {{Ck_Package.ck_audit.name}}"</li>
+ </ul>
+ </div>
+</div>
+
+<div class="row">
+ <div class="col-md-12">
+ {% with mru=mru mrj_type=mrj_type %}
+ {% include 'mrj_section.html' %}
+ {% endwith %}
+ </div>
+</div>
+
+<div class="row" style="margin-left:10px;" id="show-settings">
+ <div class="column1">
+ <h2> Package Name: <b>"{{Ck_Package.name}}"</b> </h2>
+ </div>
+ <div class="column2">
+ <h2> Audit Name: <b>"{{Ck_Package.ck_audit.name}}"</b> </h2>
+ </div>
+</div>
+
+<p><b><big>Reports: </big></b>
+ <a class="btn btn-default navbar-btn " id="summary-report" disabled>Summary report</a>
+ <a class="btn btn-default navbar-btn " id="edit-cancel" style="display:none" >Cancel</a>
+
+<!-- Combination javascript plus redirected download for this ToasterTable (no-POST) page -->
+<form id="summary-report-form" action="/cve_check/gen_download_audit_summary/" enctype="multipart/form-data" method="post" >{% csrf_token %}
+ <input type="hidden" name="action" value="download">
+ <input type="hidden" name="options" value="" id="summary_report_options">
+ <input type="hidden" name="package_id" value="{{Ck_Package.id}}">
+ <button type="submit" form="summary-report-form" value="Submit2" style="display:none" id="download-summary-report">Generate the diff report</button>
+</form>
+
+<div class="row" style="display:none;margin-left:10px;" id="show-settings">
+ <div class="column1">
+ <h4> Package: <b>{{Ck_Package.name}}</b> </h4>
+ <h4> Audit: <b>{{Ck_Package.ck_audit.name}}</b> </h4>
+ </div>
+</div>
+
+<div id="summary-report-options" style="display:none;padding-left:25px;color:DarkCyan;">
+ <br>
+ <h4> Select the pages to be included in the report:</h4>
+ <input type="checkbox" id="repo-severity" name="repo-severity" value="repo-severity" checked>
+ <label for="repo-severity">Severity by repository</label><br>
+ <input type="checkbox" id="package-severity" name="package-severity" value="package-severity" checked>
+ <label for="package-severity">Severity by package</label><br>
+ <input type="checkbox" id="base-severity" name="base-severity" value="base-severity" checked>
+ <label for="base-severity">Severity by base image</label><br>
+ <input type="checkbox" id="cve-summary" name="cve-summary" value="cve-summary">
+ <label for="cve-summary">CVE summary</label><br>
+ <input type="checkbox" id="package-summary" name="package-summary" value="package-summary">
+ <label for="package-summary">Package summary</label><br>
+ <input type="checkbox" id="artifact-labels" name="artifact-labels" value="artifact-labels">
+ <label for="artifact-labels">Artifact Labels</label><br>
+ <input type="checkbox" id="summary_baseline" name="summary_baseline" value="summary_baseline">
+ <label for="summary_baseline">Audit versus Baseline by repository</label><br>
+</div>
+
+<div class="row">
+ <div class="col-md-12">
+ <div class="page-header">
+ <h1 class="top-air" data-role="page-title"></h1>
+ </div>
+
+ {% url '' as xhr_table_url %}
+ {% include 'toastertable.html' %}
+ </div>
+</div>
+
+<!-- Javascript support -->
+<script type="text/javascript">
+ $(document).ready(function () {
+ var selected_editsettings=false;
+ var selected_summary=false;
+
+ var tableElt = $("#{{table_name}}");
+ var titleElt = $("[data-role='page-title']");
+
+ tableElt.on("table-done", function (e, total, tableParams) {
+ var title = "Package Issues (" + total + ")";
+
+ if (tableParams.search || tableParams.filter) {
+ if (total === 0) {
+ title = "No Package Issues found";
+ }
+ else if (total > 0) {
+ title = total + " Package Issue" + (total > 1 ? "s" : '') + " found";
+ }
+ }
+
+ titleElt.text(title);
+ });
+
+ function onCommitAjaxSuccess(data, textstatus) {
+ //alert("AJAX RETURN");
+ $("#run-audit-analysis").removeAttr("disabled");
+ if (window.console && window.console.log) {
+ console.log("XHR returned:", data, "(" + textstatus + ")");
+ } else {
+ alert("NO CONSOLE:\n");
+ return;
+ }
+
+ if (data.error.startsWith('UPDATE_TYPE:')) {
+ var data = data.error.replace("UPDATE_TYPE:","");
+ var data_id=data.split('|')[0];
+ var data_value=data.replace('type_','');
+ $('#'+data_id).val(data_value);
+ $('#'+data_id).css({ "color": "blue" });
+ return;
+ }
+
+ if (data.error != "ok") {
+ alert("error on request:\n" + data.error);
+ return;
+ }
+ // reload the page with the updated tables
+// alert("PAGE REFRESH");
+ location.reload(true);
+ }
+
+ function onCommitAjaxError(jqXHR, textstatus, error) {
+ console.log("ERROR:"+error+"|"+textstatus);
+ alert("XHR errored1:\n" + error + "\n(" + textstatus + ")");
+ }
+
+ /* ensure cookie exists {% csrf_token %} */
+ function postCommitAjaxRequest(reqdata,url) {
+ reqdata["Ck_Package_id"] = {{ Ck_Package.id }};
+ url = url || "{% url 'xhr_cvechecker_commit' %}";
+ var ajax = $.ajax({
+ type:"POST",
+ data: reqdata,
+ url:url,
+ headers: { 'X-CSRFToken': $.cookie("csrftoken")},
+ success: onCommitAjaxSuccess,
+ error: onCommitAjaxError,
+ });
+ }
+
+ $('#analyze_artifacts').click(function(){
+ var result = confirm("The will analyze every CVE in this audit and will take some time. Proceed?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-analyze-artifacts',
+ },"");
+ }
+ });
+
+ $('#load_artifacts').click(function(){
+ var result = confirm("The will load all CVEs of registered artifacts, and will take some time. Proceed?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-load-artifacts',
+ },"");
+ }
+ });
+
+ $('#load_backfill').click(function(){
+ var result = confirm("Backfill missing vulnerabilities using selected audit?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-backfill-vulnerabilities',
+ "backfill_id" : $("#backfill_vulnerabilities").val(),
+ },"");
+ }
+ });
+
+
+ $('#summary-report').click(function() {
+ if (selected_summary) {
+ setDefaultDisplay(true);
+ selected_summary=false;
+ var options = "";
+ if (document.getElementById('repo-severity').checked) {
+ options = options + "repo-severity,";
+ }
+ if (document.getElementById('package-severity').checked) {
+ options = options + "package-severity,";
+ }
+ if (document.getElementById('base-severity').checked) {
+ options = options + "base-severity,";
+ }
+ if (document.getElementById('cve-summary').checked) {
+ options = options + "cve-summary,";
+ }
+ if (document.getElementById('package-summary').checked) {
+ options = options + "package-summary,";
+ }
+ if (document.getElementById('artifact-labels').checked) {
+ options = options + "artifact-labels,";
+ }
+ if (document.getElementById('summary_baseline').checked) {
+ options = options + "summary_baseline,";
+ }
+ document.getElementById("summary_report_options").value=options;
+ document.getElementById("download-summary-report").click();
+ } else {
+ document.getElementById("summary-report").innerText = "Generate Summary Report";
+ setDefaultDisplay(false);
+ document.getElementById("summary-report").style.display = 'inline';
+ document.getElementById('edit-cancel').style.display = 'inline';
+ $("#summary-report-options").slideDown();
+ selected_summary=true;
+ }
+ });
+
+ $('#edit-cancel').click(function() {
+ setDefaultDisplay(true);
+ });
+
+ $('#audit-import-tern').click(function() {
+ postCommitAjaxRequest({
+ "action" : 'submit-import-tern',
+ },"");
+ });
+
+ $('.submit-downloadattachment').click(function() {
+ $("#downloadbanner-"+this.getAttribute("x-data")).submit();
+ });
+
+ $('#audit-package-versions').click(function() {
+ document.getElementById("download-package-versions").click();
+ });
+
+ $('#audit-artifacts').click(function() {
+ document.getElementById("download-artifacts-summary").click();
+ });
+
+ $('#x_summary-report').click(function() {
+ document.getElementById("download-summary-report").click();
+ });
+
+ $('#submit-refresh-tops').click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-refresh-tops',
+ },"");
+ });
+
+ $('#submit-grafana-add').click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-grafana-add',
+ },"");
+ });
+
+ $('#submit-grafana-remove').click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-grafana-remove',
+ },"");
+ });
+
+ $('#submit-ingest-update').click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-ingest-update',
+ },"");
+ });
+
+
+ $('#submit-newowner').click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-audit-addowner',
+ "owner_id" : $("#user-list").val(),
+ });
+ });
+
+ $('#submit-newgroup').click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-audit-addgroup',
+ "group_id" : $("#group-list").val(),
+ });
+ });
+
+ $('.detach-owner').click(function(){
+ var result = confirm("Are you sure?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-audit-detachowner',
+ "owner_id" : $(this).attr('x-data'),
+ });
+ }
+ });
+
+ $('.detach-group').click(function(){
+ var result = confirm("Are you sure?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-audit-detachgroup',
+ "group_id" : $(this).attr('x-data'),
+ });
+ }
+ });
+
+ $('#focus_select').on('change', function() {
+ postCommitAjaxRequest({
+ "action" : 'submit-audit-focus',
+ "focus_select" : $("#focus_select").val(),
+ });
+ });
+
+ // Turn on the default controls
+ setDefaultDisplay(true);
+
+ });
+ </script>
+{% endblock %}
diff --git a/lib/cve_checker/templates/ck-product-toastertable.html b/lib/cve_checker/templates/ck-product-toastertable.html
new file mode 100755
index 00000000..bdf1509f
--- /dev/null
+++ b/lib/cve_checker/templates/ck-product-toastertable.html
@@ -0,0 +1,309 @@
+{% extends 'base.html' %}
+{% load static %}
+
+{% block extraheadcontent %}
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.min.css' %}" type='text/css'>
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.structure.min.css' %}" type='text/css'>
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.theme.min.css' %}" type='text/css'>
+ <script src="{% static 'js/jquery-ui.min.js' %}">
+ </script>
+<style>
+.column1 {
+ float: left;
+ width: 30%;
+}
+.column2 {
+ float: left;
+ width: 60%;
+}
+/* Clear floats after the columns */
+.row:after {
+ content: "";
+ display: table;
+ clear: both;
+}
+</style>
+{% endblock %}
+
+{% load jobtags %}
+
+{% block title %} Cve Check Product Issues {% endblock %}
+
+{% block pagecontent %}
+
+<div class="row">
+ <!-- Breadcrumbs -->
+ <div class="col-md-12">
+ <ul class="breadcrumb" id="breadcrumb">
+ <li><a href="{% url 'landing' %}">Home</a></li><span class="divider">&rarr;</span>
+ <li><a href="{% url 'cvechecker_audits' %}">Audits</a></li><span class="divider">&rarr;</span>
+ <li><a href="{% url 'cvechecker_audit' Ck_Package.ck_audit.id %}">Audit packages</a></li><span class="divider">&rarr;</span>
+ <li>Package products for {{Ck_Package.name}} from audit {{Ck_Package.ck_audit.name}}"</li>
+ </ul>
+ </div>
+</div>
+
+<div class="row">
+ <div class="col-md-12">
+ {% with mru=mru mrj_type=mrj_type %}
+ {% include 'mrj_section.html' %}
+ {% endwith %}
+ </div>
+</div>
+
+<p><b><big>Actions: </big></b>
+
+<p><b><big>Reports: </big></b>
+ <a class="btn btn-default navbar-btn " id="summary-report" disabled>Summary report</a>
+ <a class="btn btn-default navbar-btn " id="edit-cancel" style="display:none" >Cancel</a>
+
+<!-- Combination javascript plus redirected download for this ToasterTable (no-POST) page -->
+<form id="summary-report-form" action="/cve_check/gen_download_audit_summary/" enctype="multipart/form-data" method="post" >{% csrf_token %}
+ <input type="hidden" name="action" value="download">
+ <input type="hidden" name="options" value="" id="summary_report_options">
+ <input type="hidden" name="package_id" value="{{Ck_Package.id}}">
+ <button type="submit" form="summary-report-form" value="Submit2" style="display:none" id="download-summary-report">Generate the diff report</button>
+</form>
+
+<div class="row" style="display:none;margin-left:10px;" id="show-settings">
+ <div class="column1">
+ <h4> Package: <b>{{Ck_Package.name}}</b> </h4>
+ <h4> Audit: <b>{{Ck_Package.ck_audit.name}}</b> </h4>
+ </div>
+</div>
+
+<div id="summary-report-options" style="display:none;padding-left:25px;color:DarkCyan;">
+ <br>
+ <h4> Select the pages to be included in the report:</h4>
+ <input type="checkbox" id="repo-severity" name="repo-severity" value="repo-severity" checked>
+ <label for="repo-severity">Severity by repository</label><br>
+ <input type="checkbox" id="package-severity" name="package-severity" value="package-severity" checked>
+ <label for="package-severity">Severity by package</label><br>
+ <input type="checkbox" id="base-severity" name="base-severity" value="base-severity" checked>
+ <label for="base-severity">Severity by base image</label><br>
+ <input type="checkbox" id="cve-summary" name="cve-summary" value="cve-summary">
+ <label for="cve-summary">CVE summary</label><br>
+ <input type="checkbox" id="package-summary" name="package-summary" value="package-summary">
+ <label for="package-summary">Package summary</label><br>
+ <input type="checkbox" id="artifact-labels" name="artifact-labels" value="artifact-labels">
+ <label for="artifact-labels">Artifact Labels</label><br>
+ <input type="checkbox" id="summary_baseline" name="summary_baseline" value="summary_baseline">
+ <label for="summary_baseline">Audit versus Baseline by repository</label><br>
+</div>
+
+<div class="row">
+ <div class="col-md-12">
+ <div class="page-header">
+ <h1 class="top-air" data-role="page-title"></h1>
+ </div>
+
+ {% url '' as xhr_table_url %}
+ {% include 'toastertable.html' %}
+ </div>
+</div>
+
+<!-- Javascript support -->
+<script type="text/javascript">
+ $(document).ready(function () {
+ var selected_editsettings=false;
+ var selected_summary=false;
+
+ var tableElt = $("#{{table_name}}");
+ var titleElt = $("[data-role='page-title']");
+
+ tableElt.on("table-done", function (e, total, tableParams) {
+ var title = "Products (" + total + ")";
+
+ if (tableParams.search || tableParams.filter) {
+ if (total === 0) {
+ title = "No products found";
+ }
+ else if (total > 0) {
+ title = total + " Product" + (total > 1 ? "s" : '') + " found";
+ }
+ }
+
+ titleElt.text(title);
+ });
+
+ function onCommitAjaxSuccess(data, textstatus) {
+ //alert("AJAX RETURN");
+ $("#run-audit-analysis").removeAttr("disabled");
+ if (window.console && window.console.log) {
+ console.log("XHR returned:", data, "(" + textstatus + ")");
+ } else {
+ alert("NO CONSOLE:\n");
+ return;
+ }
+
+ if (data.error.startsWith('UPDATE_TYPE:')) {
+ var data = data.error.replace("UPDATE_TYPE:","");
+ var data_id=data.split('|')[0];
+ var data_value=data.replace('type_','');
+ $('#'+data_id).val(data_value);
+ $('#'+data_id).css({ "color": "blue" });
+ return;
+ }
+
+ if (data.error != "ok") {
+ alert("error on request:\n" + data.error);
+ return;
+ }
+ // reload the page with the updated tables
+// alert("PAGE REFRESH");
+ location.reload(true);
+ }
+
+ function onCommitAjaxError(jqXHR, textstatus, error) {
+ console.log("ERROR:"+error+"|"+textstatus);
+ alert("XHR errored1:\n" + error + "\n(" + textstatus + ")");
+ }
+
+ /* ensure cookie exists {% csrf_token %} */
+ function postCommitAjaxRequest(reqdata,url) {
+ reqdata["Ck_Package_id"] = {{ Ck_Package.id }};
+ url = url || "{% url 'xhr_cvechecker_commit' %}";
+ var ajax = $.ajax({
+ type:"POST",
+ data: reqdata,
+ url:url,
+ headers: { 'X-CSRFToken': $.cookie("csrftoken")},
+ success: onCommitAjaxSuccess,
+ error: onCommitAjaxError,
+ });
+ }
+
+
+ $('#summary-report').click(function() {
+ if (selected_summary) {
+ setDefaultDisplay(true);
+ selected_summary=false;
+ var options = "";
+ if (document.getElementById('repo-severity').checked) {
+ options = options + "repo-severity,";
+ }
+ if (document.getElementById('package-severity').checked) {
+ options = options + "package-severity,";
+ }
+ if (document.getElementById('base-severity').checked) {
+ options = options + "base-severity,";
+ }
+ if (document.getElementById('cve-summary').checked) {
+ options = options + "cve-summary,";
+ }
+ if (document.getElementById('package-summary').checked) {
+ options = options + "package-summary,";
+ }
+ if (document.getElementById('artifact-labels').checked) {
+ options = options + "artifact-labels,";
+ }
+ if (document.getElementById('summary_baseline').checked) {
+ options = options + "summary_baseline,";
+ }
+ document.getElementById("summary_report_options").value=options;
+ document.getElementById("download-summary-report").click();
+ } else {
+ document.getElementById("summary-report").innerText = "Generate Summary Report";
+ setDefaultDisplay(false);
+ document.getElementById("summary-report").style.display = 'inline';
+ document.getElementById('edit-cancel').style.display = 'inline';
+ $("#summary-report-options").slideDown();
+ selected_summary=true;
+ }
+ });
+
+ $('#edit-cancel').click(function() {
+ setDefaultDisplay(true);
+ });
+
+ $('#audit-import-tern').click(function() {
+ postCommitAjaxRequest({
+ "action" : 'submit-import-tern',
+ },"");
+ });
+
+ $('.submit-downloadattachment').click(function() {
+ $("#downloadbanner-"+this.getAttribute("x-data")).submit();
+ });
+
+ $('#audit-package-versions').click(function() {
+ document.getElementById("download-package-versions").click();
+ });
+
+ $('#audit-artifacts').click(function() {
+ document.getElementById("download-artifacts-summary").click();
+ });
+
+ $('#x_summary-report').click(function() {
+ document.getElementById("download-summary-report").click();
+ });
+
+ $('#submit-refresh-tops').click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-refresh-tops',
+ },"");
+ });
+
+ $('#submit-grafana-add').click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-grafana-add',
+ },"");
+ });
+
+ $('#submit-grafana-remove').click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-grafana-remove',
+ },"");
+ });
+
+ $('#submit-ingest-update').click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-ingest-update',
+ },"");
+ });
+
+
+ $('#submit-newowner').click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-audit-addowner',
+ "owner_id" : $("#user-list").val(),
+ });
+ });
+
+ $('#submit-newgroup').click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-audit-addgroup',
+ "group_id" : $("#group-list").val(),
+ });
+ });
+
+ $('.detach-owner').click(function(){
+ var result = confirm("Are you sure?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-audit-detachowner',
+ "owner_id" : $(this).attr('x-data'),
+ });
+ }
+ });
+
+ $('.detach-group').click(function(){
+ var result = confirm("Are you sure?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-audit-detachgroup',
+ "group_id" : $(this).attr('x-data'),
+ });
+ }
+ });
+
+ $('#focus_select').on('change', function() {
+ postCommitAjaxRequest({
+ "action" : 'submit-audit-focus',
+ "focus_select" : $("#focus_select").val(),
+ });
+ });
+
+ });
+ </script>
+{% endblock %}
diff --git a/lib/cve_checker/tests.py b/lib/cve_checker/tests.py
new file mode 100755
index 00000000..7ce503c2
--- /dev/null
+++ b/lib/cve_checker/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/lib/cve_checker/urls.py b/lib/cve_checker/urls.py
new file mode 100755
index 00000000..396c4fda
--- /dev/null
+++ b/lib/cve_checker/urls.py
@@ -0,0 +1,47 @@
+from django.urls import re_path as url,include
+from django.views.generic import RedirectView
+from . import views, tables
+
+urlpatterns = [
+ #
+ # Main pages
+ #
+
+ url(r'^cvechecker_audits/$',
+ tables.CveCheckerAuditsTable.as_view(template_name="ck-audits-toastertable.html"),
+ name='cvechecker_audits'),
+
+ url(r'^cvechecker/(?P<audit_id>\d+)$',
+ tables.CveCheckerAuditTable.as_view(template_name="ck-audit-toastertable.html"),
+ name='cvechecker_audit'),
+
+ url(r'^cvechecker_audit_cve/(?P<audit_id>\d+)$',
+ tables.CveCheckerAuditCveTable.as_view(template_name="ck-auditcve-toastertable.html"),
+ name='cvechecker_audit_cve'),
+
+ url(r'^cvechecker_issue/(?P<package_id>\d+)$',
+ tables.CveCheckerIssueTable.as_view(template_name="ck-issue-toastertable.html"),
+ name='cvechecker_issue'),
+
+ url(r'^cvechecker_product/(?P<package_id>\d+)$',
+ tables.CveCheckerProductTable.as_view(template_name="ck-product-toastertable.html"),
+ name='cvechecker_product'),
+
+ url(r'^cvechecker_import_manager/$',
+ tables.CveCheckerImportManagementTable.as_view(template_name="ck-import_manager-toastertable.html"),
+ name='cvechecker_import_manager'),
+
+ url(r'^gen_download_cvechecker_summary/$', views.gen_download_cvechecker_summary, name='gen_download_cvechecker_summary'),
+ url(r'^gen_download_cvechecker_audit_diff/$', views.gen_download_cvechecker_audit_diff, name='gen_download_cvechecker_audit_diff'),
+ url(r'^gen_upload_cvechecker/$', views.gen_upload_cvechecker, name='gen_upload_cvechecker'),
+ url(r'^cvechecker_clear_jobs/$', views.cvechecker_clear_jobs, name='cvechecker_clear_jobs'),
+
+ url(r'^report/(?P<page_name>\D+)$', views.report, name='report'),
+
+ #
+ # Ajax
+ #
+
+ url(r'^xhr_cvechecker_commit/$', views.xhr_cvechecker_commit,
+ name='xhr_cvechecker_commit'),
+]
diff --git a/lib/cve_checker/views.py b/lib/cve_checker/views.py
new file mode 100755
index 00000000..333cda97
--- /dev/null
+++ b/lib/cve_checker/views.py
@@ -0,0 +1,325 @@
+#
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# Security Response Tool Implementation
+#
+# Copyright (C) 2023 Wind River Systems
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+import os
+import re
+from datetime import datetime, date
+import json
+import traceback
+
+from django.urls import reverse_lazy
+from django.views import generic
+from django.http import HttpResponse, HttpResponseNotFound, JsonResponse, HttpResponseRedirect
+
+from django.contrib.auth.forms import UserCreationForm, UserChangeForm, PasswordChangeForm
+from django.contrib import messages
+from django.contrib.auth import update_session_auth_hash
+from django.contrib.auth.models import Group
+from django.shortcuts import render, redirect
+
+from orm.models import SrtSetting, Product
+from orm.models import Job, ErrorLog
+from users.models import SrtUser, UserSafe
+from srtgui.api import execute_process
+from cve_checker.models import Ck_Audit, Ck_Package, CkUploadManager
+from cve_checker.reports import doCveCheckerAuditSummaryExcel, do_audit_cvechecker_diff_report
+
+from srtgui.views import MimeTypeFinder
+
+SRT_BASE_DIR = os.environ.get('SRT_BASE_DIR', '.')
+SRT_MAIN_APP = os.environ.get('SRT_MAIN_APP', '.')
+
+# quick development/debugging support
+from srtgui.api import _log
+
+#
+# Main pages
+#
+
+# determine in which mode we are running in, and redirect appropriately
+def landing(request):
+
+ # Django sometimes has a race condition with this view executing
+ # for the master app's landing page HTML which can lead to context
+ # errors, so hard enforce the default re-direction
+ if SRT_MAIN_APP and (SRT_MAIN_APP != "yp"):
+ return redirect(f"/{SRT_MAIN_URL}/landing/")
+
+ # Append the list of landing page extensions
+ landing_extensions_table = []
+ for landing_extension in SrtSetting.objects.filter(name__startswith='LANDING_LINK').order_by('name'):
+ landing_extensions_table.append(landing_extension.value.split('|'))
+
+ context = {
+ 'landing_extensions_table' : landing_extensions_table,
+ 'this_landing' : 'srtgui',
+ }
+ return render(request, 'landing.html', context)
+
+def report(request,page_name):
+ if request.method == "GET":
+ context = GitcvecheckerReportManager.get_context_data(page_name,request=request)
+ record_list = request.GET.get('record_list', '')
+ _log("EXPORT_GET!:%s|%s|" % (request,record_list))
+ context['record_list'] = record_list
+ return render(request, 'report.html', context)
+ elif request.method == "POST":
+ _log("EXPORT_POST!:%s|%s" % (request,request.FILES))
+ parent_page = request.POST.get('parent_page', '')
+ file_name,response_file_name = GitcvecheckerReportManager.exec_report(parent_page,request=request)
+ if file_name and response_file_name:
+ fsock = open(file_name, "rb")
+ content_type = MimeTypeFinder.get_mimetype(file_name)
+ response = HttpResponse(fsock, content_type = content_type)
+ disposition = "attachment; filename=" + response_file_name
+ response["Content-Disposition"] = disposition
+ _log("EXPORT_POST_Q{%s|" % (response))
+ return response
+ else:
+ return render(request, "unavailable_artifact.html", {})
+ return redirect('/')
+ raise Exception("Invalid HTTP method for this page")
+
+# Standalone download URL, for ToasterTable pages
+#
+# TBD
+#
+def gen_download_cvechecker_audit_diff(request):
+ if request.method == "GET":
+ return redirect(landing)
+ elif request.method == "POST":
+ _log("GEN_DOWNLOAD_AUDIT_DIFF(%s)" % request.POST)
+ if request.POST["action"] == "download":
+ audit_1_id = int(request.POST.get('audit_1_id',0))
+ audit_2_id = int(request.POST.get('audit_2_id',0))
+ audit_1 = cvecheckerAudit.objects.get(id=audit_1_id)
+ audit_2 = cvecheckerAudit.objects.get(id=audit_2_id)
+ audit_scope = request.POST.get('audit_scope','0')
+ audit_sort = request.POST.get('audit_sort','1')
+ # Enforce older to newer
+ if ('1' == audit_sort) and (audit_1.id > audit_2.id):
+ audit_1,audit_2 = audit_2,audit_1
+ file_path = do_audit_cvechecker_diff_report(audit_1, audit_2, {'format':'xlsx','audit_scope':audit_scope,})
+ if os.path.isfile(file_path):
+ fsock = open(file_path, "rb")
+ content_type = MimeTypeFinder.get_mimetype(file_path)
+ response = HttpResponse(fsock, content_type = content_type)
+ response['Content-Disposition'] = 'attachment; filename="{}"'.format(os.path.basename(file_path))
+ return response
+ else:
+ _log("ERROR:could not download '%s'" % file_path)
+ return render(request, "unavailable_artifact.html", context={})
+
+#
+# Upload pages
+#
+
+# Standalone download URL, for ToasterTable pages
+def gen_upload_cvechecker(request):
+ if request.method == "GET":
+ return redirect(landing)
+ elif request.method == "POST":
+ _log(f"GEN_UPLOAD_CVECHECKER({request.POST})")
+
+ ck_upload_dir = os.path.join(SRT_BASE_DIR,'data/cve_checker/upload')
+ ck_upload_manager_id = CkUploadManager.objects.get(import_mode='Upload',path='').id
+
+ if request.POST["action"] == "upload":
+ audit_name = request.POST.get('audit_name',0)
+ orm_product_id = int(request.POST.get('orm_product_id',0))
+ orm_product = Product.objects.get(id=orm_product_id)
+
+ if not os.path.isdir(ck_upload_dir):
+ os.makedirs(ck_upload_dir)
+ try:
+ file = request.FILES['fileUpload']
+ except Exception as e:
+ _log("EXPORT_POST:'fileupload:' does not exist: %s" % e)
+ try:
+ ### TODO Error if not JSON file
+ pass
+ # Upload the file
+ local_file_path = os.path.join(ck_upload_dir,file.name)
+ _log("FOO:%s" % local_file_path)
+ if os.path.isfile(local_file_path):
+ os.remove(local_file_path)
+ with open(local_file_path, 'xb+') as destination:
+ for line in file:
+ destination.write(line)
+ # Create an audit from the imported file
+ cmnd = ['./bin/cve_checker/srtool_cvechecker.py','--import-cvechk', f"{ck_upload_manager_id},{orm_product.key},{local_file_path}", "--progress"]
+ cmnd = ['./bin/cve_checker/srtool_cvechecker.py','--import-cvechk', f"{ck_upload_manager_id},{orm_product.key},{local_file_path}", "--audit-name",audit_name,"--progress"]
+ Job.start('Audit from upload','Audit from upload',' '.join(cmnd),'')
+
+ except Exception as e:
+ _log("EXPORT_POST:'fileupload:var-1': %s" % e)
+ return redirect('cvechecker_audits')
+
+#
+# Download pages
+#
+
+# Standalone download URL, for ToasterTable pages
+def gen_download_cvechecker_summary(request):
+ if request.method == "GET":
+ return redirect(landing)
+ elif request.method == "POST":
+ _log("GEN_DOWNLOAD_CVECHECK_SUMMARY(%s)" % request.POST)
+ if request.POST["action"] == "download":
+ audit_id = int(request.POST.get('audit_id',0))
+ ck_audit = Ck_Audit.objects.get(id=audit_id)
+ queryString = request.POST.get('queryString','')
+ options = request.POST.get('options','')
+ options_dict = {'format':'xlsx','audit_id':audit_id}
+ for option in options.split(','):
+ if option: options_dict[option] = 1
+ # orderby=package&filter=is_severity:critical_not_base&search=CVE-2021-44228&default_orderby=name&filter_value=on&
+ for option in queryString.split('&'):
+ if option:
+ name,value = option.split('=')
+ options_dict[name] = value
+ file_path = doCveCheckerAuditSummaryExcel(ck_audit,options_dict)
+ if os.path.isfile(file_path):
+ fsock = open(file_path, "rb")
+ content_type = MimeTypeFinder.get_mimetype(file_path)
+ response = HttpResponse(fsock, content_type = content_type)
+ response['Content-Disposition'] = 'attachment; filename="{}"'.format(os.path.basename(file_path))
+ return response
+ else:
+ _log("ERROR:could not download '%s'" % file_path)
+ return render(request, "unavailable_artifact.html", context={})
+
+ return render(request, "unavailable_artifact.html", context={})
+
+#
+# XHR pages
+#
+
+def xhr_cvechecker_commit(request):
+ _log("XHR_CVECHECK_COMMIT(%s)" % request.POST)
+ if not 'action' in request.POST:
+ _log("xhr_cvechecker_commit:NO_ACTION")
+ return HttpResponse(json.dumps({"error":"missing action\n"}), content_type = "application/json")
+
+ try:
+ error_message = "ok"
+
+ # Fetch cvechecker data from backend
+ if request.POST["action"] == "submit-createaudit":
+ # action': ['submit-createaudit'], 'product_id': ['6'], 'ab_set': ['mickledore'], 'project_name': [''], 'name': ['audit_20231114_mickledore'], 'is-shift': ['false']}>)|
+ audit_name = request.POST.get('name', 'audit_name')
+
+ product_id = int(request.POST.get('product_id', '0'))
+ product = Product.objects.get(id=product_id)
+
+ import_id = request.POST.get('import_id', '0')
+ import_select = request.POST.get('import_select', '_none_')
+
+ # bin/cve_checker/srtool_cvechecker.py --import-cvechk import_id,master,master --progress
+ cmnd = ['./bin/cve_checker/srtool_cvechecker.py','--import-cvechk',f"{import_id},{product.key},{import_select}","--audit-name",audit_name,"--progress"]
+ _log(f"FETCH_cvechecker:JOB:{cmnd}")
+ Job.start('Fetch CveChecker','Fetch CveChecker',' '.join(cmnd),'')
+ # Set update time
+ now = datetime.today().strftime('%Y/%m/%d %H:%M:%S')
+ SrtSetting.set_setting('SRT_CVECHECK_UPDATE',now)
+
+ # Delete a cvechecker
+ elif request.POST["action"] == "submit-trash-audit":
+ cvechecker_id = int(request.POST.get('record_id', '0'))
+ cvechecker_obj = Ck_Audit.objects.get(pk=cvechecker_id)
+ cvechecker_obj.delete()
+
+ # Update management cvechecker settings
+ elif request.POST["action"] == "submit-cvechecker-settings":
+ SrtSetting.set_setting('SRT_cvechecker_PATH',request.POST.get('cvechecker_path', ''))
+
+ # Update cvechecker status
+ elif request.POST["action"] == "submit-update-ck":
+ ck_id = int(request.POST.get('ck_id', '0'))
+ cvechecker_obj = Ck_Audit.objects.get(pk=ck_id)
+ cvechecker_obj.name = request.POST.get('audit_name', '0')
+ cvechecker_obj.save()
+
+ # Add cvechecker import blank
+ elif request.POST["action"] == "submit-new-import-ck":
+ ck_import_obj,created = CkUploadManager.objects.get_or_create(name='new')
+ ck_import_obj.order = 0
+ ck_import_obj.import_mode = 'File'
+ ck_import_obj.path = ''
+ ck_import_obj.pem = ''
+ ck_import_obj.repo = ''
+ ck_import_obj.branch = ''
+ ck_import_obj.auto_refresh = False
+ ck_import_obj.save()
+
+ # Update cvechecker import
+ elif request.POST["action"] == "submit-update-import-ck":
+ ck_id = int(request.POST.get('ck_id', '0'))
+ try:
+ order = int(request.POST.get('audit_order', '0').strip())
+ ck_import_obj = CkUploadManager.objects.get(id=ck_id)
+ ck_import_obj.order = order
+ ck_import_obj.name = request.POST.get('audit_name', 'new').strip()
+ ck_import_obj.import_mode = request.POST.get('audit_mode', 'File').strip()
+ ck_import_obj.path = request.POST.get('audit_path', '').strip()
+ ck_import_obj.pem = request.POST.get('audit_pem', '').strip()
+ ck_import_obj.repo = request.POST.get('audit_repo', '').strip()
+ ck_import_obj.branch = request.POST.get('audit_branch', '').strip()
+ ck_import_obj.auto_refresh = ('True' == request.POST.get('audit_refresh', 'False').strip())
+ ck_import_obj.save()
+ except:
+ error_message = "Error: order must be an integer"
+
+ # Update the Import select tables
+ elif request.POST["action"] == "submit-import-refresh":
+ cmnd = ["bin/cve_checker/srtool_cvechecker.py","--update-imports","-f"]
+ result_returncode,result_stdout,result_stderr = execute_process(*cmnd)
+ if 0 != result_returncode:
+ error_message = f"ERROR:{cmnd}: {result_stderr}:{result_stdout}:"
+
+ # Delete an import
+ elif request.POST["action"] == "submit-remove-import-ck":
+ ck_id = int(request.POST.get('record_id', '0'))
+ ck_import_obj = CkUploadManager.objects.get(pk=ck_id)
+ ck_import_obj.delete()
+
+ # Clear the dead jobs
+ elif request.POST["action"] == "submit-clearjobs":
+ if UserSafe.is_admin(request.user):
+ Job.objects.all().delete()
+
+ # Undefined action
+ else:
+ error_message ="ERROR:unknown action '%s'" % request.POST["action"]
+
+ _log("XHR_CVECHECK_COMMIT:DONE:%s" % error_message)
+ return HttpResponse(json.dumps( {"error": error_message,} ), content_type = "application/json")
+
+ except Exception as e:
+ _log("XHR_CVECHECK_COMMIT:no(%s)(%s)" % (e,traceback.format_exc()))
+ return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
+
+
+# Delete jobs
+def cvechecker_clear_jobs(request):
+ if UserSafe.is_admin(request.user):
+ Job.objects.all().delete()
+ return redirect('manage')
diff --git a/lib/orm/management/commands/checksettings.py b/lib/orm/management/commands/checksettings.py
index f5e4df02..5701c0aa 100644
--- a/lib/orm/management/commands/checksettings.py
+++ b/lib/orm/management/commands/checksettings.py
@@ -36,7 +36,7 @@ class Command(BaseCommand):
# to allow embedding comments in the JSON files
def _load_datasource(self,dir):
for ds in glob.glob(os.path.join(dir,'datasource*.json')):
- _log("Load_Datasource:%s" % ds)
+ # _log("Load_Datasource:%s" % ds)
with open(ds) as json_data:
dct = json.load(json_data)
if 'srtsetting' in dct:
@@ -49,7 +49,17 @@ class Command(BaseCommand):
if 'datasource' in dct:
for datasource in dct['datasource']:
#print(" LOAD_DATASOURCE:%s:%s" % (datasource['key'],datasource['description']))
- ds,create = DataSource.objects.get_or_create(key=datasource['key'])
+ ds,created = DataSource.objects.get_or_create(key=datasource['key'])
+ if not created:
+ # Special handling for attributes, persistent enablement
+ new_attributes = datasource['attributes'] if 'attributes' in datasource else ''
+ # An explict "ENABLE" overrides any default "DISABLE"
+ if 'ENABLE ' in ds.attributes:
+ new_attributes = 'ENABLE ' + new_attributes.replace('DISABLE ','').replace('ENABLE ','')
+ # An explict "DISABLE" overrides the default enable
+ if 'DISABLE ' in ds.attributes:
+ new_attributes = 'DISABLE ' + new_attributes.replace('DISABLE ','').replace('ENABLE ','')
+ datasource['attributes'] = new_attributes
for key in datasource.keys():
if key.startswith("_comment"):
continue
diff --git a/lib/orm/management/commands/lsupdates.py b/lib/orm/management/commands/lsupdates.py
index ca67713a..1805142f 100644
--- a/lib/orm/management/commands/lsupdates.py
+++ b/lib/orm/management/commands/lsupdates.py
@@ -5,7 +5,7 @@
# Security Response Tool Implementation
#
# Copyright (C) 2013-2015 Intel Corp.
-# Copyright (C) 2017-2018 Wind River Systems
+# Copyright (C) 2017-2021 Wind River Systems
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
@@ -103,7 +103,7 @@ class Command(BaseCommand):
(what,
pec))
sys.stdout.flush()
- if int(pec) is 100:
+ if int(pec) == 100:
sys.stdout.write("\n")
sys.stdout.flush()
@@ -322,6 +322,16 @@ class Command(BaseCommand):
logger.info("***LS UPDATES***")
+ # Disable the background updates until these are all processed
+ SrtSetting.set_setting('SRT_DISABLE_UPDATES','yes')
+
+ # First process the pre-init data sources in strict pk order to insure dependencies
+ data_sources=DataSource.objects.filter(update_frequency=DataSource.PREINIT).order_by('key')
+ for source in data_sources:
+ if source.init:
+ print("Fetching pre-init datasource '%s:%s'" % (source.source,source.description))
+ self.execute_script(source.init)
+
# Process the data sources in strict pk order to insure dependencies
data_sources=DataSource.objects.all().order_by('key')
for source in data_sources:
@@ -333,6 +343,10 @@ class Command(BaseCommand):
# No Init action?
print("Skipping datasource %s (no init action)" % (source.description))
continue
+ elif 'DISABLE ' in source.attributes:
+ # Data source disabled
+ print("Disabled datasource %s (%s)" % (source.description,source.attributes))
+ continue
else:
logger.info("Fetching datasource %s:%s" % (source.source,source.description))
print("Fetching datasource '%s:%s'" % (source.source,source.description))
@@ -395,10 +409,12 @@ class Command(BaseCommand):
logger.error("Unknown data source type for (%s,%s,%s) " % (source.data,source.source,source.name))
_log("Unknown data source type for %s,%s,%s) " % (source.data,source.source,source.name))
+ # Re-able the background updates until these are all processed
+ SrtSetting.set_setting('SRT_DISABLE_UPDATES','no')
+
os.system('setterm -cursor on')
def handle(self, *args, **options):
-
# testing shortcuts
if 'yes' == SrtSetting.objects.get(name='SRTDBG_MINIMAL_DB').value:
print("TEST: MINIMAL DATABASE LOADING")
@@ -407,5 +423,4 @@ class Command(BaseCommand):
Command.status_sustaining_limit = 10
Command.debug_defect_limit = 10
Command.cpe_limit = 10
-
self.update()
diff --git a/lib/orm/migrations/0007_components_errorlog.py b/lib/orm/migrations/0007_components_errorlog.py
new file mode 100755
index 00000000..88a02ee1
--- /dev/null
+++ b/lib/orm/migrations/0007_components_errorlog.py
@@ -0,0 +1,39 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2020-02-01 03:09
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('orm', '0006_reconcile'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='ErrorLog',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('severity', models.IntegerField(default=0)),
+ ('description', models.TextField(blank=True)),
+ ('srt_created', models.DateTimeField(auto_now_add=True, null=True)),
+ ],
+ ),
+ migrations.AddField(
+ model_name='defect',
+ name='packages',
+ field=models.TextField(blank=True),
+ ),
+ migrations.AddField(
+ model_name='investigation',
+ name='packages',
+ field=models.TextField(blank=True),
+ ),
+ migrations.AddField(
+ model_name='vulnerability',
+ name='packages',
+ field=models.TextField(blank=True),
+ ),
+ ]
diff --git a/lib/orm/migrations/0008_cveaccess.py b/lib/orm/migrations/0008_cveaccess.py
new file mode 100644
index 00000000..c12ac9ed
--- /dev/null
+++ b/lib/orm/migrations/0008_cveaccess.py
@@ -0,0 +1,24 @@
+# Generated by Django 2.2.11 on 2020-10-23 08:03
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('orm', '0007_components_errorlog'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='CveAccess',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('cve', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cve_users', to='orm.Cve')),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cve_user', to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/lib/orm/migrations/0009_recipetable.py b/lib/orm/migrations/0009_recipetable.py
new file mode 100644
index 00000000..4f3621f1
--- /dev/null
+++ b/lib/orm/migrations/0009_recipetable.py
@@ -0,0 +1,20 @@
+# Generated by Django 2.2.11 on 2020-11-13 21:48
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('orm', '0008_cveaccess'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='RecipeTable',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('recipe_name', models.CharField(max_length=50)),
+ ],
+ ),
+ ]
diff --git a/lib/orm/migrations/0010_job.py b/lib/orm/migrations/0010_job.py
new file mode 100644
index 00000000..4b837379
--- /dev/null
+++ b/lib/orm/migrations/0010_job.py
@@ -0,0 +1,35 @@
+# Generated by Django 2.2.11 on 2020-11-14 23:59
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('orm', '0009_recipetable'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Job',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(default='', max_length=50)),
+ ('description', models.TextField(blank=True)),
+ ('command', models.TextField(blank=True)),
+ ('log_file', models.TextField(blank=True)),
+ ('options', models.TextField(blank=True)),
+ ('status', models.IntegerField(choices=[(0, 'NotStarted'), (1, 'InProgress'), (2, 'Success'), (3, 'Errors'), (4, 'Cancelling'), (5, 'Cancelled')], default=0)),
+ ('parent_name', models.CharField(default='', max_length=50)),
+ ('pid', models.IntegerField(default=0)),
+ ('count', models.IntegerField(default=0)),
+ ('max', models.IntegerField(default=0)),
+ ('errors', models.IntegerField(default=0)),
+ ('warnings', models.IntegerField(default=0)),
+ ('refresh', models.IntegerField(default=0)),
+ ('message', models.CharField(default='', max_length=50)),
+ ('started_on', models.DateTimeField(null=True)),
+ ('completed_on', models.DateTimeField(null=True)),
+ ],
+ ),
+ ]
diff --git a/lib/orm/migrations/0011_extend_field_sizes.py b/lib/orm/migrations/0011_extend_field_sizes.py
new file mode 100644
index 00000000..830a2de3
--- /dev/null
+++ b/lib/orm/migrations/0011_extend_field_sizes.py
@@ -0,0 +1,33 @@
+# Generated by Django 2.2.17 on 2021-02-04 23:27
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('orm', '0010_job'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='package',
+ name='name',
+ field=models.CharField(blank=True, max_length=80),
+ ),
+ migrations.AlterField(
+ model_name='package',
+ name='realname',
+ field=models.CharField(blank=True, max_length=80),
+ ),
+ migrations.AlterField(
+ model_name='product',
+ name='cpe',
+ field=models.CharField(max_length=255),
+ ),
+ migrations.AlterField(
+ model_name='datasource',
+ name='key',
+ field=models.CharField(max_length=80),
+ ),
+ ]
diff --git a/lib/orm/migrations/0012_job_user.py b/lib/orm/migrations/0012_job_user.py
new file mode 100755
index 00000000..09af561f
--- /dev/null
+++ b/lib/orm/migrations/0012_job_user.py
@@ -0,0 +1,21 @@
+# Generated by Django 2.2.11 on 2021-10-06 18:26
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('orm', '0011_extend_field_sizes'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='job',
+ name='user',
+ field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
+ ),
+ ]
diff --git a/lib/orm/migrations/0013_update_preinit.py b/lib/orm/migrations/0013_update_preinit.py
new file mode 100755
index 00000000..6711be9a
--- /dev/null
+++ b/lib/orm/migrations/0013_update_preinit.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.11 on 2021-12-06 03:03
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('orm', '0012_job_user'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='datasource',
+ name='update_frequency',
+ field=models.IntegerField(choices=[(0, 'Minute'), (1, 'Hourly'), (2, 'Daily'), (3, 'Weekly'), (4, 'Monthly'), (5, 'OnDemand'), (6, 'OnStartup'), (7, 'PreInit')], default=2),
+ ),
+ ]
diff --git a/lib/orm/migrations/0014_alter_packagetocve_applicable.py b/lib/orm/migrations/0014_alter_packagetocve_applicable.py
new file mode 100644
index 00000000..0a7e2cc0
--- /dev/null
+++ b/lib/orm/migrations/0014_alter_packagetocve_applicable.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.0 on 2023-01-30 18:58
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('orm', '0013_update_preinit'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='packagetocve',
+ name='applicable',
+ field=models.BooleanField(null=True),
+ ),
+ ]
diff --git a/lib/orm/models.py b/lib/orm/models.py
index 9b4f99ce..f5016b7d 100644
--- a/lib/orm/models.py
+++ b/lib/orm/models.py
@@ -4,7 +4,7 @@
#
# Security Response Tool Implementation
#
-# Copyright (C) 2017 Wind River Systems
+# Copyright (C) 2017-2021 Wind River Systems
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
@@ -27,21 +27,29 @@ from django.db import transaction
from django.core import validators
from django.conf import settings
import django.db.models.signals
+from django.db.models import F, Q, Sum, Count
+from django.contrib.auth.models import AbstractUser, Group, AnonymousUser
+from srtgui.api import execute_process, execute_process_close_fds
from users.models import SrtUser
import sys
import os
import re
+import itertools
from signal import SIGUSR1
from datetime import datetime
import json
+import subprocess
+import time
+import signal
+import pytz
import logging
logger = logging.getLogger("srt")
# quick development/debugging support
-from srtgui.api import _log
+from srtgui.api import _log, parameter_join
# Sqlite support
@@ -74,7 +82,6 @@ if 'sqlite' in settings.DATABASES['default']['ENGINE']:
return _base_insert(self, *args, **kwargs)
QuerySet._insert = _insert
- from django.utils import six
def _create_object_from_params(self, lookup, params):
"""
Tries to create an object using passed params.
@@ -89,7 +96,6 @@ if 'sqlite' in settings.DATABASES['default']['ENGINE']:
return self.get(**lookup), False
except self.model.DoesNotExist:
pass
- six.reraise(*exc_info)
QuerySet._create_object_from_params = _create_object_from_params
@@ -331,8 +337,10 @@ class Update():
PUBLISH_DATE = "Publish_Date(%s,%s)"
AFFECTED_COMPONENT = "Affected_Component(%s,%s)"
ACKNOWLEDGE_DATE = "AcknowledgeDate(%s,%s)"
+ PUBLIC = "Public(%s,%s)"
ATTACH_CVE = "Attach_CVE(%s)"
DETACH_CVE = "Detach_CVE(%s)"
+ MERGE_CVE = "Merge_CVE(%s)"
ATTACH_VUL = "Attach_Vulnerability(%s)"
DETACH_VUL = "Detach_Vulnerability(%s)"
ATTACH_INV = "Attach_Investigration(%s)"
@@ -404,11 +412,11 @@ class HelpText(models.Model):
text = models.TextField()
-#UPDATE_FREQUENCY: 0 = every minute, 1 = every hour, 2 = every day, 3 = every week, 4 = every month, 5 = every year
+#UPDATE_FREQUENCY: 0 = every n minutes, 1 = every hour, 2 = every day, 3 = every week, 4 = every month, 5 = on demand
class DataSource(models.Model):
search_allowed_fields = ['key', 'name', 'description', 'init', 'update', 'lookup']
- #UPDATE FREQUENCT
+ #UPDATE FREQUENCY
MINUTELY = 0
HOURLY = 1
DAILY = 2
@@ -416,6 +424,7 @@ class DataSource(models.Model):
MONTHLY = 4
ONDEMAND = 5
ONSTARTUP = 6
+ PREINIT = 7
FREQUENCY = (
(MINUTELY, 'Minute'),
(HOURLY, 'Hourly'),
@@ -424,6 +433,7 @@ class DataSource(models.Model):
(MONTHLY, 'Monthly'),
(ONDEMAND, 'OnDemand'),
(ONSTARTUP, 'OnStartup'),
+ (PREINIT, 'PreInit'),
)
# Global date format
@@ -434,7 +444,7 @@ class DataSource(models.Model):
LOOKUP_MISSING = 'LOOKUP-MISSING'
PREVIEW_SOURCE = 'PREVIEW-SOURCE'
- key = models.CharField(max_length=20)
+ key = models.CharField(max_length=80)
data = models.CharField(max_length=20)
source = models.CharField(max_length=20)
name = models.CharField(max_length=20)
@@ -570,6 +580,9 @@ class Cve(models.Model):
def get_publish_text(self):
return Cve.PUBLISH_STATE[int(self.publish_state)][1]
@property
+ def get_public_text(self):
+ return 'Public' if self.public else 'Private'
+ @property
def is_local(self):
try:
CveLocal.objects.get(name=self.name)
@@ -592,6 +605,47 @@ class Cve(models.Model):
if the_comments == the_packages:
return the_comments
return '%s' % (the_comments)
+ def propagate_private(self):
+ # Gather allowed users
+ user_id_list = []
+ for cveaccess in CveAccess.objects.filter(cve=self):
+ user_id_list.append(cveaccess.user_id)
+ _log("BOO1:user_id=%s" % cveaccess.user_id)
+
+ # Decend the object tree
+ for c2v in CveToVulnerablility.objects.filter(cve=self):
+ vulnerability = Vulnerability.objects.get(id=c2v.vulnerability_id)
+ _log("BOO2:v=%s,%s" % (vulnerability.name,self.public))
+ vulnerability.public = self.public
+ vulnerability.save()
+ if not self.public:
+ # Remove existing users
+ for va in VulnerabilityAccess.objects.filter(vulnerability=vulnerability):
+ _log("BOO3:DEL:v=%s,%s" % (vulnerability.name,va.id))
+ va.delete()
+ # Add valid user list
+ for user_id in user_id_list:
+ va,create = VulnerabilityAccess.objects.get_or_create(vulnerability=vulnerability,user_id=user_id)
+ _log("BOO4:ADD:v=%s,%s,%s" % (vulnerability.name,va.id,user_id))
+ va.save()
+
+ for v2i in VulnerabilityToInvestigation.objects.filter(vulnerability = vulnerability):
+ investigation = Investigation.objects.get(id=v2i.investigation_id)
+ _log("BOO5:i=%s,%s" % (investigation.name,self.public))
+ investigation.public = self.public
+ investigation.save()
+ if not self.public:
+ # Remove existing users
+ for ia in InvestigationAccess.objects.filter(investigation=investigation):
+ _log("BOO6:DEL:v=%s,%s" % (investigation.name,ia.id))
+ ia.delete()
+ # Add valid user list
+ for user_id in user_id_list:
+ ia,create = InvestigationAccess.objects.get_or_create(investigation=investigation,user_id=user_id)
+ _log("BOO7:ADD:i=%s,%s,%s" % (investigation.name,ia.id,user_id))
+ ia.save()
+
+
class CveDetail():
# CPE item list
@@ -727,6 +781,10 @@ class CveSource(models.Model):
cve = models.ForeignKey(Cve,related_name="cve_parent",blank=True, null=True,on_delete=models.CASCADE,)
datasource = models.ForeignKey(DataSource,related_name="cve_datasource", blank=True, null=True,on_delete=models.CASCADE,)
+class CveAccess(models.Model):
+ cve = models.ForeignKey(Cve,related_name="cve_users",on_delete=models.CASCADE,)
+ user = models.ForeignKey(SrtUser,related_name="cve_user",on_delete=models.CASCADE,)
+
class CveHistory(models.Model):
search_allowed_fields = ['cve__name', 'comment', 'date', 'author']
cve = models.ForeignKey(Cve,related_name="cve_history",default=None, null=True, on_delete=models.CASCADE,)
@@ -764,8 +822,8 @@ class Package(models.Model):
)
mode = models.IntegerField(choices=MODE, default=FOR)
- name = models.CharField(max_length=50, blank=True)
- realname = models.CharField(max_length=50, blank=True)
+ name = models.CharField(max_length=80, blank=True)
+ realname = models.CharField(max_length=80, blank=True)
invalidname = models.TextField(blank=True)
weight = models.IntegerField(default=0)
# computed count data
@@ -812,7 +870,7 @@ class Package(models.Model):
class PackageToCve(models.Model):
package = models.ForeignKey(Package,related_name="package2cve",on_delete=models.CASCADE,)
cve = models.ForeignKey(Cve,related_name="cve2package",on_delete=models.CASCADE,)
- applicable = models.NullBooleanField(default=True, null=True)
+ applicable = models.BooleanField(null=True)
# CPE Filtering
@@ -860,6 +918,11 @@ class CveReference(models.Model):
name = models.CharField(max_length=100, null=True)
datasource = models.ForeignKey(DataSource,related_name="source_references", blank=True, null=True,on_delete=models.CASCADE,)
+class RecipeTable(models.Model):
+ search_allowed_fields = ['recipe_name']
+ recipe_name = models.CharField(max_length=50)
+
+
# PRODUCT
class Product(models.Model):
@@ -870,7 +933,7 @@ class Product(models.Model):
name = models.CharField(max_length=40)
version = models.CharField(max_length=40)
profile = models.CharField(max_length=40)
- cpe = models.CharField(max_length=40)
+ cpe = models.CharField(max_length=255)
defect_tags = models.TextField(blank=True, default='')
product_tags = models.TextField(blank=True, default='')
@@ -971,6 +1034,9 @@ class Vulnerability(models.Model):
if self.cve_primary_name:
return "%s (%s)" % (self.name,self.cve_primary_name)
return "%s" % (self.name)
+ @property
+ def get_public_text(self):
+ return 'Public' if self.public else 'Private'
@staticmethod
def new_vulnerability_name():
# get next vulnerability name atomically
@@ -1320,6 +1386,9 @@ class Investigation(models.Model):
if self.vulnerability and self.vulnerability.cve_primary_name:
return "%s (%s)" % (self.name,self.vulnerability.cve_primary_name.name)
return "%s" % (self.name)
+ @property
+ def get_public_text(self):
+ return 'Public' if self.public else 'Private'
@staticmethod
def new_investigation_name():
current_investigation_index,create = SrtSetting.objects.get_or_create(name='current_investigation_index')
@@ -1492,6 +1561,210 @@ class ErrorLog(models.Model):
def get_severity_text(self):
return ErrorLog.SEVERITY[int(self.severity)][1]
+class Job(models.Model):
+ search_allowed_fields = ['name', 'title', 'description', 'status']
+ # Job Status
+ NOTSTARTED = 0
+ INPROGRESS = 1
+ SUCCESS = 2
+ ERRORS = 3
+ CANCELLING = 4
+ CANCELLED = 5
+ STATUS = (
+ (NOTSTARTED, 'NotStarted'),
+ (INPROGRESS, 'InProgress'),
+ (SUCCESS, 'Success'),
+ (ERRORS, 'Errors'),
+ (CANCELLING, 'Cancelling'),
+ (CANCELLED, 'Cancelled'),
+ )
+
+ # Required
+ name = models.CharField(max_length=50,default='')
+ description = models.TextField(blank=True)
+ command = models.TextField(blank=True)
+ log_file = models.TextField(blank=True)
+ # Optional
+ parent_name = models.CharField(max_length=50,default='')
+ options = models.TextField(blank=True)
+ user = models.ForeignKey(SrtUser,default=None,null=True,on_delete=models.CASCADE,)
+ # Managed
+ status = models.IntegerField(choices=STATUS, default=NOTSTARTED)
+ pid = models.IntegerField(default=0)
+ count = models.IntegerField(default=0)
+ max = models.IntegerField(default=0)
+ errors = models.IntegerField(default=0)
+ warnings = models.IntegerField(default=0)
+ refresh = models.IntegerField(default=0)
+ message = models.CharField(max_length=50,default='')
+ started_on = models.DateTimeField(null=True)
+ completed_on = models.DateTimeField(null=True)
+
+ @property
+ def get_status_text(self):
+ for s_val,s_name in Job.STATUS:
+ if s_val == self.status:
+ return s_name
+ return "?STATUS?"
+
+ @staticmethod
+ def get_recent(user=None):
+ """
+ Return recent jobs as a list; if sprint is set, only return
+ jobs for that sprint
+ """
+
+ if user and not isinstance(user,AnonymousUser):
+ jobs = Job.objects.filter(user=user)
+ else:
+ jobs = Job.objects.all()
+
+ finished_criteria = \
+ Q(status=Job.SUCCESS) | \
+ Q(status=Job.ERRORS) | \
+ Q(status=Job.CANCELLED)
+
+ recent_jobs = list(itertools.chain(
+ jobs.filter(status=Job.INPROGRESS).order_by("-started_on"),
+ jobs.filter(finished_criteria).order_by("-completed_on")[:3]
+ ))
+
+ # add percentage done property to each job; this is used
+ # to show job progress in mrj_section.html
+ for job in jobs:
+ job.percentDone = job.completeper()
+ job.outcomeText = job.get_status_text
+
+ return recent_jobs
+
+ def completeper(self):
+ if self.max > 0:
+ completeper = (self.count * 100) // self.max
+ else:
+ completeper = 0
+ return completeper
+
+ def eta(self):
+ eta = datetime.now()
+ completeper = self.completeper()
+ if completeper() > 0:
+ eta += ((eta - self.started_on)*(100-completeper))/completeper
+ return eta
+
+ @staticmethod
+ def start(name,description,command,options='',log_file='logs/run_job.log',job_id=1):
+ # The audit_job.py will set the pid and time values so that there is no db race condition
+ command = ['bin/common/srtool_job.py','--name',name,'--description',description,'--command',command,'--options',options,'--log',log_file]
+ if job_id:
+ command.extend(['--job-id',str(job_id)])
+ _log("JOB_START:%s" % parameter_join(command))
+# subprocess.Popen(command,close_fds=True)
+# result_returncode,result_stdout,result_stderr = execute_process(command)
+ execute_process_close_fds(command)
+
+ def cancel(self):
+ if self.status == Job.INPROGRESS:
+ try:
+ if self.pid:
+ os.kill(self.pid, signal.SIGTERM) #or signal.SIGKILL
+ except Exception as e:
+ _log("ERROR_JOB:Cancel:%s" % (e))
+ try:
+ self.status = Job.CANCELLING
+ self.completed_on = datetime.now()
+ self.pid = 0
+ self.save()
+ except Exception as e:
+ _log("ERROR_JOB:Cancelled:%s" % (e))
+
+ def done(self):
+ if not self.pid:
+ return
+ if self.status == Job.INPROGRESS:
+ self.pid = 0
+ self.completed_on = datetime.now()
+ self.status = Job.SUCCESS
+ ### TODO COUNT ERRORS AND WARNINGS
+ self.save()
+ elif self.status == Job.CANCELLING:
+ self.pid = 0
+ self.completed_on = datetime.now()
+ self.status = Job.CANCELLED
+ self.errors = 1
+ self.save()
+
+ @staticmethod
+ def preclear_jobs(user=None,user_id=0,user_none=False):
+ # NOTE: preclear completed jobs so that this page comes up clean
+ # without completed progress bars hanging around
+ if (not user_id) and (not user) and (not user_none):
+ return
+ if user_none:
+ user_id = None
+ elif not user_id:
+ user_id = user.id
+ for job in Job.objects.filter(user_id=user_id):
+ if job.status in (Job.SUCCESS,Job.ERRORS):
+ job.delete()
+
+# Wrapper class to run internal 'jobs' with the progress bar
+class Job_Local():
+ job = None
+ log_file_fd = None
+ INTERNAL_COMMAND = '<internal>'
+ DEFAULT = -1
+ DEFAULT_LOG = '.job_log.txt'
+
+ def __init__(self, name, description='', options='', log_file=DEFAULT_LOG, user=None):
+ self.job = Job(name=name, description=description, options=options, log_file=log_file, user=user)
+ self.job.command = self.INTERNAL_COMMAND
+ self.job.started_on = datetime.now(pytz.utc)
+ self.job.completed_on = None
+ if log_file:
+ self.log_file_fd = open(self.job.log_file, 'w')
+ self.log_file_fd.write(f"JOB_START: {name},{description} @{self.job.started_on}\n" )
+ self.job.status = Job.INPROGRESS
+ self.job.save()
+
+ # If cnt == DEFAULT, increment existing cnt value
+ # If max == DEFAULT, use existing max value
+ def update(self,message,count=DEFAULT,max=DEFAULT):
+ if count == self.DEFAULT:
+ self.job.count += 1
+ else:
+ self.job.count = count
+ if max != self.DEFAULT:
+ self.job.max = max
+ if self.job.count > self.job.max:
+ self.job.count = self.job.max
+ self.job.message = message
+ if True and self.log_file_fd:
+ self.log_file_fd.write(f"JOB_UPDATE({self.job.message},{self.job.count},{self.job.max})\n")
+ self.log_file_fd.flush()
+ self.job.save()
+ def add_warning(self,msg):
+ self.job.warnings += 1
+ self.job.save()
+ if self.log_file_fd:
+ self.log_file_fd.write("WARNING: " + msg + "\n" )
+ def add_error(self,msg):
+ self.job.errors += 1
+ self.job.save()
+ if self.log_file_fd:
+ self.log_file_fd.write("ERROR: " + msg + "\n" )
+ def done(self,sleep_time=4):
+ if sleep_time:
+ time.sleep(sleep_time)
+ self.update('Done',self.job.max,self.job.max)
+ self.job.completed_on = datetime.now(pytz.utc)
+ self.job.status = Job.ERRORS if self.job.errors else Job.SUCCESS
+ self.job.save()
+ if self.log_file_fd:
+ self.log_file_fd.write(f"JOB_STOP: W={self.job.warnings},E={self.job.errors} @{self.job.completed_on}\n" )
+ self.log_file_fd.flush()
+ self.log_file_fd.close()
+ self.log_file_fd = None
+
#
# Database Cache Support
#
diff --git a/lib/srtgui/api.py b/lib/srtgui/api.py
index 761839a8..2478fb9e 100644
--- a/lib/srtgui/api.py
+++ b/lib/srtgui/api.py
@@ -2,6 +2,7 @@
# BitBake Toaster Implementation
#
# Copyright (C) 2016-2018 Intel Corporation
+# Copyright (C) 2018-2023 Wind River Systems
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
@@ -28,6 +29,7 @@ import re
import json
from django.http import JsonResponse
+from django.views.generic import View
logger = logging.getLogger("srt")
@@ -54,38 +56,71 @@ def error_log(severity,description):
error = ErrorLog.objects.create(severity=severity,description=description,)
error.save()
+# Quote parameters if spaces
+def parameter_join(a):
+ str = []
+ for s in a:
+ if (' ' in s) or (0 == len(s)):
+ str.append('"%s"' % s)
+ else:
+ str.append(s)
+ return ' '.join(str)
+
+
+#
# Sub Process calls
+#
+# Enforce that all scripts run from the SRT_BASE_DIR context
+#
+
def execute_process(*args):
+ # Only string-type parameters allowed
cmd_list = []
for arg in args:
+ if not arg: continue
if isinstance(arg, (list, tuple)):
# Flatten all the way down
for a in arg:
- cmd_list.append(a)
+ if not a: continue
+ cmd_list.append(str(a))
else:
- cmd_list.append(arg)
-
- # Python < 3.5 compatible
- if sys.version_info < (3,5):
- process = subprocess.Popen(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- try:
- stdout, stderr = process.communicate(input)
- except:
- process.kill()
- process.wait()
- raise
- retcode = process.poll()
- return retcode, stdout, stderr
- else:
- result = subprocess.run(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- return result.returncode,result.stdout,result.stderr
+ cmd_list.append(str(arg))
+
+ srt_base_dir = os.environ.get('SRT_BASE_DIR')
+ if srt_base_dir and (srt_base_dir != os.getcwd()):
+ os.chdir(srt_base_dir)
+ _log(f"FOOBAR:CHDIR{srt_base_dir}")
+ if cmd_list[0].startswith('bin/') or cmd_list[0].startswith('./bin'):
+ cmd_list[0] = os.path.join(srt_base_dir,cmd_list[0])
+ _log(f"FOOBAR:{cmd_list[0]}:{os.getcwd()}")
+
+ result = subprocess.run(cmd_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ return(result.returncode,result.stdout.decode('utf-8'),result.stderr.decode('utf-8'))
+
+# For Jobs, with captured output
+def execute_process_close_fds(cmnd):
+ srt_base_dir = os.environ.get('SRT_BASE_DIR')
+ if srt_base_dir and (srt_base_dir != os.getcwd()):
+ os.chdir(srt_base_dir)
+ if cmnd[0].startswith('bin/') or cmnd[0].startswith('./bin'):
+ cmnd[0] = os.path.join(srt_base_dir,cmnd[0])
+ subprocess.Popen(cmnd,close_fds=True)
+
+# For Jobs, with captured output
+def execute_system(cmnd):
+ srt_base_dir = os.environ.get('SRT_BASE_DIR')
+ if srt_base_dir and (srt_base_dir != os.getcwd()):
+ os.chdir(srt_base_dir)
+ if cmnd.startswith('bin/') or cmnd.startswith('./bin'):
+ cmnd = srt_base_dir + '/' + cmnd[0]
+ return os.system(cmnd)
#
# Update CVE datasource list: (a) fetch alt sources, (b) refresh preview sources
#
# #### TODO
-def update_cve_datasources(source_filter=''):
+def update_cve_datasources(source_filter='',force_update_source=True):
# Attach all matching CVE sources
_log("Alternate1:%s" % (cve_object.name))
query_set = DataSource.objects.filter(data="cve")
@@ -98,12 +133,13 @@ def update_cve_datasources(source_filter=''):
_log("Alternate CVE source %s for %s (created=%s)" % (ds.key,cve_object.name,created))
# Force update the CVE summary data from sources
- result_returncode,result_stdout,result_stderr = execute_process(
- './bin/nist/srtool_nist.py',
- '--update-cve-list',
- cve_object.name,
- '--force'
- )
+ if force_update_source:
+ result_returncode,result_stdout,result_stderr = execute_process(
+ os.path.join(os.environ.get('SRT_BASE_DIR'),'bin/nist/srtool_nist.py'),
+ '--update-cve-list',
+ cve_object.name,
+ '--force'
+ )
#
# Extract Upstream CVE record details
@@ -123,14 +159,19 @@ def readCveDetails_Upstream(cve, cve_datasource):
v.description = "ERROR(%s):missing lookup command" % (cve_datasource.description)
return v
lookup_command = lookup_command.replace('%command%','--cve-detail=%s' % cve.name)
- result_returncode,result_stdout,result_stderr = execute_process(lookup_command.split(' '))
+ lookup_commands = lookup_command.split(' ')
+ # Convert local SRT bin calls to absolute path calls
+ if not lookup_commands[0].startswith('/'):
+ lookup_commands[0] = os.path.join(os.environ.get('SRT_BASE_DIR', './'),lookup_commands[0])
+ # Execute the call
+ result_returncode,result_stdout,result_stderr = execute_process(*lookup_commands)
#_log("SRT_%s=%s|%s|%s" % (cve_datasource.key,result_returncode,result_stdout,result_stderr))
if 0 != result_returncode:
result_stdout = str(result_stdout)
v.description = "ERROR(%s):%s" % (result_returncode,result_stderr)
return v
- for line in result_stdout.decode("utf-8").splitlines():
+ for line in result_stdout.splitlines():
try:
name = line[:line.index('=')]
value = line[line.index('=')+1:].replace("[EOL]","\n")
@@ -175,7 +216,7 @@ def readCveDetails_Upstream(cve, cve_datasource):
elif name == 'ATTRIBUTES':
# Returned metadata
lookup_attributes = value
- _log("NOTE:readCveDetails_Upstream:%s:%s" % (v.name,v.cvssV2_severity))
+ #_log("NOTE:readCveDetails_Upstream:%s:%s:%s:%s:" % (v.name,v.cvssV2_severity,cve_datasource.description,v.description[:20]))
# Check for metadata special cases
if cve_datasource.LOOKUP_MISSING in lookup_attributes:
@@ -345,7 +386,7 @@ def summaryCveDetails(cve,cve_sources):
# No data sources
if not cve_main:
- return cve_detail,cve_html
+ return readCveDetails_None(cve),cve_html
# Merge the data into summary record
summaryMerge(cve_detail,cve_main,cve_local,cve_html,'description')
@@ -724,3 +765,59 @@ def publishMarkNone(cve_list,date_start,date_stop):
cvehistory = CveHistory(cve=cve, comment=Update.MARK_UNMARK, date=mid_date, author='SRTool')
cvehistory.save()
+
+class XhrJobRequest(View):
+# from orm.models import Job
+
+ def get(self, request, *args, **kwargs):
+ return HttpResponse()
+
+ def post(self, request, *args, **kwargs):
+ """
+ Job control
+
+ Entry point: /xhr_jobrequest/<project_id>
+ Method: POST
+
+ Args:
+ id: id of job to change
+ jobCancel = job_request_id ...
+ jobDelete = id ...
+
+ Returns:
+ {"error": "ok"}
+ or
+ {"error": <error message>}
+ """
+
+# project = Project.objects.get(pk=kwargs['pid'])
+
+ if 'jobCancel' in request.POST:
+ for i in request.POST['jobCancel'].strip().split(" "):
+ try:
+ job = Job.objects.get(pk=i)
+ job.cancel()
+ except Job.DoesNotExist:
+ return error_response('No such job request id %s' % i)
+
+ return error_response('ok')
+
+ if 'jobDelete' in request.POST:
+ for i in request.POST['jobDelete'].strip().split(" "):
+ try:
+ Job.objects.select_for_update().get(
+ sprint=sprint,
+ pk=i,
+ state__lte=Job.INPROGRESS).delete()
+
+ except Job.DoesNotExist:
+ pass
+ return error_response("ok")
+
+ response = HttpResponse()
+ response.status_code = 500
+ return response
+
+
+
+
diff --git a/lib/srtgui/reports.py b/lib/srtgui/reports.py
index 715c5606..3a7414c6 100644
--- a/lib/srtgui/reports.py
+++ b/lib/srtgui/reports.py
@@ -22,6 +22,10 @@ import os
import logging
from datetime import datetime, timedelta
import csv
+from openpyxl import Workbook
+from openpyxl import load_workbook
+from openpyxl.styles import Border, Side, PatternFill, Font, GradientFill, Alignment
+from openpyxl.utils import get_column_letter
from orm.models import Cve, CveSource, Vulnerability, Investigation, Defect, Product
from orm.models import Package
@@ -34,7 +38,7 @@ from django.db.models import Q
logger = logging.getLogger("srt")
-SRT_BASE_DIR = os.environ['SRT_BASE_DIR']
+SRT_BASE_DIR = os.environ.get('SRT_BASE_DIR', '.')
SRT_REPORT_DIR = '%s/reports' % SRT_BASE_DIR
# quick development/debugging support
@@ -52,6 +56,54 @@ def _log_args(msg, *args, **kwargs):
s += ')'
_log(s)
+###############################################################################
+# Excel/openpyxl common look and feel formatting objects
+#
+
+#pyxl_border_all = Border(left=thin, right=thin, top=thin, bottom=thin) # , outline=True)
+pyxl_thin = Side(border_style="thin")
+pyxl_double = Side(border_style="double")
+pyxl_border_left = Border(left=pyxl_thin)
+pyxl_border_bottom = Border(bottom=pyxl_thin)
+pyxl_border_bottom_left = Border(bottom=pyxl_thin, left=pyxl_thin)
+pyxl_alignment_left = Alignment(horizontal='left')
+pyxl_alignment_right = Alignment(horizontal='right')
+pyxl_alignment_wrap = Alignment(wrap_text=True)
+pyxl_font_bold = Font(bold=True)
+pyxl_font_red = Font(color="A00000",bold=True,size = "13")
+pyxl_font_grn = Font(color="00A000",bold=True,size = "13")
+pyxl_font_blu = Font(color="0000A0",bold=True,size = "13")
+pyxl_font_orn = Font(color="FF6600",bold=True,size = "13")
+pyxl_fill_green = PatternFill(start_color="E0FFF0", end_color="E0FFF0", fill_type = "solid")
+# Warning: the form "PatternFill(bgColor="xxxxxx", fill_type = "solid")" returns black cells
+pyxl_backcolor_red = PatternFill(start_color='FCCDBA', end_color='FCCDBA', fill_type = "solid")
+pyxl_backcolor_orn = PatternFill(start_color='FBEAAB', end_color='FBEAAB', fill_type = "solid")
+pyxl_backcolor_yel = PatternFill(start_color='FCFDC7', end_color='FCFDC7', fill_type = "solid")
+pyxl_backcolor_blu = PatternFill(start_color='C5E2FF', end_color='C5E2FF', fill_type = "solid")
+pyxl_backcolor_grn = PatternFill(start_color='D6EDBD', end_color='D6EDBD', fill_type = "solid")
+pyxl_cve_fills = [pyxl_backcolor_red,pyxl_backcolor_orn,pyxl_backcolor_yel,pyxl_backcolor_blu,None,None,None]
+
+def pyxl_write_cell(ws,row_num,column_num,value,border=None,font=None,fill=None,alignment=None):
+ cell = ws.cell(row=row_num, column=column_num)
+ try:
+ cell.value = value
+ if fill:
+ cell.fill = fill
+ if alignment:
+ cell.alignment = alignment
+ if border:
+ cell.border = border
+ if font:
+ cell.font = font
+ except Exception as e:
+ print("ERROR:(%d,%d):%s" % (row_num,column_num,e))
+ # Optional next column return value
+ return(column_num+1)
+
+###############################################################################
+# Core report support
+#
+
class Report():
def __init__(self, parent_page, *args, **kwargs):
self.parent_page = parent_page
@@ -679,6 +731,8 @@ class CvesReport(Report):
context['report_type_list'] = '\
<option value="summary">CVEs Table</option> \
+ <option value="year_pub_summary">CVE by Year Prefix Summary</option> \
+ <option value="year_summary">CVE by Publish Date Summary</option> \
<option value="cve_defects">CVE to Defects Table</option> \
'
context['report_get_title'] = ''
@@ -692,7 +746,14 @@ class CvesReport(Report):
'
context['report_format_list'] = '\
<input type="radio" name="format" value="txt" checked> Text (comma delimited)<br> \
- <input type="radio" name="format" value="csv"> CSV (tab delimited)<br> \
+ <input type="radio" name="format" value="csv"> CSV \
+ (Separator: \
+ <select name="csv_separator"> \
+ <option value="comma" checked>Comma</option> \
+ <option value="semi">Semi-colon</option> \
+ <option value="tab">Tab</option> \
+ <br> \
+ </select>) \
'
context['report_custom_list'] = '\
CVE name filter = <input type="text" placeholder="e.g. CVE-2018" name="name_filter" size="40"> <br>\
@@ -877,17 +938,20 @@ class CvesReport(Report):
request_POST = self.request.POST
- range = request_POST.get('range', '')
+ range_rec = request_POST.get('range', '')
columns = request_POST.get('columns', '')
format = request_POST.get('format', '')
title = request_POST.get('title', '')
report_type = request_POST.get('report_type', '')
record_list = request_POST.get('record_list', '')
name_filter = request_POST.get('name_filter', '').upper()
+ csv_separator = request_POST.get('csv_separator', 'semi')
report_name = '%s/cves_%s_%s.%s' % (SRT_REPORT_DIR,report_type,datetime.today().strftime('%Y%m%d_%H%M'),format)
if 'csv' == format:
- delimiter = '\t'
+ delimiter = ';'
+ if csv_separator == 'comma': delimiter = ','
+ if csv_separator == 'tab': delimiter = '\t'
else:
delimiter = ','
@@ -896,14 +960,14 @@ class CvesReport(Report):
quotechar='"', quoting=csv.QUOTE_MINIMAL)
if ('summary' == report_type):
self.print_row_summary(writer,True,"all" == columns,None)
- if 'displayed' == range:
+ if 'displayed' == range_rec:
for id in record_list.split(','):
if not id:
continue
cve = Cve.objects.get(id=id)
if not name_filter or (name_filter in cve.name):
self.print_row_summary(writer,False,"all" == columns,cve)
- elif 'all' == range:
+ elif 'all' == range_rec:
if name_filter:
query = Cve.objects.filter(name__contains=name_filter).order_by('name')
else:
@@ -913,14 +977,14 @@ class CvesReport(Report):
if ('cve_defects' == report_type):
self.print_row_cve_defects(writer,'header',"all" == columns,None,None,None,None)
- if 'displayed' == range:
+ if 'displayed' == range_rec:
for id in record_list.split(','):
if not id:
continue
cve = Cve.objects.get(id=id)
if not name_filter or (name_filter in cve.name):
self.print_row_cve_defects(writer,'cve',"all" == columns,cve,None,None,None)
- elif 'all' == range:
+ elif 'all' == range_rec:
if name_filter:
query = Cve.objects.filter(name__contains=name_filter).order_by('name')
else:
@@ -928,6 +992,114 @@ class CvesReport(Report):
for cve in query:
self.print_row_cve_defects(writer,'line',"all" == columns,cve,None,None,None)
+ if report_type in ['year_summary','year_pub_summary']:
+ columns = ["Year", "CVE_Total", "CVE_HIST", "CVE_NEW", "CVE_RES", "CVE_INV", "CVE_VUL", "CVE_NVUL", "Defect_Total", "DEFECT_HIST", "DEFECT_NEW", "DEFECT_RES", "DEFECT_INV", "DEFECT_VUL", "DEFECT_NVUL","BY_PUBLISH"]
+ for i,column in enumerate(columns):
+ csvfile.write("%s%s" % (columns[i],delimiter))
+ csvfile.write("\n")
+
+ summary = {}
+ YEAR_START = 1999
+ YEAR_STOP = 2020
+ for the_year in range(YEAR_START,YEAR_STOP+1):
+ summary[the_year] = {
+ 'CVE_TOTAL':0,
+ 'CVE_HISTORICAL':0,
+ 'CVE_NEW':0,
+ 'CVE_NEW_RESERVED':0,
+ 'CVE_INVESTIGATE':0,
+ 'CVE_VULNERABLE':0,
+ 'CVE_NOT_VULNERABLE':0,
+ 'DEFECT_TOTAL':0,
+ 'DEFECT_HISTORICAL':0,
+ 'DEFECT_NEW':0,
+ 'DEFECT_NEW_RESERVED':0,
+ 'DEFECT_INVESTIGATE':0,
+ 'DEFECT_VULNERABLE':0,
+ 'DEFECT_NOT_VULNERABLE':0,
+ 'PUBLISH_DATE':0,
+ }
+
+ # Gather historgram on CVE status
+ error_count = 0
+ for cve in Cve.objects.all():
+ # Extract the year created
+ if (report_type == 'year_pub_summary') and (not cve.status in [SRTool.HISTORICAL]) and cve.publishedDate:
+ the_year = cve.publishedDate.split('-')[0]
+ summary[the_year]['PUBLISH_DATE'] += 1
+ else:
+ the_year = cve.name.split('-')[1]
+
+ if (not the_year[0].isdigit()) or (the_year < '1999') or (the_year > '2020'):
+ if 10 > error_count:
+ _log('FOO_CVE_YEARLY:%s,%s' % (cve.name, cve.publishedDate))
+ error_count += 1
+ continue
+ the_year = int(the_year)
+
+ # Register the CVE status
+ summary[the_year]['CVE_TOTAL'] += 1
+ if cve.status in [SRTool.HISTORICAL]:
+ summary[the_year]['CVE_HISTORICAL'] += 1
+ if cve.status in [SRTool.NEW,SRTool.NEW_INACTIVE]:
+ summary[the_year]['CVE_NEW'] += 1
+ if cve.status in [SRTool.NEW_RESERVED]:
+ summary[the_year]['CVE_NEW_RESERVED'] += 1
+ if cve.status in [SRTool.INVESTIGATE,SRTool.INVESTIGATE_INACTIVE]:
+ summary[the_year]['CVE_INVESTIGATE'] += 1
+ if cve.status in [SRTool.VULNERABLE,SRTool.VULNERABLE_INACTIVE]:
+ summary[the_year]['CVE_VULNERABLE'] += 1
+ if cve.status in [SRTool.NOT_VULNERABLE,SRTool.NOT_VULNERABLE_INACTIVE]:
+ summary[the_year]['CVE_NOT_VULNERABLE'] += 1
+
+ # Register the releated defects status
+ for cv in cve.cve_to_vulnerability.all():
+ for investigation in cv.vulnerability.vulnerability_investigation.all():
+ for id in investigation.investigation_to_defect.all():
+
+ # Only check defects for current and previously active products
+ if not id.product.get_product_tag('mode') in ['support','develop','eol']:
+ continue
+
+ # Register the defect status
+ summary[the_year]['DEFECT_TOTAL'] += 1
+ if id.defect.srt_status in [SRTool.HISTORICAL]:
+ summary[the_year]['DEFECT_HISTORICAL'] += 1
+ if id.defect.srt_status in [SRTool.NEW,SRTool.NEW_INACTIVE]:
+ summary[the_year]['DEFECT_NEW'] += 1
+ if id.defect.srt_status in [SRTool.NEW_RESERVED]:
+ summary[the_year]['DEFECT_NEW_RESERVED'] += 1
+ if id.defect.srt_status in [SRTool.INVESTIGATE,SRTool.INVESTIGATE_INACTIVE]:
+ summary[the_year]['DEFECT_INVESTIGATE'] += 1
+ if id.defect.srt_status in [SRTool.VULNERABLE,SRTool.VULNERABLE_INACTIVE]:
+ summary[the_year]['DEFECT_VULNERABLE'] += 1
+ if id.defect.srt_status in [SRTool.NOT_VULNERABLE,SRTool.NOT_VULNERABLE_INACTIVE]:
+ summary[the_year]['DEFECT_NOT_VULNERABLE'] += 1
+
+ # Print historgram
+ for the_year in range(YEAR_START,YEAR_STOP+1):
+ csvfile.write("%s%s" % (the_year,delimiter))
+
+ csvfile.write("%s%s" % (summary[the_year]['CVE_TOTAL'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['CVE_HISTORICAL'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['CVE_NEW'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['CVE_NEW_RESERVED'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['CVE_INVESTIGATE'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['CVE_VULNERABLE'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['CVE_NOT_VULNERABLE'],delimiter))
+
+ csvfile.write("%s%s" % (summary[the_year]['DEFECT_TOTAL'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['DEFECT_HISTORICAL'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['DEFECT_NEW'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['DEFECT_NEW_RESERVED'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['DEFECT_INVESTIGATE'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['DEFECT_VULNERABLE'],delimiter))
+ csvfile.write("%s%s" % (summary[the_year]['DEFECT_NOT_VULNERABLE'],delimiter))
+
+ csvfile.write("%s%s" % (summary[the_year]['PUBLISH_DATE'],delimiter))
+ csvfile.write("\n")
+
+
return report_name,os.path.basename(report_name)
class SelectCvesReport(Report):
@@ -1002,6 +1174,8 @@ class SelectCvesReport(Report):
file.write("%s%s" % (cve.description,tab))
file.write("\n")
+
+
return report_name,os.path.basename(report_name)
class VulnerabilitiesReport(Report):
@@ -1458,8 +1632,72 @@ class DefectsReport(Report):
return report_name,os.path.basename(report_name)
+#
+# Products Reports
+#
+
+product_summary = {}
+
+def scan_product_jira(product):
+ global product_summary
+
+ # Totals
+ critical_count = 0
+ high_count = 0
+ medium_count = 0
+ low_count = 0
+ p1_count = 0
+ p2_count = 0
+ p3_count = 0
+ p4_count = 0
+ px_count = 0
+ unresolved_count = 0
+ resolved_count = 0
+ fixed_count = 0
+ wontfix_count = 0
+ withdrawn_count = 0
+ rejected_count = 0
+
+ # Scan the registered defects
+ queryset = product.product_defect.all()
+ for defect in queryset:
+ if Defect.CRITICAL == defect.srt_priority: critical_count += 1
+ elif Defect.HIGH == defect.srt_priority: high_count += 1
+ elif Defect.MEDIUM == defect.srt_priority: medium_count += 1
+ elif Defect.LOW == defect.srt_priority: low_count += 1
+ if Defect.DEFECT_CRITICAL == defect.priority: p1_count += 1
+ elif Defect.DEFECT_HIGH == defect.priority: p2_count += 1
+ elif Defect.DEFECT_MEDIUM == defect.priority: p3_count += 1
+ elif Defect.DEFECT_LOW == defect.priority: p4_count += 1
+ if Defect.DEFECT_UNRESOLVED == defect.resolution: unresolved_count += 1
+ elif Defect.DEFECT_RESOLVED == defect.resolution: resolved_count += 1
+ elif Defect.DEFECT_FIXED == defect.resolution: fixed_count += 1
+ elif Defect.DEFECT_WILL_NOT_FIX == defect.resolution: wontfix_count += 1
+ elif Defect.DEFECT_WITHDRAWN == defect.resolution: withdrawn_count += 1
+ elif Defect.DEFECT_REJECTED == defect.resolution: rejected_count += 1
+
+ # Add this specific entry
+ product_summary[product.long_name] = [
+ critical_count,
+ high_count,
+ medium_count,
+ low_count,
+ p1_count,
+ p2_count,
+ p3_count,
+ p4_count,
+ px_count,
+ unresolved_count,
+ resolved_count,
+ fixed_count,
+ wontfix_count,
+ withdrawn_count,
+ rejected_count,
+ ]
+
class ProductsReport(Report):
"""Report for the Products Page"""
+ global product_summary
def __init__(self, parent_page, *args, **kwargs):
_log_args("REPORT_PRODUCTS_INIT(%s)" % parent_page, *args, **kwargs)
@@ -1471,6 +1709,7 @@ class ProductsReport(Report):
context['report_type_list'] = '\
<option value="summary">Products Table</option> \
+ <option value="status_jira">Product Jira Status</option> \
'
context['report_get_title'] = '1'
context['report_recordrange_list'] = '\
@@ -1480,6 +1719,7 @@ class ProductsReport(Report):
context['report_format_list'] = '\
<input type="radio" name="format" value="txt" checked> Text<br> \
<input type="radio" name="format" value="csv"> CSV<br> \
+ <input type="radio" name="excel" value="excel"> Excel<br> \
'
return context
@@ -1495,44 +1735,131 @@ class ProductsReport(Report):
report_type = request_POST.get('report_type', '')
record_list = request_POST.get('record_list', '')
- report_name = '%s/products_%s_%s.%s' % (SRT_REPORT_DIR,report_type,datetime.today().strftime('%Y%m%d_%H%M'),format)
- with open(report_name, 'w') as file:
+ if 'summary' == report_type:
+ report_name = '%s/products_%s_%s.%s' % (SRT_REPORT_DIR,report_type,datetime.today().strftime('%Y%m%d_%H%M'),format)
+ with open(report_name, 'w') as file:
- if 'csv' == format:
- tab = "\t"
- else:
- tab = ","
-
- if ('summary' == report_type):
if 'csv' == format:
- file.write("Name\tVersion\tProfile\tCPE\tSRT SPE\tInvestigations\tDefects\n")
- if 'txt' == format:
- file.write("Report : Products Table\n")
- file.write("\n")
- file.write("Name,Version,Profile,CPE,SRT SPE,Investigations,Defects\n")
-
- for product in Product.objects.all():
- file.write("%s%s" % (product.name,tab))
- file.write("%s%s" % (product.version,tab))
- file.write("%s%s" % (product.profile,tab))
- file.write("%s%s" % (product.cpe,tab))
- file.write("%s%s" % (product.defect_tags,tab))
- file.write("%s%s" % (product.product_tags,tab))
+ tab = "\t"
+ else:
+ tab = ","
- for i,pi in enumerate(product.product_investigation.all()):
- if i > 0:
- file.write(" ")
- file.write("%s" % (pi.name))
- file.write("%s" % tab)
- for i,pd in enumerate(product.product_defect.all()):
- if i > 0:
- file.write(" ")
- file.write("%s" % (pd.name))
- #file.write("%s" % tab)
- file.write("\n")
+ if ('summary' == report_type):
+ if 'csv' == format:
+ file.write("Name\tVersion\tProfile\tCPE\tSRT SPE\tInvestigations\tDefects\n")
+ if 'txt' == format:
+ file.write("Report : Products Table\n")
+ file.write("\n")
+ file.write("Name,Version,Profile,CPE,SRT SPE,Investigations,Defects\n")
+
+ for product in Product.objects.all():
+ file.write("%s%s" % (product.name,tab))
+ file.write("%s%s" % (product.version,tab))
+ file.write("%s%s" % (product.profile,tab))
+ file.write("%s%s" % (product.cpe,tab))
+ file.write("%s%s" % (product.defect_tags,tab))
+ file.write("%s%s" % (product.product_tags,tab))
+
+ if False:
+ for i,pi in enumerate(product.product_investigation.all()):
+ if i > 0:
+ file.write(" ")
+ file.write("%s" % (pi.name))
+ file.write("%s" % tab)
+ for i,pd in enumerate(product.product_defect.all()):
+ if i > 0:
+ file.write(" ")
+ file.write("%s" % (pd.name))
+ #file.write("%s" % tab)
+ file.write("\n")
+ elif 'status_jira' == report_type:
+ def resolution_color(i):
+ if 0 == i: fill = pyxl_backcolor_orn
+ elif 1 == i: fill = pyxl_backcolor_grn
+ elif 2 == i: fill = pyxl_backcolor_grn
+ elif 3 == i: fill = pyxl_backcolor_yel
+ elif 4 == i: fill = pyxl_backcolor_blu
+ elif 5 == i: fill = pyxl_backcolor_blu
+ else: fill = None
+ return(fill)
+
+ for product in Product.objects.all():
+ scan_product_jira(product)
+
+ format = "xlsx"
+ report_name = '%s/products_jira_%s_%s.%s' % (SRT_REPORT_DIR,report_type,datetime.today().strftime('%Y%m%d_%H%M'),format)
+ wb = Workbook()
+ ws = wb.active
+ ws.title = "Product Jira Summary"
+ ws.column_dimensions[get_column_letter(1)].width = 30
+
+ row = 1
+ first_row = 2
+
+ col = 1
+ for header in ('Product','Critical','High','Medium','Low','P1','P2','P3','P4','Unresolved','Resolved','Fixed',"Won't Fix",'Withdrawn','Rejected'):
+ border = pyxl_border_bottom_left if (col in (2,6,10)) else pyxl_border_bottom
+ pyxl_write_cell(ws,row,col,header,border=border)
+ col += 1
+ row += 1
+
+ for product in Product.objects.order_by("order"):
+ key = product.long_name
+ scan_product_jira(product)
+ pyxl_write_cell(ws,row,1,key)
+ # CVE Severity
+ col_excel = 2
+ col_summary = 1
+ for i in range(0,4):
+ border = pyxl_border_left if (i==0) else None
+ value = product_summary[key][col_summary+i]
+ pyxl_write_cell(ws,row,col_excel+i,value,border=border,fill=pyxl_cve_fills[i] if value else None)
+
+ # Jira Priority
+ col_excel = 6
+ col_summary = 5
+ for i in range(0,4):
+ border = pyxl_border_left if (i==0) else None
+ value = product_summary[key][col_summary+i]
+ pyxl_write_cell(ws,row,col_excel+i,value,border=border,fill=pyxl_cve_fills[i] if value else None)
+ # Jira Resolution
+ col_excel = 10
+ col_summary = 9
+ for i in range(0,6):
+ border = pyxl_border_left if (i==0) else None
+ value = product_summary[key][col_summary+i]
+ pyxl_write_cell(ws,row,col_excel+i,value,border=border,fill=resolution_color(i) if value else None)
+ row += 1
+
+ # Sums
+ row -= 1
+ for i in range(1,16):
+ border = pyxl_border_bottom_left if (i in (2,6,10)) else pyxl_border_bottom
+ ws.cell(row=row,column=i).border=border
+ row += 1
+ letters = (' ','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q')
+ for col_excel in range(2,16):
+ # CVE Severity
+ col_excel = 2
+ for i in range(0,4):
+ pyxl_write_cell(ws,row,col_excel+i,'=SUM(%s%d:%s%d)' % (letters[col_excel+i],first_row,letters[col_excel+i],row-1),fill=pyxl_cve_fills[i])
+ # Jira Priority
+ col_excel = 6
+ for i in range(0,4):
+ pyxl_write_cell(ws,row,col_excel+i,'=SUM(%s%d:%s%d)' % (letters[col_excel+i],first_row,letters[col_excel+i],row-1),fill=pyxl_cve_fills[i])
+ # Jira Resolution
+ col_excel = 10
+ for i in range(0,6):
+ pyxl_write_cell(ws,row,col_excel+i,'=SUM(%s%d:%s%d)' % (letters[col_excel+i],first_row,letters[col_excel+i],row-1),fill=resolution_color(i))
+
+ wb.save(report_name)
return report_name,os.path.basename(report_name)
+#
+# CVE Reports
+#
+
class PublishCveReport(Report):
"""Report for the Publish Cve Page"""
diff --git a/lib/srtgui/static/js/libtoaster.js b/lib/srtgui/static/js/libtoaster.js
index 6f9b5d0f..b09511a1 100644
--- a/lib/srtgui/static/js/libtoaster.js
+++ b/lib/srtgui/static/js/libtoaster.js
@@ -81,57 +81,20 @@ var libtoaster = (function () {
});
}
- /* startABuild:
- * url: xhr_buildrequest or null for current project
- * targets: an array or space separated list of targets to build
+ /* cancelAJob:
+ * url: xhr_jobrequest url or null for current scrum
+ * jobRequestIds: space separated list of build request ids
* onsuccess: callback for successful execution
* onfail: callback for failed execution
*/
- function _startABuild (url, targets, onsuccess, onfail) {
-
+ function _cancelAJob(url, jobRequestIds, onsuccess, onfail){
if (!url)
- url = libtoaster.ctx.xhrBuildRequestUrl;
-
- /* Flatten the array of targets into a space spearated list */
- if (targets instanceof Array){
- targets = targets.reduce(function(prevV, nextV){
- return prev + ' ' + next;
- });
- }
+ url = libtoaster.ctx.xhrJobRequestUrl;
$.ajax( {
type: "POST",
url: url,
- data: { 'targets' : targets },
- headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
- success: function (_data) {
- if (_data.error !== "ok") {
- console.warn(_data.error);
- } else {
- if (onsuccess !== undefined) onsuccess(_data);
- }
- },
- error: function (_data) {
- console.warn("Call failed");
- console.warn(_data);
- if (onfail) onfail(data);
- } });
- }
-
- /* cancelABuild:
- * url: xhr_buildrequest url or null for current project
- * buildRequestIds: space separated list of build request ids
- * onsuccess: callback for successful execution
- * onfail: callback for failed execution
- */
- function _cancelABuild(url, buildRequestIds, onsuccess, onfail){
- if (!url)
- url = libtoaster.ctx.xhrBuildRequestUrl;
-
- $.ajax( {
- type: "POST",
- url: url,
- data: { 'buildCancel': buildRequestIds },
+ data: { 'jobCancel': jobRequestIds },
headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
success: function (_data) {
if (_data.error !== "ok") {
@@ -148,7 +111,7 @@ var libtoaster = (function () {
});
}
- function _getMostRecentBuilds(url, onsuccess, onfail) {
+ function _getMostRecentJobs(url, onsuccess, onfail) {
$.ajax({
url: url,
type: 'GET',
@@ -163,80 +126,6 @@ var libtoaster = (function () {
});
}
- /* Get a project's configuration info */
- function _getProjectInfo(url, onsuccess, onfail){
- $.ajax({
- type: "GET",
- url: url,
- headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
- success: function (_data) {
- if (_data.error !== "ok") {
- console.warn(_data.error);
- } else {
- if (onsuccess !== undefined) onsuccess(_data);
- }
- },
- error: function (_data) {
- console.warn(_data);
- if (onfail) onfail(_data);
- }
- });
- }
-
- /* Properties for data can be:
- * layerDel (csv)
- * layerAdd (csv)
- * projectName
- * projectVersion
- * machineName
- */
- function _editCurrentProject(data, onSuccess, onFail){
- $.ajax({
- type: "POST",
- url: libtoaster.ctx.xhrProjectUrl,
- data: data,
- headers: { 'X-CSRFToken' : $.cookie('csrftoken')},
- success: function (data) {
- if (data.error != "ok") {
- console.log(data.error);
- if (onFail !== undefined)
- onFail(data);
- } else {
- if (onSuccess !== undefined)
- onSuccess(data);
- }
- },
- error: function (data) {
- console.log("Call failed");
- console.log(data);
- }
- });
- }
-
- function _getLayerDepsForProject(url, onSuccess, onFail){
- /* Check for dependencies not in the current project */
- $.getJSON(url,
- { format: 'json' },
- function(data) {
- if (data.error != "ok") {
- console.log(data.error);
- if (onFail !== undefined)
- onFail(data);
- } else {
- var deps = {};
- /* Filter out layer dep ids which are in the
- * project already.
- */
- deps.list = data.layerdeps.list.filter(function(layerObj){
- return (data.projectlayers.lastIndexOf(layerObj.id) < 0);
- });
-
- onSuccess(deps);
- }
- }, function() {
- console.log("E: Failed to make request");
- });
- }
/* parses the query string of the current window.location to an object */
function _parseUrlParams() {
@@ -469,13 +358,9 @@ var libtoaster = (function () {
enableAjaxLoadingTimer: _enableAjaxLoadingTimer,
disableAjaxLoadingTimer: _disableAjaxLoadingTimer,
reload_params : reload_params,
- startABuild : _startABuild,
- cancelABuild : _cancelABuild,
- getMostRecentBuilds: _getMostRecentBuilds,
+ cancelAJob : _cancelAJob,
+ getMostRecentJobs: _getMostRecentJobs,
makeTypeahead : _makeTypeahead,
- getProjectInfo: _getProjectInfo,
- getLayerDepsForProject : _getLayerDepsForProject,
- editCurrentProject : _editCurrentProject,
debug: false,
parseUrlParams : _parseUrlParams,
dumpsUrlParams : _dumpsUrlParams,
diff --git a/lib/srtgui/static/js/mrjsection.js b/lib/srtgui/static/js/mrjsection.js
new file mode 100755
index 00000000..800f0e6f
--- /dev/null
+++ b/lib/srtgui/static/js/mrjsection.js
@@ -0,0 +1,131 @@
+
+function mrjSectionInit(ctx){
+ $('#latest-jobs').on('click', '.cancel-job-btn', function(e){
+ e.stopImmediatePropagation();
+ e.preventDefault();
+
+ var url = $(this).data('request-url');
+ var jobReqIds = $(this).data('jobrequest-id');
+
+ libtoaster.cancelAJob(url, jobReqIds, function () {
+ alert("CANCEL JOB");
+ window.location.reload();
+ }, null);
+ });
+
+ // cached version of jobData, so we can determine whether a job has
+ // changed since it was last fetched, and update the DOM appropriately
+ var jobData = {};
+
+ // returns the cached version of this job, or {} is there isn't a cached one
+ function getCached(job) {
+ return jobData[job.id] || {};
+ }
+
+ // returns true if a job's state changed to "Success", "Errors"
+ // or "Cancelled" from some other value
+ function jobFinished(job) {
+ var cached = getCached(job);
+ return cached.state &&
+ cached.state !== job.state &&
+ (job.state == 'Success' || job.state == 'Errors' ||
+ job.state == 'Cancelled');
+ }
+
+ // returns true if the state changed
+ function stateChanged(job) {
+ var cached = getCached(job);
+ return (cached.state !== job.state);
+ }
+
+ // returns true if the tasks_complete_percentage changed
+ function tasksProgressChanged(job) {
+ var cached = getCached(job);
+ var a = cached.tasks_complete_percentage;
+ var b = job.tasks_complete_percentage;
+ var c = cached.tasks_complete_percentage !== job.tasks_complete_percentage;
+ return (cached.tasks_complete_percentage !== job.tasks_complete_percentage);
+ }
+
+ // Auto-refresh 1500 ms AFTER its last successful refresh, to avoid refresh race conditions
+ function refreshMostRecentJobs(){
+ libtoaster.getMostRecentJobs(
+ libtoaster.ctx.mostRecentJobsUrl,
+
+ // success callback
+ function (data) {
+ var job;
+ var tmpl;
+ var container;
+ var selector;
+ var colourClass;
+ var elements;
+
+ for (var i = 0; i < data.length; i++) {
+ job = data[i];
+
+ var jobEle = document.getElementById("job-instance-"+job.id);
+ if (null == jobEle) {
+ // Job's display instance does not exist, so force refresh of page's Job MRU
+ // DISABLE THESE LINES TO Avoid a race condition loop
+// alert("NO JOB");
+ setTimeout(() => { console.log("NO_JOB_YET_DELAY!"); }, 2000);
+ window.location.reload();
+ return;
+ }
+ else if (jobFinished(job)) {
+ // a job finished: reload the whole page so that the job
+ // shows up in the jobs table
+// alert("DONE JOB");
+ window.location.reload();
+ return;
+ }
+ else if (stateChanged(job)) {
+ // update the whole template
+ job.warnings_pluralise = (job.warnings !== 1 ? 's' : '');
+ job.errors_pluralise = (job.errors !== 1 ? 's' : '');
+
+ tmpl = $.templates("#job-template");
+
+ html = $(tmpl.render(job));
+
+ selector = '[data-latest-job-result="' + job.id + '"] ' +
+ '[data-role="job-status-container"]';
+ container = $(selector);
+
+ // initialize bootstrap tooltips in the new HTML
+ html.find('span.glyphicon-question-sign').tooltip();
+
+ container.html(html);
+ }
+ else if (tasksProgressChanged(job)) {
+ // update the task progress text
+ selector = '#job-pc-done-' + job.id;
+ $(selector).html(job.tasks_complete_percentage);
+ selector = '#job-message-done-' + job.id;
+ $(selector).html(job.targets);
+
+ // update the task progress bar
+ selector = '#job-pc-done-bar-' + job.id;
+ $(selector).width(job.tasks_complete_percentage + '%');
+ }
+
+ jobData[job.id] = job;
+ }
+ },
+
+ // fail callback
+ function (data) {
+ console.error(data);
+ }
+ );
+ window.setTimeout(refreshMostRecentJobs, 1500);
+ var msg = "REFRESH:"+Date.now();
+ console.log(msg);
+ }
+
+ // window.setInterval(refreshMostRecentJobs, 1500);
+
+ // Self refresh every 1500 ms
+ refreshMostRecentJobs();
+}
diff --git a/lib/srtgui/static/js/table.js b/lib/srtgui/static/js/table.js
index fd241aa6..9d3030d7 100644
--- a/lib/srtgui/static/js/table.js
+++ b/lib/srtgui/static/js/table.js
@@ -1,11 +1,14 @@
'use strict';
-function tableInit(ctx){
+function tableInit(ctx, SelectedFilterVal = ""){
if (ctx.url.length === 0) {
throw "No url supplied for retreiving data";
}
+ var clearallfilterBtn = $("#clear-all-filter");
+ var lstRemoveCurrent = []
+ let result = ""
var tableChromeDone = false;
var tableTotal = 0;
@@ -32,6 +35,28 @@ function tableInit(ctx){
tableParams.limit = Number(tableParams.limit);
tableParams.page = Number(tableParams.page);
+ if (tableParams.filter != null && SelectedFilterVal != ""){
+ lstFilterval.splice(lstFilterval.indexOf(SelectedFilterVal),1)
+
+ lstRemoveCurrent = tableParams.filter.replace(/%20/g, " ").split(",");
+ lstRemoveCurrent.splice(lstRemoveCurrent.indexOf(SelectedFilterVal),1)
+ if (lstRemoveCurrent.length > 1){
+ tableParams.filter = lstRemoveCurrent.join(",")
+ }
+ else{
+ tableParams.filter = lstRemoveCurrent[0]
+ clearallfilterBtn.tooltip('destroy');
+ clearallfilterBtn.removeClass("btn-primary");
+ }
+
+ }
+ else if(tableParams.filter != null && SelectedFilterVal == ""){
+ tableParams.filter = null;
+ lstFilterval = [];
+ clearallfilterBtn.tooltip('destroy');
+ clearallfilterBtn.removeClass("btn-primary");
+ }
+
loadData(tableParams);
// clicking on this set of elements removes the search
@@ -263,10 +288,9 @@ function tableInit(ctx){
filterBtn.prop('id', col.filter_name);
filterBtn.click(filterOpenClicked);
- /* If we're currently being filtered setup the visial indicator */
+ /* If we're currently being filtered setup the visual indicator */
if (tableParams.filter &&
- tableParams.filter.match('^'+col.filter_name)) {
-
+ tableParams.filter.includes(col.filter_name)) {
filterBtnActive(filterBtn, true);
}
header.append(filterBtn);
@@ -310,24 +334,36 @@ function tableInit(ctx){
}
/* Toggles the active state of the filter button */
- function filterBtnActive(filterBtn, active){
+ function filterBtnActive(filterBtn, active, ActiveButton = ""){
+// var clearallfilterBtn = $("#clear-all-filter");
if (active) {
filterBtn.removeClass("btn-link");
filterBtn.addClass("btn-primary");
+ if(lstFilterval.length > 1){
+ clearallfilterBtn.addClass("btn-primary");
+ clearallfilterBtn.tooltip(
+ {
+ html: true,
+ title: '<button class="btn btn-sm btn-primary" onClick=\'$("#clear-filter-btn-'+ ctx.tableName +'").click();\'>Clear filter</button>',
+ placement: 'bottom',
+ delay: {
+ hide: 1500,
+ show: 400,
+ },
+ }
+ );
+ };
+
filterBtn.tooltip({
html: true,
- title: '<button class="btn btn-sm btn-primary" onClick=\'$("#clear-filter-btn-'+ ctx.tableName +'").click();\'>Clear filter</button>',
+ title: '<button class="btn btn-sm btn-primary" onClick=\'ClearFilter("'+ ActiveButton +'").click();\'>Clear filter</button>',
placement: 'bottom',
delay: {
hide: 1500,
show: 400,
},
});
- } else {
- filterBtn.removeClass("btn-primary");
- filterBtn.addClass("btn-link");
- filterBtn.tooltip('destroy');
}
}
@@ -627,16 +663,29 @@ function tableInit(ctx){
return action;
}
+ function table_objToString (obj) {
+ let str = '';
+ for (const [p, val] of Object.entries(obj)) {
+ str += `${p}=${val},`;
+ }
+ return str;
+ }
+
function filterOpenClicked(){
var filterName = $(this).data('filter-name');
/* We need to pass in the current search so that the filter counts take
- * into account the current search term
+ * into account the current search term.
+ *
+ * Also, pass all of the URL params via the tableParams object, in case
+ * the user's table needs custom params for processing.
*/
+
var params = {
'name' : filterName,
'search': tableParams.search,
'cmd': 'filterinfo',
+ 'tableParams': table_objToString(tableParams),
};
$.ajax({
@@ -816,11 +865,20 @@ function tableInit(ctx){
$("#clear-filter-btn-"+ctx.tableName).click(function(e){
e.preventDefault();
- var filterBtn = $("#" + tableParams.filter.split(":")[0]);
- filterBtnActive(filterBtn, false);
-
+ // var filterBtn = $("#" + tableParams.filter.split(":")[0]);
+ //filterBtnActive(filterBtn, false);
+ for(var i = 0, size = lstFilterval.length; i < size ; i++){
+ var item = lstFilterval[i];
+ var filterBtn = $("#" + item.split(":")[0]);
+ filterBtn.tooltip('destroy');
+ filterBtn.removeClass("btn-primary");
+ }
tableParams.filter = null;
+ lstFilterval = [];
loadData(tableParams);
+
+ clearallfilterBtn.tooltip('destroy');
+ clearallfilterBtn.removeClass("btn-primary");
});
$("#filter-modal-form-"+ctx.tableName).submit(function(e){
@@ -834,20 +892,33 @@ function tableInit(ctx){
// checked radio button
var checkedFilter = $(this).find("input[name='filter']:checked");
- tableParams.filter = checkedFilter.val();
+// # True? vvvv FOO
+// tableParams.filter = checkedFilter.val();
// hidden field holding the value for the checked filter
var checkedFilterValue = $(this).find("input[data-value-for='" +
tableParams.filter + "']");
tableParams.filter_value = checkedFilterValue.val();
+ if (lstFilterval.indexOf(checkedFilter.val()) == -1){
+ lstFilterval.push(checkedFilter.val());
+ tableParams.filter = lstFilterval.join(",")
+ }
+ else{
+ tableParams.filter =lstFilterval.join(",")
+ }
+ //tableParams.filter = checkedFilter.val() //lstFilterval
+ // hidden field holding the value for the checked filter
+ // tableParams.filter_value = checkedFilterValue.val();
+ var currentFilterValue = String(lstFilterval.slice(-1))
+
/* All === remove filter */
- if (tableParams.filter.match(":all$")) {
+ if (currentFilterValue.match(":all$")) {
tableParams.filter = null;
tableParams.filter_value = null;
} else {
- var filterBtn = $("#" + tableParams.filter.split(":")[0]);
- filterBtnActive(filterBtn, true);
+ var filterBtn = $("#" + currentFilterValue.split(":")[0]);
+ filterBtnActive(filterBtn, true,currentFilterValue);
}
loadData(tableParams);
diff --git a/lib/srtgui/static/js/typeahead_affected_components.js b/lib/srtgui/static/js/typeahead_affected_components.js
new file mode 100755
index 00000000..d8f5d25e
--- /dev/null
+++ b/lib/srtgui/static/js/typeahead_affected_components.js
@@ -0,0 +1,9 @@
+'use strict';
+
+function autocompInit() {
+ var newComponentInput = $("#input-isvulnerable-components");
+
+ libtoaster.makeTypeahead(newComponentInput,
+ libtoaster.ctx.recipeTypeAheadUrl, {}, function (item) {});
+
+} \ No newline at end of file
diff --git a/lib/srtgui/tables.py b/lib/srtgui/tables.py
index b8ff6f67..dfb0571b 100644
--- a/lib/srtgui/tables.py
+++ b/lib/srtgui/tables.py
@@ -4,7 +4,7 @@
#
# Security Response Tool Implementation
#
-# Copyright (C) 2017 Wind River Systems
+# Copyright (C) 2017-2021 Wind River Systems
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
@@ -19,12 +19,58 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# NOTICE: Important ToasterTable implementation concepts and limitations
+#
+# 1) The order of table method execution:
+#
+# a) __init__
+# b) get_context_data
+# c) __init__ (second call reason unknown)
+# d) setup_queryset
+# e) setup_filters (if present)
+# f) setup_columns
+# g) apply_row_customization (if present)
+#
+# 2) Named URL path arguments from "urls.py" are accessible via kwargs
+# WARNING: these values not NOT available in "__init__"
+#
+# Example:
+# urls.ps : url(r'^foo/(?P<my_value>\d+)$',
+# tables.py: my_value = int(kwargs['my_value'])
+#
+# 3) Named URL query arguments the table's url are accessible via the request
+#
+# Example:
+# url : http://.../foo/bar/42605?my_value=25
+# tables.py: my_value = self.request.GET.get('my_value','0')
+#
+# 4) The context[] values are NOT present in the "setup_columns" context
+# They must be explicitly implemented into the column data
+#
+# 5) The HTML page's templatetags are NOT present in the "setup_columns" context
+# They must be explicitly added into the template code
+#
+# Example:
+# static_data_template = '''
+# {% load jobtags %}<span onclick="toggle_select(\'box_{{data.id}}\');">{{data.recommend|recommend_display}}</span>
+# '''
+#
+# WARNING: because there is no context (#4), you cannot for example use dictionary lookup filters
+# use apply_row_customization() method instead, and set the self.dict_name in setup_columns()
+#
+
+import os
import re
import json
+from datetime import timedelta, datetime
+import traceback
from srtgui.widgets import ToasterTable
+from srtgui.api import execute_process
from orm.models import SRTool
from orm.models import Cve, Vulnerability, Investigation, CweTable, Product
+from orm.models import CveAccess
from orm.models import Package
from orm.models import CpeTable, CpeFilter, Defect, DataSource, SrtSetting
from orm.models import PublishPending
@@ -32,8 +78,11 @@ from orm.models import Notify, NotifyCategories
from orm.models import CveHistory, VulnerabilityHistory, InvestigationHistory, DefectHistory
from orm.models import PublishSet
from orm.models import ErrorLog
+from orm.models import Job
from users.models import UserSafe
+from django.contrib.auth.models import AnonymousUser
+
from django.db.models import Q
from srtgui.tablefilter import TableFilter
@@ -105,7 +154,23 @@ class CvesTable(ToasterTable):
is_recommend.add_action(exec_p3)
self.add_filter(is_recommend)
- def setup_queryset(self, *args, **kwargs):
+ # Is Public filter
+ is_public = TableFilter(name="is_public",
+ title="Filter CVEs by 'Public'")
+ exec_public = TableFilterActionToggle(
+ "public",
+ "Public",
+ Q(public=True))
+ exec_private = TableFilterActionToggle(
+ "private",
+ "Private",
+ Q(public=False))
+ is_public.add_action(exec_public)
+ is_public.add_action(exec_private)
+ self.add_filter(is_public)
+
+
+ def orig_setup_queryset(self, *args, **kwargs):
self.queryset = \
Cve.objects.all()
@@ -115,6 +180,27 @@ class CvesTable(ToasterTable):
self.queryset = self.queryset.order_by(self.default_orderby)
+ def setup_queryset(self, *args, **kwargs):
+ _log("FOO_PRIVATE0:%s:" % (self.request.user))
+ if UserSafe.is_admin(self.request.user):
+ self.queryset = Cve.objects.all()
+ else:
+ # Add all public records
+ self.queryset = Cve.objects.filter(public = True)
+ if not isinstance(self.request.user,AnonymousUser):
+ # Add all user accessible private records
+ for cve_private_access in CveAccess.objects.filter(user=self.request.user):
+ cve = cve_private_access.cve
+ _log("FOO_PRIVATE1:%s:%s" % (self.request.user.username,cve.name))
+ private_queryset = Cve.objects.filter(name=cve.name)
+ _log("FOO_PRIVATE2:%s:%s" % (self.request.user.username,cve.name))
+ self.queryset |= private_queryset
+ _log("FOO_PRIVATE3:%s:%s" % (self.request.user.username,cve.name))
+
+ _log("FOO_PRIVATE4")
+ self.queryset = self.queryset.order_by(self.default_orderby)
+ _log("FOO_PRIVATE5")
+
def setup_columns(self, *args, **kwargs):
@@ -145,6 +231,15 @@ class CvesTable(ToasterTable):
static_data_template="{{data.get_status_text}}"
)
+ self.add_column(title="Public",
+ field_name="public",
+ hideable=True,
+ orderable=True,
+ filter_name="is_public",
+ static_data_name="public",
+ static_data_template="{{data.get_public_text}}"
+ )
+
score_link_template = '''
{% if 0 == data.recommend %}0{% else %}{{data.recommend}}{% endif %}
'''
@@ -313,7 +408,7 @@ class SelectCveTable(ToasterTable):
return context
- def apply_row_customization(self, row):
+ def apply_row_customization(self, row, **kwargs):
data = super(SelectCveTable, self).apply_row_customization(row)
# data:dict_keys(['rows', 'total', 'default_orderby', 'error', 'columns'])
@@ -417,11 +512,18 @@ class SelectCveTable(ToasterTable):
def setup_columns(self, *args, **kwargs):
+ self.add_column(title="Id (creation order)",
+ field_name="id",
+ hideable=True,
+ hidden=True,
+ orderable=True,
+ )
+
self.add_column(title="Select",
field_name="Select",
hideable=False,
static_data_name="select",
- static_data_template='<input type="checkbox" id="box_{{data.id}}" name="{{data.name}}" />',
+ static_data_template='<input type="checkbox" class="selectbox" id="box_{{data.id}}" name="{{data.name}}" />',
)
self.add_column(title="Status",
@@ -443,7 +545,7 @@ class SelectCveTable(ToasterTable):
)
recommend_link_template = '''
- {% load projecttags %}<span onclick="toggle_select(\'box_{{data.id}}\');">{{data.recommend|recommend_display}}</span>
+ {% load jobtags %}<span onclick="toggle_select(\'box_{{data.id}}\');">{{data.recommend|recommend_display}}</span>
'''
self.add_column(title="Recommendation",
hideable=False,
@@ -497,20 +599,20 @@ class SelectCveTable(ToasterTable):
)
self.add_column(title="publishedDate",
- field_name="publisheddate",
+ field_name="publishedDate",
hideable=True,
hidden=True,
orderable=True,
- static_data_name="publisheddate",
+ static_data_name="publishedDate",
static_data_template='<span onclick="toggle_select(\'box_{{data.id}}\');">{{data.publishedDate}}</span>',
)
self.add_column(title="lastModifiedDate",
- field_name="lastmodifieddate",
+ field_name="lastModifiedDate",
hideable=True,
hidden=True,
orderable=True,
- static_data_name="lastmodifieddate",
+ static_data_name="lastModifiedDate",
static_data_template='<span onclick="toggle_select(\'box_{{data.id}}\');">{{data.lastModifiedDate}}</span>',
)
@@ -602,7 +704,7 @@ class DefectsTable(ToasterTable):
is_srt_priority.add_action(TableFilterActionToggle(
SRTool.SRT_PRIORITY[priority][1].lower().replace(' ','_'),
SRTool.SRT_PRIORITY[priority][1],
- Q(priority=SRTool.SRT_PRIORITY[priority][0]))
+ Q(srt_priority=SRTool.SRT_PRIORITY[priority][0]))
)
self.add_filter(is_srt_priority)
@@ -613,7 +715,7 @@ class DefectsTable(ToasterTable):
is_srt_status.add_action(TableFilterActionToggle(
SRTool.SRT_STATUS[status][1].lower().replace(' ','_'),
SRTool.SRT_STATUS[status][1],
- Q(status=SRTool.SRT_STATUS[status][0]))
+ Q(srt_status=SRTool.SRT_STATUS[status][0]))
)
self.add_filter(is_srt_status)
@@ -624,7 +726,7 @@ class DefectsTable(ToasterTable):
is_srt_outcome.add_action(TableFilterActionToggle(
Defect.SRT_OUTCOME[status][1].lower().replace(' ','_'),
Defect.SRT_OUTCOME[status][1],
- Q(status=Defect.SRT_OUTCOME[status][0]))
+ Q(srt_outcome=Defect.SRT_OUTCOME[status][0]))
)
self.add_filter(is_srt_outcome)
@@ -778,7 +880,7 @@ class DefectsTable(ToasterTable):
orderable=True,
field_name="date_created",
static_data_name="date_created",
- static_data_template='{{date_created}}'
+ static_data_template='{{data.date_created}}'
)
self.add_column(title="Defect Update",
hideable=True,
@@ -1370,6 +1472,22 @@ class VulnerabilitiesTable(ToasterTable):
is_priority.add_action(exec_is_critical)
self.add_filter(is_priority)
+ # Is Public filter
+ is_public = TableFilter(name="is_public",
+ title="Filter Vulnerabilities by 'Public'")
+ exec_public = TableFilterActionToggle(
+ "public",
+ "Public",
+ Q(public=True))
+ exec_private = TableFilterActionToggle(
+ "private",
+ "Private",
+ Q(public=False))
+ is_public.add_action(exec_public)
+ is_public.add_action(exec_private)
+ self.add_filter(is_public)
+
+
def setup_queryset(self, *args, **kwargs):
self.queryset = \
Vulnerability.objects.all()
@@ -1393,6 +1511,15 @@ class VulnerabilitiesTable(ToasterTable):
static_data_template=id_link_template,
)
+ self.add_column(title="Public",
+ field_name="public",
+ hideable=True,
+ orderable=True,
+ filter_name="is_public",
+ static_data_name="public",
+ static_data_template="{{data.get_public_text}}"
+ )
+
# !!! HACK: 'vc.cve.name' is returning '%s' when it is supposed to be null !!!
cve_link_template = '''
{% for vc in data.vulnerability_to_cve.all %}
@@ -1592,6 +1719,20 @@ class InvestigationsTable(ToasterTable):
is_priority.add_action(exec_is_critical)
self.add_filter(is_priority)
+ # Is Public filter
+ is_public = TableFilter(name="is_public",
+ title="Filter CVEs by 'Public'")
+ exec_public = TableFilterActionToggle(
+ "public",
+ "Public",
+ Q(public=True))
+ exec_private = TableFilterActionToggle(
+ "private",
+ "Private",
+ Q(public=False))
+ is_public.add_action(exec_public)
+ is_public.add_action(exec_private)
+ self.add_filter(is_public)
# Product filter
is_product = TableFilter(name="is_product",
@@ -1627,6 +1768,16 @@ class InvestigationsTable(ToasterTable):
static_data_template=id_link_template,
)
+ self.add_column(title="Public",
+ field_name="public",
+ hideable=True,
+ hidden=True,
+ orderable=True,
+ filter_name="is_public",
+ static_data_name="public",
+ static_data_template="{{data.get_public_text}}"
+ )
+
defect_link_template = '''
{% for ij in data.investigation_to_defect.all %}
{% if not forloop.first %} {% endif %}<a href="{% url 'defect_name' ij.defect.name %}">{{ij.defect.name}} </a>
@@ -1731,6 +1882,7 @@ class SourcesTable(ToasterTable):
def get_context_data(self, **kwargs):
context = super(SourcesTable, self).get_context_data(**kwargs)
+ context['mru'] = Job.get_recent()
return context
def setup_queryset(self, *args, **kwargs):
@@ -1740,6 +1892,20 @@ class SourcesTable(ToasterTable):
self.queryset = self.queryset.order_by(self.default_orderby)
def setup_columns(self, *args, **kwargs):
+ # Get the 'next update' values
+ source_update = {}
+ cmnds = [os.path.join(os.environ.get('SRT_BASE_DIR'),'./bin/common/srtool_update.py'),'--fetch-updates-dhm']
+ result_returncode,result_stdout,result_stderr = execute_process(*cmnds)
+ if 0 != result_returncode:
+ _log("ERROR:FETCH-UPDATES-DHM:%s" % result_stderr)
+ for line in result_stdout.splitlines():
+ try:
+ name = line[:line.index(',')]
+ value = line[line.index(',')+1:].strip()
+ source_update[name] = value
+ except:
+ continue
+ self.source_update = source_update
self.add_column(title="ID",
hideable=True,
@@ -1779,6 +1945,8 @@ class SourcesTable(ToasterTable):
hidden=True,
orderable=False,
field_name="attributes",
+ static_data_name="attributes",
+ static_data_template='''<span id="attr_{{data.id}}">{{data.attributes}}</span>''',
)
self.add_column(title="Description",
@@ -1808,11 +1976,14 @@ class SourcesTable(ToasterTable):
field_name="lookup",
)
+ last_modified_date_template = '{% load jobtags %}'
+ last_modified_date_template += '{{ data.lastModifiedDate|shift_timezone:"%d" }}' % self.request.user.get_timezone_offset
self.add_column(title="Data Modified",
help_text="Last upstream date",
hideable=False,
orderable=True,
- field_name="lastModifiedDate",
+ static_data_name="str_lastModifiedDate",
+ static_data_template=last_modified_date_template,
)
updated_template = '''
@@ -1831,7 +2002,8 @@ class SourcesTable(ToasterTable):
{% if data.update %}{{data.get_frequency_text}}{% else %}({{data.get_frequency_text}}){% endif %}
'''
self.add_column(title="Update Freq.",
- hideable=False,
+ hideable=True,
+ hidden=True,
orderable=True,
field_name="update_frequency",
static_data_name="update_frequency",
@@ -1855,6 +2027,54 @@ class SourcesTable(ToasterTable):
field_name="cve_filter",
)
+# update_now_template = '''
+# {% load jobtags %}<span id="next_{{data.id}}">{{source_update|get_dict_value:data.id}}{% if data.update %}<button class="execute run-update-job" style="float:right;" x-data="{{data.id}}">Now</button>{% endif %}</span>
+# '''
+ update_now_template = '''{{data.id}}|{{data.attributes}}'''
+ self.add_column(title="Update Next (D|H:M:S)",
+ hideable=True,
+ hidden=False,
+ static_data_name="update_now",
+ static_data_template=update_now_template,
+ )
+
+ source_enabled_template='''
+ <input type="checkbox" class="source-enabled" name="source_enabled" x-data="{{data.id}}" {% if "DISABLE " in data.attributes %}checked{% endif %}>
+ <label for="audit_top_artifact"> Disabled</label><br>
+ '''
+ self.add_column(title="Disable",
+ hideable=True,
+ hidden=True,
+ static_data_name="source_enabled",
+ static_data_template=source_enabled_template,
+ )
+
+ def apply_row_customization(self, row):
+ data = super(SourcesTable, self).apply_row_customization(row)
+ def get_key(key,dict):
+ if key in dict:
+ return(dict[key])
+ return ''
+ # {'Severity_V2': '["", "MEDIUM"]', 'Severity_V3': '["", "MEDIUM"]'}
+ for i in range(len(data['rows'])):
+ source_id,attributes = data['rows'][i]['update_now'].split('|')
+ try:
+ update_now_str = self.source_update[source_id]
+ disabled = ("DISABLE " in attributes)
+ hidden_on = 'style="display:none;"' if disabled else ''
+ hidden_off = 'style="display:none;"' if not disabled else ''
+# <span id="next_{{data.id}}">{{source_update|get_dict_value:data.id}}{% if data.update %}<button class="execute run-update-job" style="float:right;" x-data="{{data.id}}">Now</button>{% endif %}</span>
+ update_now = '<span id="next_on_%s" %s>%s' % (source_id,hidden_on,update_now_str)
+ if update_now_str and ('(' != update_now_str[0]):
+ update_now += '<button class="execute run-update-job" style="float:right;" x-data="%s">Now</button>' % source_id
+ update_now += '</span>'
+ update_now += '<span id="next_off_%s" %s>(Disabled)</span>' % (source_id,hidden_off)
+ data['rows'][i]['update_now'] = update_now
+ except Exception as e:
+ _log("ERROR_APPLY_ROW_CUSTOMIZATION:%s" % e)
+ continue
+ return data
+
class SelectPublishTable(ToasterTable):
"""Table of Publishable CVE's in SRTool"""
@@ -1919,8 +2139,7 @@ class SelectPublishTable(ToasterTable):
field_name="Select",
hideable=False,
static_data_name="select",
- static_data_template='<input type="checkbox" name="{{data.name}}" />',
- )
+ static_data_template='<input type="checkbox" class="selectbox" id="box_{{data.id}}" name="{{data.name}}" />', )
self.add_column(title="Status",
field_name="status",
@@ -2154,7 +2373,7 @@ class ErrorLogsTable(ToasterTable):
field_name="Select",
hideable=False,
static_data_name="select",
- static_data_template='<input type="checkbox" value="{{data.pk}}" name="select-notify" />',
+ static_data_template='<input type="checkbox" class="selectbox" id="box_{{data.pk}}" value="{{data.pk}}" name="select-notify" />',
)
self.add_column(title="SRT Created",
@@ -2925,3 +3144,158 @@ class PublishDefectTable(ToasterTable):
orderable=False,
)
+class ManageJobsTable(ToasterTable):
+ """Table of All Audits """
+
+ def __init__(self, *args, **kwargs):
+ super(ManageJobsTable, self).__init__(*args, **kwargs)
+ _log("MANAGEJOBSTABLE:INIT|%s|%s|%s|" % (str(self),','.join(args),json.dumps(kwargs) ))
+ self.default_orderby = "-started_on"
+# _log("TRACE:%s" % str(traceback.print_stack()))
+
+ def get_context_data(self, **kwargs):
+ context = super(ManageJobsTable, self).get_context_data(**kwargs)
+ _log("MANAGEJOBSTABLE:GET_CONTEXT_DATA|%s|%s|" % (str(self),json.dumps(kwargs) ))
+ context['mru'] = Job.get_recent()
+ context['mrj_type'] = 'all'
+ return context
+
+ def setup_queryset(self, *args, **kwargs):
+ _log("MANAGEJOBSTABLE:SETUP_QUERYSET|%s|%s|%s|" % (str(self),','.join(args),json.dumps(kwargs) ))
+ self.queryset = Job.objects.all()
+ self.queryset = self.queryset.order_by(self.default_orderby)
+
+ def setup_filters(self, *args, **kwargs):
+ _log("MANAGEJOBSTABLE:SETUP_FILTERS|%s|%s|%s|" % (str(self),','.join(args),json.dumps(kwargs) ))
+ # Is Status filter
+ is_status = TableFilter(name="is_status",
+ title="Filter Jobs by 'Status'")
+ for status in range(len(Job.STATUS)):
+ is_status.add_action(TableFilterActionToggle(
+ Job.STATUS[status][1].lower().replace(' ','_'),
+ Job.STATUS[status][1],
+ Q(status=Job.STATUS[status][0]))
+ )
+ self.add_filter(is_status)
+
+ def setup_columns(self, *args, **kwargs):
+ _log("MANAGEJOBSTABLE:SETUP_COLUMNS|%s|%s|%s|" % (str(self),','.join(args),json.dumps(kwargs) ))
+
+ # Fetch pid run data
+ pid_table = {}
+ pid_table[0] = 0
+ pid_table['0'] = 0
+ pid_table[''] = 0
+ result_returncode,result_stdout,result_stderr = execute_process(['bin/common/srtool_job.py','--job-pid-status'])
+ for line in result_stdout.splitlines():
+ pid = line[:line.index(':')]
+ value = line[line.index(':')+1:]
+ pid_table[pid] = value
+ self.pid_table = pid_table
+ _log("FOO:%s" % pid_table)
+
+ self.add_column(title="ID",
+ field_name="id",
+ orderable=True,
+ )
+
+ self.add_column(title="Name",
+ field_name="name",
+ orderable=True,
+ )
+
+ self.add_column(title="Status",
+ filter_name="is_status",
+ static_data_name="status",
+ static_data_template='{{data.get_status_text}}',
+ )
+
+ self.add_column(title="Description",
+ field_name="description",
+ hideable=True,
+ )
+
+ self.add_column(title="Command",
+ field_name="command",
+ hideable=True,
+ )
+
+ self.add_column(title="Message",
+ field_name="message",
+ hideable=True,
+ hidden=True,
+ )
+
+ job_pid_template = '''
+ {{data.pid}}
+ '''
+ self.add_column(title="PID",
+ hideable=True,
+ static_data_name="pid",
+ static_data_template=job_pid_template,
+ )
+
+ self.add_column(title="Started_On",
+ field_name="started_on",
+ hideable=True,
+ orderable=True,
+ )
+
+ self.add_column(title="Completed_On",
+ field_name="completed_on",
+ hideable=True,
+ )
+
+ self.add_column(title="Count",
+ field_name="count",
+ hideable=True,
+ hidden=True,
+ )
+ self.add_column(title="Max",
+ field_name="max",
+ hideable=True,
+ hidden=True,
+ )
+ self.add_column(title="Errors",
+ hideable=True,
+ hidden=True,
+ static_data_name="errors",
+ static_data_template='{{data.errors|default:"0"}}',
+ )
+ self.add_column(title="Warnings",
+ hideable=True,
+ hidden=True,
+ static_data_name="warnings",
+ static_data_template='{{data.warnings|default:"0"}}',
+ )
+
+ self.add_column(title="Log_File",
+ field_name="log_file",
+ hideable=True,
+ )
+
+ if UserSafe.is_creator(self.request.user):
+ trash_job_template = '''
+ <span class="glyphicon glyphicon-trash trash-job" x-data="{{data.name}}|{{data.id}}"></span>
+ '''
+ self.add_column(title="Manage",
+ hideable=False,
+ static_data_name="trash_job",
+ static_data_template=trash_job_template,
+ )
+
+ def apply_row_customization(self, row, **kwargs):
+ # data:dict_keys(['rows', 'total', 'default_orderby', 'error', 'columns'])
+ data = super(ManageJobsTable, self).apply_row_customization(row)
+ for i in range(len(data['rows'])):
+ pid = data['rows'][i]['pid'].strip()
+ status = data['rows'][i]['status'].strip()
+ if (pid in self.pid_table) and ('1' == self.pid_table[pid]):
+ if 'Success' == status:
+ data['rows'][i]['pid'] = '%s <span style="color:green"> (Done)</span>' % (pid)
+ else:
+ data['rows'][i]['pid'] = '%s <span style="color:red"> (Dead)</span>' % (pid)
+ else:
+ data['rows'][i]['pid'] = '%s <span style="color:blue"> (Running)</span>' % (pid)
+ return data
+
diff --git a/lib/srtgui/templates/base.html b/lib/srtgui/templates/base.html
index 623dfdd8..8967a104 100644
--- a/lib/srtgui/templates/base.html
+++ b/lib/srtgui/templates/base.html
@@ -1,7 +1,6 @@
<!DOCTYPE html>
{% load static %}
-{% load projecttags %}
-{% load project_url_tag %}
+{% load jobtags %}
<html lang="en">
<head>
<title>
@@ -39,6 +38,9 @@
libtoaster.ctx = {
jsUrl : "{% static 'js/' %}",
htmlUrl : "{% static 'html/' %}",
+ recipeTypeAheadUrl: {% url 'xhr_recipetypeahead' as paturl %}{{paturl|json}},
+ xhrJobRequestUrl: "{% url 'xhr_jobrequest' %}",
+ mostRecentJobsUrl: "{% url 'most_recent_jobs' %}",
};
</script>
{% block extraheadcontent %}
@@ -71,7 +73,7 @@
display: none;
position: absolute;
background-color: #f9f9f9;
- min-width: 160px;
+ min-width: 260px;
overflow: auto;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1;
@@ -112,6 +114,11 @@ toggle between hiding and showing the dropdown content */
function myFunction() {
document.getElementById("myDropdown").classList.toggle("show");
}
+/* When the user clicks on the Product,
+toggle between hiding and showing the dropdown content */
+function selectProduct() {
+ document.getElementById("ProductDropdown").classList.toggle("show");
+}
// Close the dropdown if the user clicks outside of it
window.onclick = function(event) {
@@ -158,7 +165,7 @@ window.onclick = function(event) {
<img class="logo" src="{{ srt_logo.1 }}" alt="{{srt_logo.0}}"/>
</a>
{% endif %}
- <a class="brand" href="/">SRTool:Security Response Tool</a>
+ <a class="brand" href="/">SRTool:Security Response Tool {{srt_mode}}</a>
{% if DEBUG %}
<span class="glyphicon glyphicon-info-sign" title="<strong>SRTool version information</strong>" data-content="<dl><dt>Git branch</dt><dd>{{TOASTER_BRANCH}}</dd><dt>Git revision</dt><dd>{{TOASTER_REVISION}}</dd></dl>"></i>
{% endif %}
@@ -168,7 +175,7 @@ window.onclick = function(event) {
<ul class="nav navbar-nav">
<li id="navbar-home" {% if request.resolver_match.url_name == 'landing' %}class="active"{% endif %}>
- <a href="{% url 'landing' %}">
+ <a href="/"> <!--href="{ % url 'landing' % }"> -->
<i class="glyphicon glyphicon-tasks"></i>
Home
</a>
@@ -234,7 +241,7 @@ window.onclick = function(event) {
<div class="dropdown navbar-right">
{% if user.is_authenticated %}
- <button onclick="myFunction()" class="dropbtn ">Hello '{{user.username}}'</button>
+ <button onclick="myFunction()" class="dropbtn ">Hello '{{user.username}}'</button>{% if user.timezone %}({{user.timezone}}){% endif %}
{% else %}
<button onclick="myFunction()" class="dropbtn ">Hello 'Guest' (Login here)</button>
{% endif %}
@@ -247,9 +254,11 @@ window.onclick = function(event) {
<a href="{% url 'password_reset' %}">Reset password</a>
<a href="{% url 'tbd' %}">Request permissions</a>
-->
+ <a href="{% url 'email_admin' %}">Request admin help</a>
{% else %}
<a href="{% url 'login' %}">Login</a>
<a href="{% url 'signup' %}">Request account</a>
+ <a href="{% url 'email_admin' %}">Request admin help</a>
{% endif %}
</div>
</div>
diff --git a/lib/srtgui/templates/basetable_top.html b/lib/srtgui/templates/basetable_top.html
index ce478c05..db9ca7ed 100644
--- a/lib/srtgui/templates/basetable_top.html
+++ b/lib/srtgui/templates/basetable_top.html
@@ -1,4 +1,4 @@
-{% load projecttags %}
+{% load jobtags %}
<!-- component to display a generic table -->
<script>
diff --git a/lib/srtgui/templates/create_vulnerability.html b/lib/srtgui/templates/create_vulnerability.html
index f8e56d24..c62aed2d 100644
--- a/lib/srtgui/templates/create_vulnerability.html
+++ b/lib/srtgui/templates/create_vulnerability.html
@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% load static %}
-{% load projecttags %}
+{% load jobtags %}
{% load humanize %}
{% block title %} Create New Vulnerability {% endblock %}
diff --git a/lib/srtgui/templates/cve-edit-local.html b/lib/srtgui/templates/cve-edit-local.html
index 7dde6b68..6c8bcdce 100755
--- a/lib/srtgui/templates/cve-edit-local.html
+++ b/lib/srtgui/templates/cve-edit-local.html
@@ -12,7 +12,7 @@
</div>
<div class="col-md-5">
<div class="well" style="width: 400px;">
- <h3>Quick Info: </h3>
+ <h3>EDIT Quick Info: </h3>
<p/>
<dl class="dl-horizontal">
diff --git a/lib/srtgui/templates/cve-nist-local.html b/lib/srtgui/templates/cve-nist-local.html
index 9c4c454c..3fe16e74 100755
--- a/lib/srtgui/templates/cve-nist-local.html
+++ b/lib/srtgui/templates/cve-nist-local.html
@@ -5,7 +5,7 @@
<div class="col-md-5">
<div>
<h3>Description</h3>
- <textarea rows="9" style="min-width: 100%" class="localblue">{{details.description}}</textarea>
+ <textarea rows="9" readonly style="min-width: 100%" class="localblue">{{details.description}}</textarea>
</div>
<p/>
</div>
@@ -31,7 +31,8 @@
<dd>
{% if object.cve_to_vulnerability.all %}
{% for cv in object.cve_to_vulnerability.all %}
- {% if not forloop.first %}| {% endif %}<a href="{% url 'vulnerability' cv.vulnerability.pk %}">{{cv.vulnerability.name}}</a>
+ {% if not forloop.first %}<p>{% endif %}<a href="{% url 'vulnerability' cv.vulnerability.pk %}">{{cv.vulnerability.name}}</a>
+ <span class="glyphicon glyphicon-trash detach-vulnerability" id="detach_vulnerability_'+{{cv.vulnerability.id}}+'" x-data="{{cv.vulnerability.id}}"></span>
{% endfor %}
{% endif %}
<button class="execute btn btn-info" id="submit-create-vulnerability" style="margin-bottom: 5px; margin-top: 5px;">Create Vulnerability</button>
diff --git a/lib/srtgui/templates/cve-nist.html b/lib/srtgui/templates/cve-nist.html
index 9792865b..0b8cf3f0 100755
--- a/lib/srtgui/templates/cve-nist.html
+++ b/lib/srtgui/templates/cve-nist.html
@@ -1,13 +1,13 @@
<!-- vvvvvvvvvvvvvvvvvvvvvvvvvvvvvv -->
-{% load projecttags %}
+{% load jobtags %}
<!-- Row: Description and Quick Info -->
<div class="row" style="padding-left: 25px;">
<div class="col-md-6">
<h3>Description</h3>
<div>
- <textarea rows="9" style="min-width: 100%" {{cve_html|get_dict_value:'description'}}>{{details.description}}</textarea>
+ <textarea rows="9" readonly style="min-width: 100%" {{cve_html|get_dict_value:'description'}}>{{details.description}}</textarea>
</div>
<p/>
</div>
@@ -33,7 +33,8 @@
<dd>
{% if object.cve_to_vulnerability.all %}
{% for cv in object.cve_to_vulnerability.all %}
- {% if not forloop.first %}| {% endif %}<a href="{% url 'vulnerability' cv.vulnerability.pk %}">{{cv.vulnerability.name}}</a>
+ {% if not forloop.first %}<p>{% endif %}<a href="{% url 'vulnerability' cv.vulnerability.pk %}">{{cv.vulnerability.name}}</a>
+ <span class="glyphicon glyphicon-trash detach-vulnerability" id="detach_vulnerability_'+{{cv.vulnerability.id}}+'" x-data="{{cv.vulnerability.id}}"></span>
{% endfor %}
{% endif %}
<button class="execute btn btn-info" id="submit-create-vulnerability" style="margin-bottom: 5px; margin-top: 5px;">Create Vulnerability</button>
@@ -231,6 +232,7 @@
{% for cpe in details.get_cpe_list %}
{% if not cpe %}
{% elif not cpe.0 %}
+ No CPE configurations
{% elif '[config' in cpe.0 %}
<div style="padding-left: 25px;">
<h4>&bull; Configuration </h3>
diff --git a/lib/srtgui/templates/cve.html b/lib/srtgui/templates/cve.html
index c3cfcac5..e3fe0ca0 100644
--- a/lib/srtgui/templates/cve.html
+++ b/lib/srtgui/templates/cve.html
@@ -1,6 +1,6 @@
{% extends "base.html" %}
-{% load projecttags %}
+{% load jobtags %}
{% block title %} {{object.name}} - SRTool {% endblock %}
@@ -29,8 +29,11 @@
<div class="col-md-12">
<div class="page-header build-data">
<span id="cve-name-container">
- <span id="cve-name" class="srt_h1">{{object.name}} {% if not object.public %} <font color="red">[PRIVATE]</font> {% endif %}</span>
- {% if object.is_local and request.user.is_contributor %}<span class="glyphicon glyphicon-edit" id="cve-change-form-toggle"></span>{% endif %}
+ &nbsp;&nbsp;
+ <span id="cve-name" class="srt_h1">{{object.name}}
+ {% if object.is_local and request.user.is_contributor %}&nbsp;&nbsp;<span class="glyphicon glyphicon-edit" id="cve-change-form-toggle"></span>{% endif %}
+ {% if not object.public %}&nbsp;&nbsp;<font color="red" >[PRIVATE]</font> {% endif %}
+ </span>
{% if request.user.is_creator %}
<span style="padding-left:30px;"><button id="select-quickedit" class="btn btn-default" type="button">Edit Status...</button></span>
<span style="padding-left:30px;"><button id="select-notification" class="btn btn-default" type="button">Create Notification ...</button></span>
@@ -41,6 +44,9 @@
<span style="padding-left:30px;"><button id="select-cveedit" class="btn btn-default" type="button">Edit CVE Data ...</button></span>
{% endif %}
<span style="padding-left:30px;"><button id="submit-delete-cve" class="btn btn-default" type="button">Delete CVE</button></span>
+ {% if object.is_local %}
+ <span style="padding-left:30px;"><button id="select-merge-cve" class="btn btn-default" type="button">Merge CVE</button></span>
+ {% endif %}
{% endif %}
</span>
{% if not is_edit %}
@@ -59,13 +65,26 @@
<!-- include SRtool Metadata/Notification -->
{% include "srtool_metadata_include.html" with default_category="CVE" default_url="cve" %}
+<!-- CVE Merge -->
+{% if object.is_local %}
+ <div id="details-cve-merge" style="display:none;padding-left:25px;">
+ <fieldset style="border: 1px solid Blue; background-color:LightBlue; padding-left: 25px; padding-right: 20px;"> <!-- class="fieldset-auto-width" -->
+ <p><p>
+ <button class="btn btn-primary btn-lg" id="submit-merge-cve"> Submit Merge </button>
+ <p>Target CVE: <input type="text" placeholder="CVE Number" id="target-cve-name" size="40" ></p>
+ </fieldset>
+ <p>
+ <p>
+ </div>
+{% endif %}
+
<div class="row">
<div class="col-md-12 tabbable">
<ul class="nav nav-tabs">
- {% for details,state,id,cve_html in cve_list_table %}
+ {% for details,state,id,cve_html,ds_id in cve_list_table %}
<li class="{{state}}">
<a href="#{{id}}" data-toggle="tab">
- {{id}}
+ {{id}}{% if request.user.is_admin %}({{ds_id}}){% endif %}
<span class="glyphicon glyphicon-question-sign get-help" title="{{id}} CVE data"></span>
</a>
</li>
@@ -73,7 +92,7 @@
</ul>
<div class="tab-content">
- {% for details,state,id,cve_html in cve_list_table %}
+ {% for details,state,id,cve_html,ds_id in cve_list_table %}
<div class="tab-pane {{state}}" id="{{id}}">
{% if 'Local' == id %}
@@ -95,6 +114,73 @@
</form>{% csrf_token %}
{% endif %}
+{% if not object.public %}
+ {% if request.user.is_creator %}
+
+ <div class="row" style="padding-left: 25px;">
+ <h3>User Access
+ {% if request.user.is_creator %}
+ <button id="select-adduseraccess" class="btn btn-default" type="button">Add user access ...</button>
+ {% endif %}
+ </h3>
+
+ <div id="details-adduseraccess" style="padding-left: 50px; display:none;">
+ <p><p>
+ <button class="execute" id="submit-adduseraccess"> Submit </button>
+ <div class="row">
+ <p>
+ <div id="all-users" class="scrolling" style="width: 300px;">
+ {% for user in users %}
+ <div class="checkbox">
+ <label>
+ <input class="checkbox-users" name="access-users" value="{{user.pk}}" type="checkbox">{{user.name}}
+ </label>
+ <p>
+ </div>
+ {% endfor %}
+ </div>
+ </div>
+ </div>
+
+ <table class="table table-striped table-condensed" data-testid="vuln-hyperlinks-table">
+ <thead>
+ <tr>
+ <th>User</th>
+ <th>Manage</th>
+ </tr>
+ </thead>
+
+ {% if object.public %}
+ <tr>
+ <td>All</td>
+ <td>
+ </td>
+ </tr>
+ {% endif %}
+
+ {% if object.cve_users.all %}
+ {% for u in object.cve_users.all %}
+ <tr>
+ <td>{{ u.user.username }}</td>
+ <td>
+ <span id="attachment_entry_'+{{u.id}}+'" class="js-config-var-name"></span>
+ <span class="glyphicon glyphicon-trash trash-useraccess" id="attachment_trash_'+{{u.id}}+'" x-data="{{u.id}}"></span>
+ </td>
+ </tr>
+ {% endfor %}
+ {% else %}
+ {% if not object.public %}
+ <tr>
+ <td>No users found</td>
+ </tr>
+ {% endif %}
+ {% endif %}
+ </table>
+
+ </div>
+ {% endif %}
+{% endif %}
+
<div class="row" style="padding-left: 25px;">
<h3>History</h3>
@@ -129,6 +215,8 @@ Created={{object.srt_created}} Updated={{object.srt_updated}}
<script>
var selected_quickedit=false;
var selected_notifyedit=false;
+ var selected_adduseraccess=false;
+ var selected_mergecve=false;
/* CVE Name change support */
var cveNameForm = $("#cve-name-change-form");
@@ -154,7 +242,7 @@ Created={{object.srt_created}} Updated={{object.srt_updated}}
if (('new_name' in data) && (0 == data.new_name.indexOf("url:"))) {
window.location.replace(data.new_name.replace("url:",""));
} else if (('new_name' in data) && ("" != data.new_name)) {
- var new_url = "{% url 'cve' object.name %}".replace("{{object.name}}",data.new_name);
+ var new_url = "{% url 'cve' 123 %}".replace("123",data.new_name);
window.location.replace(new_url);
} else {
location.reload(true);
@@ -190,7 +278,7 @@ Created={{object.srt_created}} Updated={{object.srt_updated}}
selected_quickedit=true;
$("#display-status").slideUp();
$("#details-quickedit").slideDown();
- document.getElementById("select-quickedit").innerText = "Close edit status...";
+ document.getElementById("select-quickedit").innerText = "Cancel edit status...";
$("#select-quickedit").addClass("blueborder");
document.getElementById("select-status-state").focus();
}
@@ -206,14 +294,31 @@ Created={{object.srt_created}} Updated={{object.srt_updated}}
var tags=$('#text-tags').val().trim();
var priority=$('#select-priority-state').val();
var status=$('#select-status-state').val();
+ var public=$('#select-public-state').val();
var publish_state=$('#select-publish-state').val();
var publish_date=$('#select-publish-date').val();
var acknowledge_date=$('#text-acknowledge-date').val();
var affected_components=$('#text-affected-components').val();
+ /* Double check any public status changes */
+ {% if object.public %}
+ if ("0" == public) {
+ if (! confirm("Are you sure you want to make this CVE and all its children as PRIVATE?")) {
+ return
+ }
+ }
+ {% endif %}
+ {% if not object.public %}
+ if ("1" == public) {
+ if (! confirm("Are you sure you want to make this CVE and all its children as PUBLIC?")) {
+ return
+ }
+ }
+ {% endif %}
postCommitAjaxRequest({
"action" : 'submit-quickedit',
"priority" : priority,
"status" : status,
+ "public" : public,
"note" : note,
"private_note" : private_note,
"tags" : tags,
@@ -301,6 +406,16 @@ Created={{object.srt_created}} Updated={{object.srt_updated}}
"vul_name" : $("#vulnerability_name").val(),
});
});
+ $('.detach-vulnerability').click(function() {
+ var result = confirm("Are you sure you want to detach this Vulnerability?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-detach-vulnerability',
+ "record_id" : $(this).attr('x-data'),
+ });
+ }
+ });
+
$("#submit-delete-cve").click(function(){
var result = confirm("Are you sure you want to permamently delete '{{object.name}}' and all its related records?");
@@ -311,7 +426,64 @@ Created={{object.srt_created}} Updated={{object.srt_updated}}
}
});
+ $('#select-merge-cve').click(function(){
+ if (selected_mergecve) {
+ selected_mergecve=false;
+ $("#details-cve-merge").slideUp();
+ document.getElementById("select-merge-cve").innerText = "Merge CVE";
+ $("#select-merge-cve").removeClass("blueborder");
+ } else {
+ selected_mergecve=true;
+ $("#details-cve-merge").slideDown();
+ document.getElementById("select-merge-cve").innerText = "Close merge CVE";
+ $("#select-merge-cve").addClass("blueborder");
+ document.getElementById("target-cve-name").focus();
+ }
+ });
+ $("#submit-merge-cve").click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-merge-cve',
+ "cve_merge_name" : $("#target-cve-name").val(),
+ });
+ });
+ $('#select-adduseraccess').click(function(){
+ if (selected_adduseraccess) {
+ selected_adduseraccess=false;
+ $("#details-adduseraccess").slideUp();
+ } else {
+ selected_adduseraccess=true;
+ $("#details-adduseraccess").slideDown();
+ }
+ });
+
+ $('#submit-adduseraccess').click(function(){
+ var user_list=[];
+ $('input[name="access-users"]').each(function(){
+ if ($(this).is(':checked')) {
+ user_list.push($(this).prop('value'));
+ }
+ });
+ user_list = user_list.join(",");
+ if ("" == user_list) {
+ alert("No users were selected");
+ return;
+ }
+ postCommitAjaxRequest({
+ "action" : 'submit-adduseraccess',
+ "users" : user_list,
+ });
+ });
+
+ $('.trash-useraccess').click(function(){
+ var result = confirm("Are you sure?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-trashuseraccess',
+ "record_id" : $(this).attr('x-data'),
+ });
+ }
+ })
/* Set the report link */
$('#report_link').attr('href',"{% url 'report' request.resolver_match.url_name %}?record_list={{object.id}}");
diff --git a/lib/srtgui/templates/cve.html_orig b/lib/srtgui/templates/cve.html_orig
index e5ec7eff..f674c1d3 100755
--- a/lib/srtgui/templates/cve.html_orig
+++ b/lib/srtgui/templates/cve.html_orig
@@ -1,6 +1,6 @@
{% extends "base.html" %}
-{% load projecttags %}
+{% load jobtags %}
{% block title %} {{cve_list_table.0.0.name}} - SRTool {% endblock %}
diff --git a/lib/srtgui/templates/cves-select-toastertable.html b/lib/srtgui/templates/cves-select-toastertable.html
index d29a2b92..d1b89f34 100644
--- a/lib/srtgui/templates/cves-select-toastertable.html
+++ b/lib/srtgui/templates/cves-select-toastertable.html
@@ -1,5 +1,5 @@
{% extends 'base.html' %}
-{% load projecttags %}
+{% load jobtags %}
{% load humanize %}
{% load static %}
@@ -9,8 +9,8 @@
<link rel="stylesheet" href="{% static 'css/jquery-ui.min.css' %}" type='text/css'>
<link rel="stylesheet" href="{% static 'css/jquery-ui.structure.min.css' %}" type='text/css'>
<link rel="stylesheet" href="{% static 'css/jquery-ui.theme.min.css' %}" type='text/css'>
- <script src="{% static 'js/jquery-ui.min.js' %}">
- </script>
+ <script src="{% static 'js/jquery-ui.min.js' %}"></script>
+ <script src="{% static 'js/typeahead_affected_components.js' %}"></script>
<script>
// Toggle the row checkbox if any column element is clicked
function toggle_select(toggle_id) {
@@ -140,7 +140,7 @@
<p><b><big>Reason: </big></b>
<input type="text" id="input-isvulnerable-reason" name="reason" size="40">&nbsp;&nbsp;<input id="markPublishIs" type="checkbox">&nbsp;Mark for Publish</input>&nbsp;&nbsp;<input id="markFor" type="checkbox"> Add Keywords to 'For' </input>
<p><b><big>Affected Components: </big></b>
- <input type="text" id="input-isvulnerable-components" name="components" size="40"> (e.g. space-separated list of packages, recipes, sub-system list, applications, )
+ <input type="text" id="input-isvulnerable-components" name="components" size="40" autocomplete="off"> (e.g. space-separated list of packages, recipes, sub-system list, applications, )
<div id="published-date-list">
<p><i>Acknowledge Date</i> =
@@ -207,7 +207,7 @@
</div>
<!-- Javascript support -->
- <script>
+ <script type="text/javascript">
//# sourceURL=somename.js
@@ -217,9 +217,19 @@
var selected_investigate=false;
var selected_other=false;
var cve_total=0;
+ var lastChecked = null;
+ var $selectboxes = null;
$(document).ready(function() {
+ // Init the recipe typeahead
+ try {
+ autocompInit();
+ } catch (e) {
+ document.write("Sorry, An error has occurred initiating the autocomplete feature");
+ console.warn(e);
+ }
+
function onCommitAjaxSuccess(data, textstatus) {
if (window.console && window.console.log) {
console.log("XHR returned:", data, "(" + textstatus + ")");
@@ -536,6 +546,21 @@
});
$('#report_link').attr('href',"{% url 'report' request.resolver_match.url_name %}?record_list="+record_list);
+ /* Enable shift-select ranges */
+ $selectboxes = $('.selectbox');
+ $selectboxes.click(function(e) {
+ if (!lastChecked) {
+ lastChecked = this;
+ return;
+ }
+ if (e.shiftKey) {
+ var start = $selectboxes.index(this);
+ var end = $selectboxes.index(lastChecked);
+ $selectboxes.slice(Math.min(start,end), Math.max(start,end)+ 1).prop('checked', lastChecked.checked);
+ }
+ lastChecked = this;
+ });
+
});
});
</script>
diff --git a/lib/srtgui/templates/cves-toastertable.html b/lib/srtgui/templates/cves-toastertable.html
index 45dce261..1f6548f5 100644
--- a/lib/srtgui/templates/cves-toastertable.html
+++ b/lib/srtgui/templates/cves-toastertable.html
@@ -47,7 +47,7 @@
$("#table-loading").slideDown();
tableElt.on("table-done", function (e, total, tableParams) {
- var title = "All CVE's";
+ var title = "All CVE's (" + total + ")";
if (tableParams.search || tableParams.filter) {
if (total === 0) {
diff --git a/lib/srtgui/templates/date-time-test.html b/lib/srtgui/templates/date-time-test.html
new file mode 100755
index 00000000..d123f79c
--- /dev/null
+++ b/lib/srtgui/templates/date-time-test.html
@@ -0,0 +1,88 @@
+{% extends "base.html" %}
+
+{% load static %}
+{% load jobtags %}
+{% load humanize %}
+
+{% block title %} DateTime Test {% endblock %}
+{% block pagecontent %}
+
+ <div class="col-md-5">
+ <b>DateTime Info</b>
+ <div class="well">
+ <dl class="dl-horizontal">
+ <dt>UTC Current Time:</dt>
+ <dd>{{current_utc}}</dd>
+ <dt>Alameda Current Time:</dt>
+ <dd>{{current_ala}}</dd>
+ <dt>Your Current Time:</dt>
+ <dd>{{current_local}}</dd>
+ <dt>Datetime Shift</dt>
+ <dd>"2021-05-10 13:38:22"|shift_timezone:"{{user.get_timezone_offset}}" => {{ "2021-05-10 13:38:22"|shift_timezone:user.get_timezone_offset }},{{user.get_timezone_offset}}</dd>
+ </dl>
+ </div>
+ </div>
+ <p><i>Timezone</i> = {{user_timezone}}</p>
+
+ <fieldset style="border: 1px solid Blue; background-color:LightBlue; padding-left: 25px; padding-right: 20px;">
+ <button class="execute btn btn-primary btn-lg" id="submit-timezone"> Submit Changes </button>
+
+<select name="timezone" id="select-timezone">
+
+ {% for tz in timezone_list %}
+
+ <option value="{{tz}}" {% if user_timezone == tz %}selected{% endif %}>{{tz}}</option>
+
+ {% endfor %}
+
+</select>
+</fieldset>
+
+<script type="text/javascript">
+
+ $(document).ready(function() {
+ function onCommitAjaxSuccess(data, textstatus) {
+ if (window.console && window.console.log) {
+ console.log("XHR returned:", data, "(" + textstatus + ")");
+ } else {
+ alert("NO CONSOLE:\n");
+ return;
+ }
+ if (data.error != "ok") {
+ alert("error on request:\n" + data.error);
+ return;
+ }
+ location.reload(true);
+ }
+
+ function onCommitAjaxError(jqXHR, textstatus, error) {
+ console.log("ERROR:"+error+"|"+textstatus);
+ alert("XHR errored1:\n" + error + "\n(" + textstatus + ")");
+ }
+
+ /* ensure cookie exists {% csrf_token %} */
+ function postCommitAjaxRequest(reqdata) {
+ var ajax = $.ajax({
+ type:"POST",
+ data: reqdata,
+ url:"{% url 'xhr_date_time_test' %}",
+ headers: { 'X-CSRFToken': $.cookie("csrftoken")},
+ success: onCommitAjaxSuccess,
+ error: onCommitAjaxError,
+ });
+ }
+ $('#submit-timezone').click(function(){
+ var timezone=$('#select-timezone').val();
+
+ /* Double check any public status changes */
+ postCommitAjaxRequest({
+ "action" : 'submit-timezone',
+ "timezone" : timezone,
+ });
+ });
+
+ });
+
+</script>
+
+{% endblock %}
diff --git a/lib/srtgui/templates/defect.html b/lib/srtgui/templates/defect.html
index 2cae9514..b1ccae31 100644
--- a/lib/srtgui/templates/defect.html
+++ b/lib/srtgui/templates/defect.html
@@ -1,6 +1,6 @@
{% extends "base.html" %}
-{% load projecttags %}
+{% load jobtags %}
{% block title %} {{object.name}} - SRTool {% endblock %}
diff --git a/lib/srtgui/templates/detail_sorted_header.html b/lib/srtgui/templates/detail_sorted_header.html
index 4434df43..6554df2e 100644
--- a/lib/srtgui/templates/detail_sorted_header.html
+++ b/lib/srtgui/templates/detail_sorted_header.html
@@ -4,7 +4,7 @@
Must be followed by <tbody>...</tbody></table>.
Requires tablecols setup column fields dclass, clclass, qhelp, orderfield.
{% endcomment %}
-{% load projecttags %}
+{% load jobtags %}
{# <table class="table table-bordered table-hover tablesorter" id="otable"> #}
<thead>
<!-- Table header row; generated from "tablecols" entry in the context dict -->
diff --git a/lib/srtgui/templates/email_admin.html b/lib/srtgui/templates/email_admin.html
new file mode 100755
index 00000000..7c5f0fda
--- /dev/null
+++ b/lib/srtgui/templates/email_admin.html
@@ -0,0 +1,70 @@
+{% extends "base.html" %}
+
+{% load jobtags %}
+
+{% block title %} Admin Help - SRTool {% endblock %}
+
+{% block pagecontent %}
+
+<div class="row">
+ <!-- Breadcrumbs -->
+ <div class="col-md-12">
+ <ul class="breadcrumb" id="breadcrumb">
+ <li><a href="{% url 'landing' %}">Home</a></li><span class="divider">&rarr;</span>
+ <li>Admin Help</li>
+ </ul>
+ </div>
+</div>
+
+<!-- Begin container -->
+
+<div class="row">
+ <div class="col-md-12">
+ <div class="page-header build-data">
+ <h1>Admin Help</h1>
+ Send an email to the SRTool Admin staff for assistance.
+ </div>
+ </div>
+</div>
+
+<form method="post">
+{% csrf_token %}
+
+ {% if error_message %} <h3 style="color:red">{{error_message}}</h3><br> {% endif %}
+
+ <button class="btn btn-primary btn-lg" name="action" value="submit">Submit Request</button>
+ <button class="btn btn-primary btn-lg" name="action" value="cancel">Cancel</button>
+ <br>
+
+ <h3>Request:
+ <select name="request-type">
+ <option value="Request account verification">Request account verification</option>
+ <option value="Request password reset">Request password reset</option>
+ <option value="Request new group name">Request new group name</option>
+ <option value="Request new repo source name">Request new repo source name</option>
+ <option value="Request 'Contributor' status">Request 'Contributor' status</option>
+ <option value="Request 'Creator' status">Request 'Creator' status</option>
+ <option value="Request general Help">Request general help</option>
+ </select>
+ </h2><br>
+
+ <p>Your name: <input type="text" placeholder="your name" name="user-name" size="80" value="{% if request.user.user_fullname %}{{request.user.user_fullname}}{% endif %}"></p>
+ <p>Your email: <input type="text" placeholder="your name" name="user-email" size="80" value="{% if request.user.user_fullname %}{{request.user.email}}{% endif %}"></p>
+
+ <h3>Message text:</h2>
+ <textarea rows="9" style="min-width: 50%" name="message"></textarea>
+
+<hr>
+
+</form>
+
+
+<!-- Javascript support -->
+<script type="text/javascript">
+ $(document).ready(function() {
+
+ });
+</script>
+
+
+{% endblock %}
diff --git a/lib/srtgui/templates/email_success.html b/lib/srtgui/templates/email_success.html
new file mode 100755
index 00000000..baa44163
--- /dev/null
+++ b/lib/srtgui/templates/email_success.html
@@ -0,0 +1,49 @@
+{% extends "base.html" %}
+
+{% load jobtags %}
+
+{% block title %} Admin Help - SRTool {% endblock %}
+
+{% block pagecontent %}
+
+<div class="row">
+ <!-- Breadcrumbs -->
+ <div class="col-md-12">
+ <ul class="breadcrumb" id="breadcrumb">
+ <li><a href="{% url 'landing' %}">Home</a></li><span class="divider">&rarr;</span>
+ <li>Admin Help</li>
+ </ul>
+ </div>
+</div>
+
+<!-- Begin container -->
+
+<div class="row">
+ <div class="col-md-12">
+ <div class="page-header build-data">
+ <h1>Admin Success</h1>
+ Email sent to the SRTool Admin staff for assistance.
+ </div>
+ </div>
+</div>
+
+<form method="post">
+{% csrf_token %}
+
+ <h3> Email successfully sent!</h3>
+ <br>
+ <button class="btn btn-primary btn-lg" name="action" value="close">Close</button>
+ <br>
+
+</form>
+
+
+<!-- Javascript support -->
+<script type="text/javascript">
+ $(document).ready(function() {
+
+ });
+</script>
+
+
+{% endblock %}
diff --git a/lib/srtgui/templates/errorlog-toastertable.html b/lib/srtgui/templates/errorlog-toastertable.html
new file mode 100755
index 00000000..91cf8d55
--- /dev/null
+++ b/lib/srtgui/templates/errorlog-toastertable.html
@@ -0,0 +1,142 @@
+{% extends 'base.html' %}
+{% load static %}
+
+{% block extraheadcontent %}
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.min.css' %}" type='text/css'>
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.structure.min.css' %}" type='text/css'>
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.theme.min.css' %}" type='text/css'>
+ <script src="{% static 'js/jquery-ui.min.js' %}">
+ </script>
+{% endblock %}
+
+{% block title %} Error Logs {% endblock %}
+
+{% block pagecontent %}
+
+<div class="row">
+ <!-- Breadcrumbs -->
+ <div class="col-md-12">
+ <ul class="breadcrumb" id="breadcrumb">
+ <li><a href="{% url 'landing' %}">Home</a></li><span class="divider">&rarr;</span>
+ <li><a href="{% url 'manage' %}">Management</a></li><span class="divider">&rarr;</span>
+ <li>Error Logs</li>
+ </ul>
+ </div>
+</div>
+
+<div > <!--class="form-inline" -->
+ <b><big>Actions: </big></b>
+ <button id="delete-notification" class="btn btn-default" type="button">Delete Selected</button>
+</div>
+
+<div class="row">
+ <div class="col-md-12">
+ <div class="page-header">
+ <h1 class="top-air" data-role="page-title"></h1>
+ </div>
+
+ {# xhr_table_url is just the current url so leave it blank #}
+ {% url '' as xhr_table_url %}
+ {% include 'toastertable.html' %}
+ </div>
+</div>
+
+ <!-- Javascript support -->
+ <script type="text/javascript">
+ var selected_notifyedit=false;
+ var lastChecked = null;
+ var $selectboxes = null;
+
+ $(document).ready(function () {
+ var tableElt = $("#{{table_name}}");
+ var titleElt = $("[data-role='page-title']");
+
+ tableElt.on("table-done", function (e, total, tableParams) {
+ var title = "Pending Notifications";
+
+ if (tableParams.search || tableParams.filter) {
+ if (total === 0) {
+ title = "No Error Logs found";
+ }
+ else if (total > 0) {
+ title = total + " Error" + (total > 1 ? 's' : '') + " found";
+ }
+ }
+
+ titleElt.text(title);
+
+ /* Enable shift-select ranges */
+ $selectboxes = $('.selectbox');
+ $selectboxes.click(function(e) {
+ if (!lastChecked) {
+ lastChecked = this;
+ return;
+ }
+ if (e.shiftKey) {
+ var start = $selectboxes.index(this);
+ var end = $selectboxes.index(lastChecked);
+ $selectboxes.slice(Math.min(start,end), Math.max(start,end)+ 1).prop('checked', lastChecked.checked);
+ }
+ lastChecked = this;
+ });
+
+ });
+
+ function onCommitAjaxSuccess(data, textstatus) {
+ if (window.console && window.console.log) {
+ console.log("XHR returned:", data, "(" + textstatus + ")");
+ } else {
+ alert("NO CONSOLE:\n");
+ return;
+ }
+ if (data.error != "ok") {
+ alert("error on request:\n" + data.error);
+ return;
+ } else if (('results_msg' in data) && ("" != data.results_msg)) {
+ alert("Results: " + data.results_msg);
+ }
+ // reload the page with the updated tables
+ location.reload(true);
+ }
+
+ function onCommitAjaxError(jqXHR, textstatus, error) {
+ console.log("ERROR:"+error+"|"+textstatus);
+ alert("XHR errored1:\n" + error + "\n(" + textstatus + ")");
+ }
+
+ /* ensure cookie exists {% csrf_token %} */
+ function postCommitAjaxRequest(reqdata) {
+ var ajax = $.ajax({
+ type:"POST",
+ data: reqdata,
+ url:"{% url 'xhr_errorlogs'%}",
+ headers: { 'X-CSRFToken': $.cookie("csrftoken")},
+ success: onCommitAjaxSuccess,
+ error: onCommitAjaxError,
+ })
+ }
+
+ $('#delete-notification').click(function(){
+ log_list = [];
+ $('#errorlogstable input').each(function(){
+ if ($(this).is(':checked')) {
+ log_list.push($(this).prop('value'));
+ }
+ });
+ log_list = log_list.join(",");
+ if ("" == log_list) {
+ alert("No Error Logs were selected");
+ return;
+ }
+ if ("" != log_list) {
+ postCommitAjaxRequest({
+ "action" : 'delete-errorlogs',
+ "log_list" : log_list,
+ });
+ }
+ });
+
+ }); <!-- $(document).ready() -->
+
+ </script>
+{% endblock %}
diff --git a/lib/srtgui/templates/export.html b/lib/srtgui/templates/export.html
index 8b2309ca..82f48016 100644
--- a/lib/srtgui/templates/export.html
+++ b/lib/srtgui/templates/export.html
@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% load static %}
-{% load projecttags %}
+{% load jobtags %}
{% load humanize %}
{% block title %} Export Report {% endblock %}
diff --git a/lib/srtgui/templates/filtersnippet.html b/lib/srtgui/templates/filtersnippet.html
index 1286ca31..eb835c1a 100644
--- a/lib/srtgui/templates/filtersnippet.html
+++ b/lib/srtgui/templates/filtersnippet.html
@@ -1,4 +1,4 @@
-{% load projecttags %}
+{% load jobtags %}
<!-- '{{f.class}}' filter -->
{% with f.class as key %}
diff --git a/lib/srtgui/templates/generic-toastertable-page.html b/lib/srtgui/templates/generic-toastertable-page.html
index b3eabe1a..cecfacc9 100644
--- a/lib/srtgui/templates/generic-toastertable-page.html
+++ b/lib/srtgui/templates/generic-toastertable-page.html
@@ -1,5 +1,5 @@
{% extends "baseprojectpage.html" %}
-{% load projecttags %}
+{% load jobtags %}
{% load humanize %}
{% load static %}
diff --git a/lib/srtgui/templates/guided_tour.html b/lib/srtgui/templates/guided_tour.html
index bdc8987e..68c114ef 100644
--- a/lib/srtgui/templates/guided_tour.html
+++ b/lib/srtgui/templates/guided_tour.html
@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% load static %}
-{% load projecttags %}
+{% load jobtags %}
{% load humanize %}
{% block title %} Guided Tour of SRTool {% endblock %}
diff --git a/lib/srtgui/templates/investigation.html b/lib/srtgui/templates/investigation.html
index c2bf92d7..bd974796 100644
--- a/lib/srtgui/templates/investigation.html
+++ b/lib/srtgui/templates/investigation.html
@@ -29,7 +29,7 @@
</style>
{% endblock %}
-{% load projecttags %}
+{% load jobtags %}
{% block title %} {{object.name}} - SRTool {% endblock %}
@@ -107,21 +107,32 @@
</div>
<div class="row">
<div class="column1">
- <p><b><label id="priority">Set Priority:</label></b>
+ <p><b><label id="priority">Set Priority ({{object.priority}}):</label></b>
+
<div id="priority-list" class="scrolling" style="width: 120px;">
- <div class="checkbox"> <label>
- <input type="radio" name="priority" value="4" type="checkbox"> P1
- </label><p></div>
- <div class="checkbox"> <label>
- <input type="radio" name="priority" value="3" type="checkbox" checked="yes"> P2
- </label><p></div>
- <div class="checkbox"> <label>
- <input type="radio" name="priority" value="2" type="checkbox"> P3
- </label><p></div>
- <div class="checkbox"> <label>
- <input type="radio" name="priority" value="1" type="checkbox"> P4
- </label><p></div>
+ <div>
+ <label for="priority_p4">P4</label>
+ <input type="radio" id="priority_p4" name="priority_defect" value="1"
+ {% if 1 == object.priority %}checked{% endif %}
+ {% if 0 == object.priority %}checked{% endif %}>
+ </div>
+ <div>
+ <label for="priority_p3">P3</label>
+ <input type="radio" id="priority_p3" name="priority_defect" value="2"
+ {% if 2 == object.priority %}checked{% endif %}>
+ </div>
+ <div>
+ <label for="priority_p2">P2</label>
+ <input type="radio" id="priority_p2" name="priority_defect" value="3"
+ {% if 3 == object.priority %}checked{% endif %}>
+ </div>
+ <div>
+ <label for="priority_p1">P1</label>
+ <input type="radio" id="priority_p1" name="priority_defect" value="4"
+ {% if 4 == object.priority %}checked{% endif %}>
+ </div>
</div>
+
</div>
<div class="column2">
<p><b><label id="components">Set Components:</label></b>
@@ -387,9 +398,11 @@
<thead>
<tr>
<th>User</th>
+<!--
{% if request.user.is_creator %}
<th>Manage</th>
{% endif %}
+-->
</tr>
</thead>
@@ -404,7 +417,8 @@
{% if object.investigation_users.all %}
{% for u in object.investigation_users.all %}
<tr>
- <td>{{ u.user.name }}</td>
+ <td>{{ u.user.username }}</td>
+<!--
{% if request.user.is_creator %}
<td>
<span id="attachment_entry_'+{{u.id}}+'" class="js-config-var-name"></span>
@@ -413,6 +427,7 @@
</td>
{% endif %}
</tr>
+-->
{% endfor %}
{% else %}
{% if not object.public %}
@@ -630,7 +645,7 @@ Created={{object.srt_created}} Updated={{object.srt_updated}}
}
});
- $('.submit-downloadattac hment').click(function() {
+ $('.submit-downloadattachment').click(function() {
$("#downloadbanner-"+this.getAttribute("x-data")).submit();
});
@@ -735,7 +750,7 @@ Created={{object.srt_created}} Updated={{object.srt_updated}}
selected_quickedit=true;
$("#display-status").slideUp();
$("#details-quickedit").slideDown();
- document.getElementById("select-quickedit").innerText = "Close edit status...";
+ document.getElementById("select-quickedit").innerText = "Cancel edit status...";
$("#select-quickedit").addClass("blueborder");
document.getElementById("select-status-state").focus();
}
@@ -766,13 +781,13 @@ Created={{object.srt_created}} Updated={{object.srt_updated}}
selected_notifyedit=false;
$("#details-notify-edit").slideUp();
$("#display-status").slideDown();
- document.getElementById("select-notification").innerText = "Create Notification ...";
+ document.getElementById("select-notification").innerText = "Create notification ...";
$("#select-notification").removeClass("blueborder");
} else {
selected_notifyedit=true;
$("#display-status").slideUp();
$("#details-notify-edit").slideDown();
- document.getElementById("select-notification").innerText = "Close notification";
+ document.getElementById("select-notification").innerText = "Cancel notification";
$("#select-notification").addClass("blueborder");
document.getElementById("select-category-notify").focus();
}
diff --git a/lib/srtgui/templates/joblog.html b/lib/srtgui/templates/joblog.html
new file mode 100755
index 00000000..1e4abca0
--- /dev/null
+++ b/lib/srtgui/templates/joblog.html
@@ -0,0 +1,39 @@
+{% extends "base.html" %}
+
+{% load static %}
+{% load jobtags %}
+{% load humanize %}
+
+{% block title %} Job Log {% endblock %}
+{% block pagecontent %}
+<div class="row">
+ <div class="col-md-7" style="padding-left: 50px;">
+ <h1>Job Log: {{object.name}} : {{log_date}}
+ <form id="downloadbanner-log" enctype="multipart/form-data" method="post" >{% csrf_token %}
+ <input type="hidden" id="action" name="action" value="download-job-log">
+ <input type="hidden" id="report_path" name="report_path" value="JOBLOG">
+ <span class="glyphicon glyphicon-download-alt submit-download-log" x-data="log"></span>
+ </form>
+ </h1>
+ </div>
+</div>
+
+<div class="row" style="padding-left: 25px;">
+ <textarea id="log-text" readonly placeholder="Job log" cols="120" rows="30" style="background-color: #cccccc;" />{{ log_text }}</textarea>
+</div>
+
+
+<!-- Javascript support -->
+<script>
+ $(document).ready(function() {
+
+ $('.submit-download-log').click(function() {
+ $("#downloadbanner-log").submit();
+ });
+
+ /* Set the report link */
+ $('#report_link').attr('href',"{% url 'report' request.resolver_match.url_name %}?record_list={{object.id}}");
+ });
+</script>
+
+{% endblock %}
diff --git a/lib/srtgui/templates/js-unit-tests.html b/lib/srtgui/templates/js-unit-tests.html
index ca248962..6ebca39f 100644
--- a/lib/srtgui/templates/js-unit-tests.html
+++ b/lib/srtgui/templates/js-unit-tests.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% load projecttags %}
+{% load jobtags %}
{% load humanize %}
{% load static %}
{% block pagecontent %}
diff --git a/lib/srtgui/templates/landing.html b/lib/srtgui/templates/landing.html
index 67c61b1e..f0e4f13d 100644
--- a/lib/srtgui/templates/landing.html
+++ b/lib/srtgui/templates/landing.html
@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% load static %}
-{% load projecttags %}
+{% load jobtags %}
{% load humanize %}
{% block title %} Welcome to SRTool{% endblock %}
@@ -9,7 +9,7 @@
<div class="row">
<div class="col-md-7" style="padding-left: 50px;">
<h1>Security Response Tool (SRTool)</h1>
- <p>A web interface to SRTool CVE investigations</p>
+ <p>A web interface to SRTool CVE investigations ({{this_landing}})</p>
</div>
</div>
<div class="row">
@@ -67,6 +67,13 @@
<td>SRTool Products<td>
</tr>
+ {% for ext_url,ext_title,ext_description in landing_extensions_table %}
+ <tr>
+ <td> <a class="btn btn-info btn-lg" href="{% url ext_url %}">{{ext_title}}</a></td>
+ <td>{{ext_description}}<td>
+ </tr>
+ {% endfor %}
+
</table>
</div>
diff --git a/lib/srtgui/templates/landing_not_managed.html b/lib/srtgui/templates/landing_not_managed.html
index baa4b72c..25e7f713 100644
--- a/lib/srtgui/templates/landing_not_managed.html
+++ b/lib/srtgui/templates/landing_not_managed.html
@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% load static %}
-{% load projecttags %}
+{% load jobtags %}
{% load humanize %}
{% block title %} Welcome to Toaster {% endblock %}
diff --git a/lib/srtgui/templates/login.html b/lib/srtgui/templates/login.html
index 96fb6fe1..49d4ab30 100644
--- a/lib/srtgui/templates/login.html
+++ b/lib/srtgui/templates/login.html
@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% load static %}
-{% load projecttags %}
+{% load jobtags %}
{% load humanize %}
{% block title %} Login Page {% endblock %}
diff --git a/lib/srtgui/templates/maintenance.html b/lib/srtgui/templates/maintenance.html
index a0bb1845..c35d6961 100755
--- a/lib/srtgui/templates/maintenance.html
+++ b/lib/srtgui/templates/maintenance.html
@@ -1,82 +1,216 @@
{% extends "base.html" %}
{% load static %}
-{% load projecttags %}
+{% load jobtags %}
{% load humanize %}
{% block title %} Maintenance tools {% endblock %}
{% block pagecontent %}
- <div class="row">
- <div class="col-md-7" style="padding-left: 50px;">
- <h1>Maintenance</h1>
- </div>
- </div>
- <div class="row">
- <div class="jumbotron well-transparent">
-
- <div class="col-md-6">
- <div>
- <table class="table table-striped table-condensed" data-testid="landing-hyperlinks-table">
- <thead>
- <tr>
- <th>Action</th>
- <th>Description</th>
- </tr>
- </thead>
-
- <tr>
- <td><a class="btn btn-info btn-lg" href="{% url 'error_logs' %}">Error Logs</a></td>
- <td>Examine Error Logs ({{errorlog_total}})</td>
- </tr>
- <tr>
- <td><a class="btn btn-info btn-lg" href="{% url 'history_cve' %}">History CVE</a></td>
- <td>Examine History for CVEs</td>
- </tr>
- <tr>
- <td><a class="btn btn-info btn-lg" href="{% url 'history_vulnerability' %}">History Vulnerabilities</a></td>
- <td>Examine History for Vulnerabilities</td>
- </tr>
- <tr>
- <td><a class="btn btn-info btn-lg" href="{% url 'history_investigation' %}">History Investigations</a></td>
- <td>Examine History for Investigations</td>
- </tr>
- <tr>
- <td><a class="btn btn-info btn-lg" href="{% url 'history_defect' %}">History Defects</a></td>
- <td>Examine History for Defects</td>
- </tr>
-
- </table>
- </div>
-
- </div>
-
- <div class="col-md-5">
- <b>Quick Info</b>
- <div class="well">
- <dl class="dl-horizontal">
-
- <dt>CVE History: Total Count =</dt>
- <dd>
- {{history_cve_total}}
- </dd>
- <dt>Vulnerability History: Total Count =</dt>
- <dd>
- {{history_vulnerability_total}}
- </dd>
- <dt>Investigation: Total Count =</dt>
- <dd>
- {{history_investigation_total}}
- </dd>
- <dt>Defect: Total Count =</dt>
- <dd>
- {{defect_investigation_total}}
- </dd>
-
- </dl>
- </div>
- </div>
-
- </div>
- </div>
+
+<div class="row">
+ <!-- Breadcrumbs -->
+ <div class="col-md-12">
+ <ul class="breadcrumb" id="breadcrumb">
+ <li><a href="{% url 'landing' %}">Home</a></li><span class="divider">&rarr;</span>
+ <li><a href="{% url 'manage' %}">Management</a></li><span class="divider">&rarr;</span>
+ <li>Maintenance</a>
+ </ul>
+ </div>
+</div>
+
+<div class="row">
+ <div class="col-md-7" style="padding-left: 50px;">
+ <h1>Maintenance</h1>
+ </div>
+</div>
+
+<div class="row">
+ <div class="col-md-12">
+ {% with mru=mru %}
+ {% include 'mrj_section.html' %}
+ {% endwith %}
+ </div>
+</div>
+
+<div class="row">
+<div class="jumbotron well-transparent">
+
+ <div class="col-md-6">
+ <div>
+ <table class="table table-striped table-condensed" data-testid="landing-hyperlinks-table">
+ <thead>
+ <tr>
+ <th>Action</th>
+ <th>Description</th>
+ </tr>
+ </thead>
+
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'error_logs' %}">Error Logs</a></td>
+ <td>Examine Error Logs ({{errorlog_total}})</td>
+ </tr>
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'history_cve' %}">History CVE</a></td>
+ <td>Examine History for CVEs</td>
+ </tr>
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'history_vulnerability' %}">History Vulnerabilities</a></td>
+ <td>Examine History for Vulnerabilities</td>
+ </tr>
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'history_investigation' %}">History Investigations</a></td>
+ <td>Examine History for Investigations</td>
+ </tr>
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'history_defect' %}">History Defects</a></td>
+ <td>Examine History for Defects</td>
+ </tr>
+
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'manage_jobs' 77 %}">Manage Jobs</a></td>
+ <td>Manage the Jobs table</td>
+ </tr>
+
+ <tr>
+ <td>
+ <button class="execute" id="submit-clearjobs"> Clear Jobs</button>
+ <td>Clear the Jobs table of all entries</td>
+ </tr>
+
+ <tr>
+ <td>
+ <button class="execute" id="submit-testjob1"> Test Job #1 Progress </button>
+ <td>Test job progress bars support with default job #1</td>
+ </tr>
+
+ <tr>
+ <td>
+ <button class="execute" id="submit-testjob1-2"> Test Job #2 Progress </button>
+ <td>Test job progress bars support with job #2</td>
+ </tr>
+
+ <tr>
+ <td>
+ <button class="execute" id="submit-parent-child"> Test Parent/Child Jobs</button>
+ <td>Test Progress using parent and child jobs</td>
+ </tr>
+
+ </table>
+ </div>
+
+ </div>
+
+ <div class="col-md-5">
+ <b>Quick Info</b>
+ <div class="well">
+ <dl class="dl-horizontal">
+
+ <dt>CVE History: Total Count =</dt>
+ <dd>
+ {{history_cve_total}}
+ </dd>
+ <dt>Vulnerability History: Total Count =</dt>
+ <dd>
+ {{history_vulnerability_total}}
+ </dd>
+ <dt>Investigation: Total Count =</dt>
+ <dd>
+ {{history_investigation_total}}
+ </dd>
+ <dt>Defect: Total Count =</dt>
+ <dd>
+ {{defect_investigation_total}}
+ </dd>
+
+ </dl>
+ </div>
+
+ <form method="post"> {% csrf_token %}
+ <b>Remote Backup Path
+ <button class="execute btn btn-primary">Update</button> <!-- btn-lg -->
+ </b>
+ <div class="well">
+ <input type="hidden" name="action" value="submit-remote-backup-path">
+ Path = <input type="text" placeholder="remote backup path" name="text-remote-backup-path" size="60" value="{{remote_backup_path}}">
+ </div>
+ <form>
+ </div>
+
+</div>
+</div>
+
+
+<script type="text/javascript">
+
+ $(document).ready(function() {
+
+ function onCommitAjaxSuccess(data, textstatus) {
+ if (window.console && window.console.log) {
+ console.log("XHR returned:", data, "(" + textstatus + ")");
+ } else {
+ alert("NO CONSOLE:\n");
+ return;
+ }
+ if (data.error != "ok") {
+ alert("error on request:\n" + data.error);
+ return;
+ }
+ // reload the page with the updated tables
+ location.reload(true);
+ }
+
+ function onCommitAjaxError(jqXHR, textstatus, error) {
+ console.log("ERROR:"+error+"|"+textstatus);
+ alert("XHR errored1:\n" + error + "\n(" + textstatus + ")");
+ }
+
+ /* ensure cookie exists {% csrf_token %} */
+ function postCommitAjaxRequest(reqdata,url) {
+ var ajax = $.ajax({
+ type:"POST",
+ data: reqdata,
+ url: url,
+ headers: { 'X-CSRFToken': $.cookie("csrftoken")},
+ success: onCommitAjaxSuccess,
+ error: onCommitAjaxError,
+ });
+ }
+
+
+ $('#submit-clearjobs').click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-clearjobs',
+ },"{% url 'xhr_job_post' %}");
+ });
+
+ $('#submit-testjob1').click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-testjob',
+ "command" : 'SELFTEST',
+ "name" : 'Basic self test',
+ },"{% url 'xhr_job_post' %}");
+ });
+
+ $('#submit-testjob1-2').click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-testjob-j2',
+ "command" : 'SELFTEST',
+ "name" : 'Basic self test',
+ },"{% url 'xhr_job_post' %}");
+ });
+
+ $('#submit-parent-child').click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-testjob-parent',
+ "command" : 'PARENTTEST',
+ "name" : 'Test Parent/Children',
+ },"{% url 'xhr_job_post' %}");
+ });
+
+
+ /* Set the report link */
+ $('#report_link').attr('href',"{% url 'report' request.resolver_match.url_name %}");
+ });
+</script>
{% endblock %}
diff --git a/lib/srtgui/templates/manage-jobs-toastertable.html b/lib/srtgui/templates/manage-jobs-toastertable.html
new file mode 100755
index 00000000..34e89c57
--- /dev/null
+++ b/lib/srtgui/templates/manage-jobs-toastertable.html
@@ -0,0 +1,126 @@
+{% extends 'base.html' %}
+{% load static %}
+
+{% block extraheadcontent %}
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.min.css' %}" type='text/css'>
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.structure.min.css' %}" type='text/css'>
+ <link rel="stylesheet" href="{% static 'css/jquery-ui.theme.min.css' %}" type='text/css'>
+ <script src="{% static 'js/jquery-ui.min.js' %}">
+ </script>
+{% endblock %}
+
+{% block title %} Manage Jobs {% endblock %}
+
+{% block pagecontent %}
+
+<div class="row">
+ <!-- Breadcrumbs -->
+ <div class="col-md-12">
+ <ul class="breadcrumb" id="breadcrumb">
+ <li><a href="{% url 'landing' %}">Home</a></li><span class="divider">&rarr;</span>
+ <li><a href="{% url 'manage' %}">Management</a></li><span class="divider">&rarr;</span>
+ <li><a href="{% url 'maintenance' %}">Maintenance</a></li><span class="divider">&rarr;</span>
+ <li>Manage Jobs</a>
+ </ul>
+ </div>
+</div>
+
+<p><b><big>Actions: </big></b>
+<a class="btn btn-default navbar-btn " id="submit-clearjobs" href="">Clear Jobs</a>
+
+<div class="row">
+ <div class="col-md-12">
+ <div class="page-header">
+ <h1 class="top-air" data-role="page-title"></h1>
+ </div>
+
+ {% url '' as xhr_table_url %}
+ {% include 'toastertable.html' %}
+ </div>
+</div>
+
+<!-- Javascript support -->
+<script type="text/javascript">
+ $(document).ready(function () {
+ var tableElt = $("#{{table_name}}");
+ var titleElt = $("[data-role='page-title']");
+
+ tableElt.on("table-done", function (e, total, tableParams) {
+ var title = "Manage Jobs ("+total+")";
+
+ if (tableParams.search || tableParams.filter) {
+ if (total === 0) {
+ title = "No Projects found";
+ }
+ else if (total > 0) {
+ title = total + " Job" + (total > 1 ? "s" : '') + " found";
+ }
+ }
+
+ /* Add handler into the Toaster Table context */
+ $('.trash-job').click(function() {
+ var result = confirm("Are you sure you want to remove Job '" + $(this).attr('x-data').split('|')[0] + "'?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-trash-job',
+ "record_id" : $(this).attr('x-data').split('|')[1],
+ });
+ }
+ });
+
+ titleElt.text(title);
+ });
+
+ function onCommitAjaxSuccess(data, textstatus) {
+ if (window.console && window.console.log) {
+ console.log("XHR returned:", data, "(" + textstatus + ")");
+ } else {
+ alert("NO CONSOLE:\n");
+ return;
+ }
+ if (data.error != "ok") {
+ alert("error on request:\n" + data.error);
+ return;
+ }
+ // reload the page with the updated tables
+ location.reload(true);
+ }
+
+ function onCommitAjaxError(jqXHR, textstatus, error) {
+ console.log("ERROR:"+error+"|"+textstatus);
+ alert("XHR errored1:\n" + error + "\n(" + textstatus + ")");
+ }
+
+ /* ensure cookie exists {% csrf_token %} */
+ function postCommitAjaxRequest(reqdata,url) {
+ url = url || "{% url 'xhr_job_post' %}";
+ var ajax = $.ajax({
+ type:"POST",
+ data: reqdata,
+ url:url,
+ headers: { 'X-CSRFToken': $.cookie("csrftoken")},
+ success: onCommitAjaxSuccess,
+ error: onCommitAjaxError,
+ });
+ }
+
+ $('#project_refresh').click(function(){
+ postCommitAjaxRequest({
+ "action" : 'submit-refresh-projects',
+ "audit_id" : '{% if hb_audit %}{{hb_audit.id}}{% endif %}',
+ },"");
+ });
+
+ $('#submit-clearjobs').click(function(){
+ var result = confirm("Are you sure you want to remove all jobs?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-clearjobs',
+ },"");
+ }
+ });
+
+ });
+
+ </script>
+{% endblock %}
diff --git a/lib/srtgui/templates/management.html b/lib/srtgui/templates/management.html
index b99f4613..60769873 100644
--- a/lib/srtgui/templates/management.html
+++ b/lib/srtgui/templates/management.html
@@ -1,168 +1,188 @@
{% extends "base.html" %}
{% load static %}
-{% load projecttags %}
+{% load jobtags %}
{% load humanize %}
{% block title %} Manage Resources {% endblock %}
{% block pagecontent %}
- <div class="row">
- <div class="col-md-7" style="padding-left: 50px;">
- <h1>Management</h1>
- </div>
+
+<div class="row">
+ <!-- Breadcrumbs -->
+ <div class="col-md-12">
+ <ul class="breadcrumb" id="breadcrumb">
+ <li><a href="{% url 'landing' %}">Home</a></li><span class="divider">&rarr;</span>
+ <li>Management</a>
+ </ul>
+ </div>
+</div>
+
+<div class="row">
+ <div class="col-md-7" style="padding-left: 50px;">
+ <h1>Management</h1>
</div>
- <div class="row">
- <div class="jumbotron well-transparent">
-
- <div class="col-md-6">
- <div>
- <table class="table table-striped table-condensed" data-testid="landing-hyperlinks-table">
- <thead>
- <tr>
- <th>Action</th>
- <th>Description</th>
- </tr>
- </thead>
-
- <tr>
- <td><a class="btn btn-info btn-lg" href="{% url 'triage_cves' %}">Triage CVE's</a></td>
- <td>Triage the CVE's ({{cve_new}})</td>
- </tr>
-
- <tr>
- <td><a class="btn btn-info btn-lg" href="{% url 'manage_notifications' %}">Pending notifications</a></td>
- <td>Triage the pending notifications ({{notification_total}})</td>
- </tr>
-
- <tr>
- <td><a class="btn btn-info btn-lg" href="{% url 'manage_report' %}">Summary Report</a></td>
- <td>Report on the over all response system status</td>
- </tr>
-
- <tr>
- <td><a class="btn btn-info btn-lg" href="{% url 'publish' %}">Publish Reports</a></td>
- <td>Process items to be published from the SRTool</td>
- </tr>
-
- {% if request.user.is_admin %}
- <tr>
- <td><a class="btn btn-info btn-lg" href="{% url 'users' %}">Manage Users</a></td>
- <td>Add, edit, and remove users</td>
- </tr>
-
- <tr>
- <td><a class="btn btn-info btn-lg" href="{% url 'sources' %}?nocache=1">Manage Sources</a></td>
- <td>Manage source list, perform manual pulls</td>
- </tr>
-
- <tr>
- <td><a class="btn btn-info btn-lg" href="{% url 'maintenance' %}?nocache=1">Maintenance</a></td>
- <td>Maintenance utilities ({{errorlog_total}})</td>
- </tr>
- {% endif %}
-
- </table>
- </div>
-
- </div>
-
- <div class="col-md-5">
- <b>Quick Info</b>
- <div class="well">
- <dl class="dl-horizontal">
- <dt>CVE's: Total Count =</dt>
- <dd>
- <a href="{% url 'cves' %}"> {{cve_total}} </a>
- </dd>
- <dt>Pending triaged =</dt>
- <dd>
- <a href="{% url 'cves' %}?limit=25&page=1&orderby=name&filter=is_status:new&default_orderby=name&filter_value=on&"> {{cve_new}} </a>
- </dd>
- <dt>Investigate =</dt>
- <dd>
- <a href="{% url 'cves' %}?limit=25&page=1&orderby=name&filter=is_status:investigate&default_orderby=name&filter_value=on&"> {{cve_investigate}} </a>
- </dd>
- <dt>Vulnerable =</dt>
- <dd>
- <a href="{% url 'cves' %}?limit=25&page=1&orderby=name&filter=is_status:vulnerable&default_orderby=name&filter_value=on&"> {{cve_vulnerable}} </a>
- </dd>
- <dt>Not Vulnerable =</dt>
- <dd>
- <a href="{% url 'cves' %}?limit=25&page=1&orderby=name&filter=is_status:not_vulnerable&default_orderby=name&filter_value=on&"> {{cve_not_vulnerable}} </a>
- </dd>
- <dt>Vulnerabilities: Total Count =</dt>
- <dd>
- <a href="{% url 'vulnerabilities' %}"> {{vulnerability_total}} </a>
- </dd>
- <dt>Open =</dt>
- <dd>
- <a href="{% url 'vulnerabilities' %}?limit=25&page=1&orderby=name&filter=is_outcome:open&default_orderby=name&filter_value=on&"> {{vulnerability_open}} </a>
- </dd>
- <dt>Critical active =</dt>
- <dd>
- <a href="{% url 'vulnerabilities' %}?limit=25&page=1&orderby=name&filter=is_priority:critical&default_orderby=name&filter_value=on&" %}> {{vulnerability_critical}} </a>
- </dd>
- <dt>High active =</dt>
- <dd>
- <a href="{% url 'vulnerabilities' %}?limit=25&page=1&orderby=name&filter=is_priority:high&default_orderby=name&filter_value=on&" %}> {{vulnerability_high}} </a>
- </dd>
- <dt>Medium active =</dt>
- <dd>
- <a href="{% url 'vulnerabilities' %}?limit=25&page=1&orderby=name&filter=is_priority:medium&default_orderby=name&filter_value=on&" %}> {{vulnerability_medium}} </a>
- </dd>
-
- <dt>Investigations: Total Count =</dt>
- <dd>
- <a href="{% url 'investigations' %}" %}> {{investigation_total}} </a>
- </dd>
- <dt>Open =</dt>
- <dd>
- <a href="{% url 'investigations' %}?limit=25&page=1&orderby=name&filter=is_outcome:open&default_orderby=name&filter_value=on&" %}> {{investigation_open}} </a>
- </dd>
- <dt>Critical active =</dt>
- <dd>
- <a href="{% url 'investigations' %}?limit=25&page=1&orderby=name&filter=is_priority:critical&default_orderby=name&filter_value=on&" %}> {{investigation_critical}} </a>
- </dd>
- <dt>High active =</dt>
- <dd>
- <a href="{% url 'investigations' %}?limit=25&page=1&orderby=name&filter=is_priority:high&default_orderby=name&filter_value=on&" %}> {{investigation_high}} </a>
- </dd>
- <dt>Medium active =</dt>
- <dd>
- <a href="{% url 'investigations' %}?limit=25&page=1&orderby=name&filter=is_priority:medium&default_orderby=name&filter_value=on&" %}> {{investigation_medium}} </a>
- </dd>
-
- <dt>Defects: Total Count =</dt>
- <dd>
- <a href="{% url 'defects' %}" %}> {{defect_total}} </a>
- </dd>
- <dt>Open =</dt>
- <dd>
- <a href="{% url 'defects' %}?limit=25&page=1&orderby=-priority&filter=is_srt_outcome:open&default_orderby=name&filter_value=on&" %}> {{defect_open}} </a>
- </dd>
- <dt>InProgress =</dt>
- <dd>
- <a href="{% url 'defects' %}?limit=25&page=1&orderby=-priority&filter=is_defect_status:in_progress&default_orderby=name&filter_value=on&" %}> {{defect_inprogress}} </a>
- </dd>
- <dt>P1 active =</dt>
- <dd>
- <a href="{% url 'defects' %}?limit=25&page=1&orderby=-priority&filter=is_defect_priority:critical&default_orderby=name&filter_value=on&" %}> {{defect_p1}} </a>
- </dd>
- <dt>P2 active =</dt>
- <dd>
- <a href="{% url 'defects' %}?limit=25&page=1&orderby=-priority&filter=is_defect_priority:high&default_orderby=name&filter_value=on&" %}> {{defect_p2}} </a>
- </dd>
-
- <dt>Packages: Affected=</dt>
- <dd>
- <a href="{% url 'cpes_srtool' %}" %}> {{package_total}} </a>
- </dd>
-
- </dl>
- </div>
- </div>
-
- </div>
+</div>
+
+<div class="row">
+ <div class="col-md-12">
+ {% with mru=mru %}
+ {% include 'mrj_section.html' %}
+ {% endwith %}
+ </div>
+</div>
+
+<div class="row">
+ <div class="jumbotron well-transparent">
+
+ <div class="col-md-6">
+ <div>
+ <table class="table table-striped table-condensed" data-testid="landing-hyperlinks-table">
+ <thead>
+ <tr>
+ <th>Action</th>
+ <th>Description</th>
+ </tr>
+ </thead>
+
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'triage_cves' %}">Triage CVE's</a></td>
+ <td>Triage the CVE's ({{cve_new}})</td>
+ </tr>
+
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'manage_notifications' %}">Pending notifications</a></td>
+ <td>Triage the pending notifications ({{notification_total}})</td>
+ </tr>
+
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'manage_report' %}">Summary Report</a></td>
+ <td>Report on the over all response system status</td>
+ </tr>
+
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'publish' %}">Publish Reports</a></td>
+ <td>Process items to be published from the SRTool</td>
+ </tr>
+
+ {% if request.user.is_admin %}
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'users' %}">Manage Users</a></td>
+ <td>Add, edit, and remove users</td>
+ </tr>
+
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'sources' %}?nocache=1">Manage Sources</a></td>
+ <td>Manage source list, perform manual pulls</td>
+ </tr>
+
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'maintenance' %}?nocache=1">Maintenance</a></td>
+ <td>Maintenance utilities ({{errorlog_total}})</td>
+ </tr>
+ {% endif %}
+
+ </table>
+ </div>
+
</div>
+ <div class="col-md-5">
+ <b>Quick Info</b>
+ <div class="well">
+ <dl class="dl-horizontal">
+ <dt>CVE's: Total Count =</dt>
+ <dd>
+ <a href="{% url 'cves' %}"> {{cve_total}} </a>
+ </dd>
+ <dt>Pending triaged =</dt>
+ <dd>
+ <a href="{% url 'cves' %}?limit=25&page=1&orderby=name&filter=is_status:new&default_orderby=name&filter_value=on&"> {{cve_new}} </a>
+ </dd>
+ <dt>Investigate =</dt>
+ <dd>
+ <a href="{% url 'cves' %}?limit=25&page=1&orderby=name&filter=is_status:investigate&default_orderby=name&filter_value=on&"> {{cve_investigate}} </a>
+ </dd>
+ <dt>Vulnerable =</dt>
+ <dd>
+ <a href="{% url 'cves' %}?limit=25&page=1&orderby=name&filter=is_status:vulnerable&default_orderby=name&filter_value=on&"> {{cve_vulnerable}} </a>
+ </dd>
+ <dt>Not Vulnerable =</dt>
+ <dd>
+ <a href="{% url 'cves' %}?limit=25&page=1&orderby=name&filter=is_status:not_vulnerable&default_orderby=name&filter_value=on&"> {{cve_not_vulnerable}} </a>
+ </dd>
+ <dt>Vulnerabilities: Total Count =</dt>
+ <dd>
+ <a href="{% url 'vulnerabilities' %}"> {{vulnerability_total}} </a>
+ </dd>
+ <dt>Open =</dt>
+ <dd>
+ <a href="{% url 'vulnerabilities' %}?limit=25&page=1&orderby=name&filter=is_outcome:open&default_orderby=name&filter_value=on&"> {{vulnerability_open}} </a>
+ </dd>
+ <dt>Critical active =</dt>
+ <dd>
+ <a href="{% url 'vulnerabilities' %}?limit=25&page=1&orderby=name&filter=is_priority:critical&default_orderby=name&filter_value=on&" %}> {{vulnerability_critical}} </a>
+ </dd>
+ <dt>High active =</dt>
+ <dd>
+ <a href="{% url 'vulnerabilities' %}?limit=25&page=1&orderby=name&filter=is_priority:high&default_orderby=name&filter_value=on&" %}> {{vulnerability_high}} </a>
+ </dd>
+ <dt>Medium active =</dt>
+ <dd>
+ <a href="{% url 'vulnerabilities' %}?limit=25&page=1&orderby=name&filter=is_priority:medium&default_orderby=name&filter_value=on&" %}> {{vulnerability_medium}} </a>
+ </dd>
+
+ <dt>Investigations: Total Count =</dt>
+ <dd>
+ <a href="{% url 'investigations' %}" %}> {{investigation_total}} </a>
+ </dd>
+ <dt>Open =</dt>
+ <dd>
+ <a href="{% url 'investigations' %}?limit=25&page=1&orderby=name&filter=is_outcome:open&default_orderby=name&filter_value=on&" %}> {{investigation_open}} </a>
+ </dd>
+ <dt>Critical active =</dt>
+ <dd>
+ <a href="{% url 'investigations' %}?limit=25&page=1&orderby=name&filter=is_priority:critical&default_orderby=name&filter_value=on&" %}> {{investigation_critical}} </a>
+ </dd>
+ <dt>High active =</dt>
+ <dd>
+ <a href="{% url 'investigations' %}?limit=25&page=1&orderby=name&filter=is_priority:high&default_orderby=name&filter_value=on&" %}> {{investigation_high}} </a>
+ </dd>
+ <dt>Medium active =</dt>
+ <dd>
+ <a href="{% url 'investigations' %}?limit=25&page=1&orderby=name&filter=is_priority:medium&default_orderby=name&filter_value=on&" %}> {{investigation_medium}} </a>
+ </dd>
+
+ <dt>Defects: Total Count =</dt>
+ <dd>
+ <a href="{% url 'defects' %}" %}> {{defect_total}} </a>
+ </dd>
+ <dt>Open =</dt>
+ <dd>
+ <a href="{% url 'defects' %}?limit=25&page=1&orderby=-priority&filter=is_srt_outcome:open&default_orderby=name&filter_value=on&" %}> {{defect_open}} </a>
+ </dd>
+ <dt>InProgress =</dt>
+ <dd>
+ <a href="{% url 'defects' %}?limit=25&page=1&orderby=-priority&filter=is_defect_status:in_progress&default_orderby=name&filter_value=on&" %}> {{defect_inprogress}} </a>
+ </dd>
+ <dt>P1 active =</dt>
+ <dd>
+ <a href="{% url 'defects' %}?limit=25&page=1&orderby=-priority&filter=is_defect_priority:critical&default_orderby=name&filter_value=on&" %}> {{defect_p1}} </a>
+ </dd>
+ <dt>P2 active =</dt>
+ <dd>
+ <a href="{% url 'defects' %}?limit=25&page=1&orderby=-priority&filter=is_defect_priority:high&default_orderby=name&filter_value=on&" %}> {{defect_p2}} </a>
+ </dd>
+
+ <dt>Packages: Affected=</dt>
+ <dd>
+ <a href="{% url 'cpes_srtool' %}" %}> {{package_total}} </a>
+ </dd>
+
+ </dl>
+ </div>
+ </div>
+
+ </div>
+</div>
+
{% endblock %}
diff --git a/lib/srtgui/templates/mrj_section.html b/lib/srtgui/templates/mrj_section.html
new file mode 100755
index 00000000..480dfef2
--- /dev/null
+++ b/lib/srtgui/templates/mrj_section.html
@@ -0,0 +1,194 @@
+{% load static %}
+{% load humanize %}
+<script src="{% static 'js/mrjsection.js' %}"></script>
+
+{% if mru %}
+ <div id="latest-jobs">
+ {% for job in mru %}
+ <div id="job-instance-{{job.id}}" data-latest-job-result="{{job.id}}" class="alert job-result {% if job.status == job.SUCCESS %}alert-success{% elif job.status == job.ERRORS %}alert-danger{% else %}alert-info{% endif %}">
+ <!-- job title -->
+ <div class="row job-name">
+ <div class="col-md-12">
+ <small>
+ {{job.name}}{% if request.user.is_admin %} ({{job.id}}){% endif %}
+ </small>
+ </div>
+ </div>
+
+ <div class="row" data-role="job-status-container">
+ <div class="col-md-12">
+ Loading...
+ </div>
+ </div>
+ </div>
+ {% endfor %}
+ </div>
+{% endif %}
+
+<!-- job main template -->
+<script id="job-template" type="text/x-jsrender">
+ <div class="col-md-3">
+ <!-- only show link for completed jobs -->
+ <%if state == 'Success' || state == 'Errors'%>
+ <%:targets%>
+ <%else%>
+ <span data-toggle="tooltip" id="job-message-done-<%:id%>" data-role="targets-text" title="Job: <%:targets%>">
+ <%:targets%>
+ </span>
+ <%/if%>
+ </div>
+
+ <div data-job-state="<%:state%>">
+ <%if state == 'Success' || state == 'Errors'%>
+ <%include tmpl='#succeeded-or-failed-job-template'/%>
+ <%else state == 'Cancelling'%>
+ <%include tmpl='#cancelling-job-template'/%>
+ <%else state == 'NotStarted'%>
+ <%include tmpl='#starting-template'/%>
+ <%else state == 'InProgress'%>
+ <%include tmpl='#in-progress-job-template'/%>
+ <%else state == 'Cancelled'%>
+ <%include tmpl='#cancelled-job-template'/%>
+ <%/if%>
+ </div>
+</script>
+
+<!-- queued job -->
+<script id="queued-job-template" type="text/x-jsrender">
+ <div class="col-md-5">
+ <span class="glyphicon glyphicon-question-sign get-help get-help-blue"
+ title="This job is waiting for the background application to start">
+ </span>
+
+ Job queued
+ </div>
+
+ <div class="col-md-4">
+ <!-- cancel button -->
+ <%include tmpl='#cancel-template'/%>
+ </div>
+</script>
+
+<!-- in progress job; at least one task finished -->
+<script id="in-progress-job-template" type="text/x-jsrender">
+ <!-- progress bar and task completion percentage -->
+ <div data-role="job-status" class="col-md-4 col-md-offset-1 progress-info">
+ <!-- progress bar -->
+ <div class="progress" id="job-pc-done-title-<%:id%>">
+ <div id="job-pc-done-bar-<%:id%>"
+ style="width: <%:tasks_complete_percentage%>%;"
+ class="progress-bar">
+ </div>
+ </div>
+ </div>
+
+ <div class="col-md-4 progress-info">
+ <!-- task completion percentage -->
+ <span id="job-pc-done-<%:id%>"><%:tasks_complete_percentage%></span>% of
+ tasks complete
+
+ <!-- cancel button -->
+ <%include tmpl='#cancel-template'/%>
+ </div>
+</script>
+
+<!-- cancelling job -->
+<script id="cancelling-job-template" type="text/x-jsrender">
+ <div class="col-md-9">
+ Cancelling the job ...
+ </div>
+</script>
+
+<!-- succeeded or failed job -->
+<script id="succeeded-or-failed-job-template" type="text/x-jsrender">
+ <!-- completed_on -->
+ <div class="col-md-2">
+ <%:completed_on%>
+ </div>
+
+ <!-- errors -->
+ <div class="col-md-2">
+ <%if errors%>
+ <span class="glyphicon glyphicon-minus-sign"></span>
+ <a href="<%:dashboard_errors_url%>" class="alert-link">
+ <%:errors%> error<%:errors_pluralise%>
+ </a>
+ <%/if%>
+ </div>
+
+ <!-- warnings -->
+ <div class="col-md-2">
+ <%if warnings%>
+ <span class="glyphicon glyphicon-warning-sign job-warnings"></span>
+ <a href="<%:dashboard_warnings_url%>" class="alert-link job-warnings">
+ <%:warnings%> warning<%:warnings_pluralise%>
+ </a>
+ <%/if%>
+ </div>
+
+<!-- <%if errors == 0 and warnings == 0%>
+ <div class="col-md-2">
+ No Errors
+ </div>
+ <%/if%>
+-->
+
+ <!-- job time -->
+ <div class="col-md-3">
+ Job time:
+ <span data-role="data-recent-job-jobtime-field">
+ <b><%:jobtime%></b>
+ </span>
+ <form id="downloadbanner-log" enctype="multipart/form-data" method="post" >{% csrf_token %}
+ <input type="hidden" name="action" value="download-log">
+ <input type="hidden" name="report_path" value="JOBLOG">
+ <a href="/srtgui/joblog/<%:id%>" class="glyphicon glyphicon-download-alt submit-download-joblog" x-data="log" target="_blank"></a>
+ </form>
+ </div>
+</script>
+
+<!-- cancelled job -->
+<script id="cancelled-job-template" type="text/x-jsrender">
+ <!-- job cancelled message -->
+ <div class="col-md-6">
+ Job cancelled
+ </div>
+</script>
+
+<!-- cancel button or no cancel icon -->
+<script id="cancel-template" type="text/x-jsrender">
+ <!-- cancel button -->
+ <span class="cancel-job-btn pull-right alert-link"
+ data-jobrequest-id="<%:id%>" data-request-url="<%:cancel_url%>">
+ <span class="glyphicon glyphicon-remove-circle"></span>
+ Cancel
+ </span>
+</script>
+
+<script>
+ $(document).ready(function () {
+ var ctx = {
+ }
+
+ try {
+ mrjSectionInit(ctx);
+ $('.submit-download-joblog').click(function() {
+ alert("submit-download-joblog:"+this.getAttribute("x-data"));
+ $("#downloadbanner-"+this.getAttribute("x-data")).submit();
+ });
+ } catch (e) {
+ document.write("Sorry, An error has occurred loading this page");
+ console.warn(e);
+ }
+
+ $('.submit-downloadattachment').click(function() {
+ $("#downloadbanner-"+this.getAttribute("x-data")).submit();
+ });
+
+ $('.submit-download-joblog').click(function() {
+ alert("submit-download-joblog:"+this.getAttribute("x-data"));
+ $("#downloadbanner-job").submit();
+ });
+
+ });
+</script>
diff --git a/lib/srtgui/templates/product.html b/lib/srtgui/templates/product.html
index 7f8d1b1f..64234778 100644
--- a/lib/srtgui/templates/product.html
+++ b/lib/srtgui/templates/product.html
@@ -1,6 +1,6 @@
{% extends "base.html" %}
-{% load projecttags %}
+{% load jobtags %}
{% block title %} {{object.name}} - SRTool {% endblock %}
diff --git a/lib/srtgui/templates/publish.html b/lib/srtgui/templates/publish.html
index 0e7f58e7..6c915f85 100644
--- a/lib/srtgui/templates/publish.html
+++ b/lib/srtgui/templates/publish.html
@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% load static %}
-{% load projecttags %}
+{% load jobtags %}
{% load humanize %}
{% block title %} Publish Reports {% endblock %}
diff --git a/lib/srtgui/templates/publish_diff_snapshot.html b/lib/srtgui/templates/publish_diff_snapshot.html
index cf0f2294..44958632 100644
--- a/lib/srtgui/templates/publish_diff_snapshot.html
+++ b/lib/srtgui/templates/publish_diff_snapshot.html
@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% load static %}
-{% load projecttags %}
+{% load jobtags %}
{% load humanize %}
{% block title %} Publish Requests {% endblock %}
@@ -18,6 +18,14 @@
</div>
</div>
+<div class="row">
+ <div class="col-md-12">
+ {% with mru=mru %}
+ {% include 'mrj_section.html' %}
+ {% endwith %}
+ </div>
+</div>
+
<h2>Publish Report Management</h2>
<ul>
<li>The SRTool supports exporting new and updated CVEs to external publishing tools</li>
@@ -63,6 +71,7 @@
<div>
<span style="padding-left:30px;"><button id="export-snapshot" class="btn btn-default" type="button">Generate</button></span>
+ <span style="padding-left:30px;"><button id="export-snapshot-progress" class="btn btn-default" type="button">Generate (Progress)</button></span>
<!--<button type="submit" name="action" value="export-snapshot">Export</button> -->
<span id="export-snapshot-text">Generate the publish table on-demand (using snapshots)</span>
<span id="generating-report" hidden style="color:red"><I>... Generating the report - this will take a few minutes ...</I></span>
@@ -80,7 +89,7 @@
</option>
{% endfor %}
</select>
- <span style="padding-left:30px;"><button id="export-snapshot" class="btn btn-default" type="button" disabled>Save</button></span>
+ <span style="padding-left:30px;"><button id="export-snapshot-XXX" class="btn btn-default" type="button" disabled>Save</button></span>
<!--<button type="submit" name="action" value="export-snapshot">Export</button> -->
Save the automatic publishing frequency
</div>
@@ -302,6 +311,35 @@
}
});
+ $('#export-snapshot-progress').click(function(){
+ snap_date_base = $("#snap_date_base").val();
+ snap_date_top = $("#snap_date_top").val();
+ snap_date_start = $("#snap_date_start").val();
+ snap_date_stop = $("#snap_date_stop").val();
+ if (snap_date_start > snap_date_stop) {
+ alert("Error: the start date is after the stop date");
+ return;
+ }
+ if (snap_date_start < snap_date_base) {
+ alert("Error: the start date is before the snapshot base date");
+ return;
+ }
+ if (snap_date_stop > snap_date_top) {
+ alert("Error: the stop date is after the snapshot top date");
+ return;
+ }
+ var result = confirm("Generate the report? This will take several minutes.");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'export-snapshot-progress',
+ "snap_date_base" : snap_date_base,
+ "snap_date_top" : snap_date_top,
+ "snap_date_start" : snap_date_start,
+ "snap_date_stop" : snap_date_stop
+ });
+ }
+ });
+
/* Manage report files */
diff --git a/lib/srtgui/templates/report.html b/lib/srtgui/templates/report.html
index 4c2b2450..f89628fe 100644
--- a/lib/srtgui/templates/report.html
+++ b/lib/srtgui/templates/report.html
@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% load static %}
-{% load projecttags %}
+{% load jobtags %}
{% load humanize %}
{% block title %} Report/Export {% endblock %}
@@ -34,6 +34,7 @@
Note: There is no report defined for this page.<br>
{% endif %}
+ <!--
{% if report_get_title %}
<hr>
Title:<br>
@@ -41,6 +42,7 @@
<br>
{% endif %}
<hr>
+ -->
{% if report_recordrange_list %}
Record Range:<br>
diff --git a/lib/srtgui/templates/snippets/gitrev_popover.html b/lib/srtgui/templates/snippets/gitrev_popover.html
index c1e3dabf..445c39cd 100644
--- a/lib/srtgui/templates/snippets/gitrev_popover.html
+++ b/lib/srtgui/templates/snippets/gitrev_popover.html
@@ -1,4 +1,4 @@
-{% load projecttags %}
+{% load jobtags %}
{% if vcs_ref|is_shaid %}
<a class="btn btn-default" data-content="{{vcs_ref}}">
{{vcs_ref|truncatechars:10}}
diff --git a/lib/srtgui/templates/snippets/investigations_popover.html b/lib/srtgui/templates/snippets/investigations_popover.html
index 0f65d3d4..22197a13 100644
--- a/lib/srtgui/templates/snippets/investigations_popover.html
+++ b/lib/srtgui/templates/snippets/investigations_popover.html
@@ -1,5 +1,5 @@
{# Popover that displays the Investigations related to this Product #}
-{% load projecttags %}
+{% load jobtags %}
{% with investigations='Wind River Linux 9' %}
{% with count_investigations=1 %}
diff --git a/lib/srtgui/templates/snippets/pkg_dependencies_popover.html b/lib/srtgui/templates/snippets/pkg_dependencies_popover.html
index 273437e3..eefbc122 100644
--- a/lib/srtgui/templates/snippets/pkg_dependencies_popover.html
+++ b/lib/srtgui/templates/snippets/pkg_dependencies_popover.html
@@ -1,5 +1,5 @@
{# Popover that displays the dependences and sizes of a package 'data' used in the Packages table #}
-{% load projecttags %}
+{% load jobtags %}
{% with package_deps=data.package_dependencies_source|for_target:extra.target_name %}
{% with count_package=package_deps.packages|length %}
diff --git a/lib/srtgui/templates/snippets/pkg_revdependencies_popover.html b/lib/srtgui/templates/snippets/pkg_revdependencies_popover.html
index e6ef816e..8eca0357 100644
--- a/lib/srtgui/templates/snippets/pkg_revdependencies_popover.html
+++ b/lib/srtgui/templates/snippets/pkg_revdependencies_popover.html
@@ -1,5 +1,5 @@
{# Popover that displays the reverse dependences and sizes of a package 'data' used in the Packages table #}
-{% load projecttags %}
+{% load jobtags %}
{% with package_deps=data.package_dependencies_target|for_target:extra.target_name %}
{% with count_package=package_deps.packages|length %}
diff --git a/lib/srtgui/templates/sources-toastertable.html b/lib/srtgui/templates/sources-toastertable.html
index 1721e3b0..279f279c 100644
--- a/lib/srtgui/templates/sources-toastertable.html
+++ b/lib/srtgui/templates/sources-toastertable.html
@@ -1,5 +1,8 @@
{% extends 'base.html' %}
+
{% load static %}
+{% load jobtags %}
+{% load humanize %}
{% block extraheadcontent %}
<link rel="stylesheet" href="{% static 'css/jquery-ui.min.css' %}" type='text/css'>
@@ -24,6 +27,14 @@
</div>
</div>
+<div class="row">
+ <div class="col-md-12">
+ {% with mru=mru %}
+ {% include 'mrj_section.html' %}
+ {% endwith %}
+ </div>
+</div>
+
<div class="row">
<div class="col-md-12">
@@ -66,6 +77,76 @@
});
$('#report_link').attr('href',"{% url 'report' request.resolver_match.url_name %}?record_list="+record_list);
+ //
+ // Listeners that must reside in the Toaster table context
+ //
+
+ /* Dynamically run the job function */
+ $('.run-update-job').click(function(){
+ var datasource_id = $(this).attr('x-data');
+ var result = confirm("Are you sure you want to force update this datasource right now?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-run-update-job',
+ "id" : datasource_id,
+ });
+ }
+ });
+
+ // Toggle the data source init/update enables
+ $('.source-enabled').click(function() {
+ postCommitAjaxRequest({
+ "action" : 'submit-toggle-enable',
+ "id" : $(this).attr('x-data'),
+ });
+ });
+
+ function onCommitAjaxSuccess(data, textstatus) {
+ if (window.console && window.console.log) {
+ console.log("XHR returned:", data, "(" + textstatus + ")");
+ } else {
+ alert("NO CONSOLE:\n");
+ return;
+ }
+ if (data.error == "no_refresh") {
+ if (data.data_message) {
+ const nv_pair = data.data_message.split("=");
+ document.getElementById('attr_'+nv_pair[0]).innerText = nv_pair[1];
+ if (0 <= nv_pair[1].indexOf("DISABLE ")) {
+ document.getElementById('next_on_'+nv_pair[0]).style.display = 'none';
+ document.getElementById('next_off_'+nv_pair[0]).style.display = 'inline';
+ } else {
+ document.getElementById('next_on_'+nv_pair[0]).style.display = 'inline';
+ document.getElementById('next_off_'+nv_pair[0]).style.display = 'none';
+ };
+ };
+ return;
+ }
+ if (data.error != "ok") {
+ alert("error on request:\n" + data.error);
+ return;
+ }
+ // reload the page with the updated tables
+ location.reload(true);
+ }
+
+ function onCommitAjaxError(jqXHR, textstatus, error) {
+ console.log("ERROR:"+error+"|"+textstatus);
+ alert("XHR errored1:\n" + error + "\n(" + textstatus + ")");
+ }
+
+ /* ensure cookie exists {% csrf_token %} */
+ function postCommitAjaxRequest(reqdata) {
+ var ajax = $.ajax({
+ type:"POST",
+ data: reqdata,
+ url:"{% url 'xhr_sources_commit' %}",
+ headers: { 'X-CSRFToken': $.cookie("csrftoken")},
+ success: onCommitAjaxSuccess,
+ error: onCommitAjaxError,
+ });
+ }
+
});
});
</script>
diff --git a/lib/srtgui/templates/sources.html b/lib/srtgui/templates/sources.html
index 1b017c06..df2852a2 100644
--- a/lib/srtgui/templates/sources.html
+++ b/lib/srtgui/templates/sources.html
@@ -1,6 +1,6 @@
{% extends "base.html" %}
-{% load projecttags %}
+{% load jobtags %}
{% block title %} Data Sources - SRTool {% endblock %}
diff --git a/lib/srtgui/templates/srtool_metadata_include.html b/lib/srtgui/templates/srtool_metadata_include.html
index 05c62d3d..297510a1 100755
--- a/lib/srtgui/templates/srtool_metadata_include.html
+++ b/lib/srtgui/templates/srtool_metadata_include.html
@@ -11,10 +11,17 @@
{% if request.user.is_creator %}
<i>Publish</i> = {{object.get_publish_text}}, <i>Publish Date</i> = {{object.publish_date}}, <i>Acknowledge Date</i> = {{object.acknowledge_date|date:'Y-m-d'}}, <i>Initial Release</i> = {{object.publishedDate}}, <i>Last Modified</i> = {{object.lastModifiedDate}}
<!--<a class="btn btn-default navbar-btn " id="login-button" href="">Publish Now</a> -->
+ , <i>Public = </i> {{object.get_public_text}}&nbsp;&nbsp;
{% else %}
<i>Publish = {{object.get_publish_text}}</i>
{% endif %}
</LI>
+ {% elif default_category == "VULNERABILITY" %}
+ {% if request.user.is_creator %}
+ <i>Public = </i> {{object.get_public_text}}&nbsp;&nbsp;
+ {% endif %}
+ <i>Outcome:</i> {{object.get_outcome_text}}
+ <p>
{% else %}
<i>Outcome:</i> {{object.get_outcome_text}}
<p>
@@ -64,6 +71,16 @@
<option value="8" {% if 8 == object.status %}selected{% endif %}>(Vulnerable)</option>
<option value="9" {% if 9 == object.status %}selected{% endif %}>(Not Vulnerable)</option>
</select>
+
+ {% if default_category == "CVE" or default_category == "VULNERABILITY" %}
+ &nbsp;&nbsp;
+ <i>Public</i> =
+ <select name="Public" id="select-public-state">
+ <option value="1" {% if object.public %}selected{% endif %}>Public</option>
+ <option value="0" {% if not object.public %}selected{% endif %}>Private</option>
+ </select>
+ {% endif %}
+
<p>
{% if default_category == "CVE" %}
<i>Publish</i> =
@@ -85,15 +102,21 @@
<option value="3" {% if 3 == object.outcome_state %}selected{% endif %}>Closed (Won't Fix)</option>
</select>
{% endif %}
- <p>Comments: <input type="text" placeholder="Edit comments" id="text-note" size="80" value="{{object.comments}}"></p>
+ <p>Notes: <input type="text" placeholder="Edit comments" id="text-note" size="80" value="{{object.comments}}"></p>
{% if request.user.is_creator %}
- <p>Private Comments: <input type="text" placeholder="Edit private comments" id="text-private-note" size="80" value="{{object.comments_private}}"></p>
+ <p>Private Notes: <input type="text" placeholder="Edit private comments" id="text-private-note" size="80" value="{{object.comments_private}}"></p>
{% endif %}
<p>Tags: <input type="text" placeholder="Edit tags" id="text-tags" size="80" value="{{object.tags}}"></p>
<p>Affected Components: <input type="text" placeholder="Edit affected components" id="text-affected-components" size="80" value="{{object.packages}}"></p>
{% if default_category == "CVE" %}
<i>Acknowledge Date</i> = <input type="text" placeholder="Acknowledge Date" id="text-acknowledge-date" size="40" value="{{object.acknowledge_date|date:'Y-m-d'}}"> (YYYY-MM-DD, or empty string for None)<p>
{% endif %}
+
+ {% if default_category == "VULNERABILITY" %}
+ <p>Description:<p>
+ <textarea name="description" rows="9" style="min-width: 100%" class="localblue" id="text-description">{{object.description}}</textarea>
+ {% endif %}
+
<p><p>
</fieldset>
</div>
diff --git a/lib/srtgui/templates/tablesort.html b/lib/srtgui/templates/tablesort.html
index 36247429..1224b3bf 100644
--- a/lib/srtgui/templates/tablesort.html
+++ b/lib/srtgui/templates/tablesort.html
@@ -1,4 +1,4 @@
-{% load projecttags %}
+{% load jobtags %}
<!-- component to display a generic table -->
{% if disable_sort %}
<table class="table table-bordered table-hover" id="detail_table">
diff --git a/lib/srtgui/templates/tbd.html b/lib/srtgui/templates/tbd.html
index d8979f6e..a50d806f 100644
--- a/lib/srtgui/templates/tbd.html
+++ b/lib/srtgui/templates/tbd.html
@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% load static %}
-{% load projecttags %}
+{% load jobtags %}
{% load humanize %}
{% block title %} TBD {% endblock %}
diff --git a/lib/srtgui/templates/toastertable-simple.html b/lib/srtgui/templates/toastertable-simple.html
index 56cd2ce3..858c87e6 100644
--- a/lib/srtgui/templates/toastertable-simple.html
+++ b/lib/srtgui/templates/toastertable-simple.html
@@ -1,6 +1,6 @@
{% load static %}
-{% load projecttags %}
+{% load jobtags %}
<script src="{% static 'js/table.js' %}"></script>
<script src="{% static 'js/layerBtn.js' %}"></script>
diff --git a/lib/srtgui/templates/toastertable.html b/lib/srtgui/templates/toastertable.html
index 99eb01e2..a33321a9 100644
--- a/lib/srtgui/templates/toastertable.html
+++ b/lib/srtgui/templates/toastertable.html
@@ -1,10 +1,28 @@
{% load static %}
-{% load projecttags %}
+{% load jobtags %}
<script src="{% static 'js/table.js' %}"></script>
<script src="{% static 'js/layerBtn.js' %}"></script>
<script>
+
+ var lstFilterval = [];
+ function ClearFilter(test) {
+ (function(){
+ var ctx = {
+ tableName : "{{table_name}}",
+ url : "{{ xhr_table_url }}?format=json",
+ title : "{{title}}",
+ };
+
+ try {
+ tableInit(ctx,test);
+ } catch (e) {
+ document.write("Problem loading table widget: " + e);
+ }
+ })();
+ }
+
$(document).ready(function() {
(function(){
@@ -26,7 +44,9 @@
{% include 'toastertable-filter.html' %}
<div class="row-fluid" id="empty-state-{{table_name}}" style="display:none">
- <div class="alert alert-info">{{empty_state|safe}}</div>
+ <div class="alert alert-info">{{empty_state|safe}}
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<button id="clear-all-filter" class="btn btn-default navbar-btn" align="right">Clear All Filters</button>
+ </div>
</div>
<div id="no-results-{{table_name}}" style="display:none">
@@ -83,6 +103,11 @@
</select>
</div>
</form>
+
+ <div class="btn-group navbar-right">
+ <button id="clear-all-filter" class="btn btn-default navbar-btn " >Clear All Filters</button>&nbsp;&nbsp;&nbsp;&nbsp;
+ </div>
+
<div class="btn-group navbar-right">
<button id="edit-columns-button" class="btn btn-default navbar-btn dropdown-toggle" data-toggle="dropdown">Edit columns
<span class="caret"></span>
diff --git a/lib/srtgui/templates/triage_cves.html b/lib/srtgui/templates/triage_cves.html
index ddef1501..0cc774d3 100644
--- a/lib/srtgui/templates/triage_cves.html
+++ b/lib/srtgui/templates/triage_cves.html
@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% load static %}
-{% load projecttags %}
+{% load jobtags %}
{% load humanize %}
{% block title %} Export Report {% endblock %}
diff --git a/lib/srtgui/templates/unavailable_artifact.html b/lib/srtgui/templates/unavailable_artifact.html
index fc77e405..dedaa41b 100644
--- a/lib/srtgui/templates/unavailable_artifact.html
+++ b/lib/srtgui/templates/unavailable_artifact.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% load projecttags %}
+{% load jobtags %}
{% load humanize %}
{% load static %}
diff --git a/lib/srtgui/templates/users.html b/lib/srtgui/templates/users.html
index fd2c8c18..970291b6 100644
--- a/lib/srtgui/templates/users.html
+++ b/lib/srtgui/templates/users.html
@@ -1,6 +1,6 @@
{% extends "base.html" %}
-{% load projecttags %}
+{% load jobtags %}
{% block title %} Users - SRTool {% endblock %}
@@ -34,46 +34,56 @@
<div style="padding-left: 25px;">
<p><b>Reader</b>: User that can read the content (Field, TechPubs)</p>
<p><b>Contributor</b>: Reader that can can add notes and attachements (Engineers, Test, Managers)</p>
- <p><b>Creator</b>: Contributor that can create Investiations and defect records </p>
+ <p><b>Creator</b>: Contributor that can create Investigations and defect records </p>
<p><b>Admin</b>: Creator that can manage users, data sources</p>
</div>
</div>
- <p/>
</div>
</div>
<div class="row" style="padding-left: 25px;">
<h3>User List
- <a class="btn btn-default navbar-btn " id="new-investigation-attachement" href="{% url 'edit_user' 0 %}">Add user</a>
+ <a class="btn btn-default navbar-btn " href="{% url 'edit_user' 0 %}">Add user</a>
</h3>
- <table class="table table-striped table-condensed" data-testid="vuln-hyperlinks-table">
+ <table class="table table-striped table-condensed">
<thead>
<tr>
+ {% if user.is_admin %}
+ <th>ID</th>
+ {% endif %}
<th>User</th>
<th>First</th>
<th>Last</th>
<th>Email</th>
<th>Role</th>
+ <th>Time zone</th>
<th>Group</th>
+ <th>Last Login</th>
<th>Manage</th>
</tr>
</thead>
{% if object.all %}
- {% for user in object.all %}
+ {% for user_obj in object.all %}
<tr>
- <td>{{ user.username }} </td>
- <td>{{ user.first_name }} </td>
- <td>{{ user.last_name }} </td>
- <td>{{ user.email }} </td>
- <td>{{ user.role }} </td>
- <td>{{ user.get_groups }} </td>
+ {% if user.is_admin %}
+ <td>{{ user_obj.id }}</td>
+ {% endif %}
+ <td>{{ user_obj.username }}</td>
+ <td>{{ user_obj.first_name }}</td>
+ <td>{{ user_obj.last_name }}</td>
+ <td>{{ user_obj.email }}</td>
+ <td>{{ user_obj.role }}</td>
+ <td>{{ user_obj.timezone }}</td>
+ <td>{% if user_obj.is_superuser %}SuperUser{% else %}{{ user_obj.get_groups }}{%endif %}</td>
+ <td>{{ user_obj.last_login|date:'Y-m-d'}}</td>
<td>
- {% if user.is_superuser or not user.is_staff %}
+ {% if user_obj.is_superuser or not user_obj.is_staff %}
<span id="user_'+{{user.id}}+'" class="js-user-name"></span>
- <a href="{% url 'edit_user' user.id %}"><span class="glyphicon glyphicon-edit js-icon-pencil-config_var"></span></a>
- <span class="glyphicon glyphicon-trash trash-user" id="user_trash_'+{{user.id}}+'" x-data="{{user.username}}:{{user.id}}"></span>
+ <a href="{% url 'edit_user' user_obj.id %}"><span class="glyphicon glyphicon-edit js-icon-pencil-config_var"></span></a>
+ &nbsp;&nbsp;
+ <span class="glyphicon glyphicon-trash trash-user" id="user_trash_'+{{user_obj.id}}+'" x-data="{{user_obj.username}}:{{user_obj.id}}"></span>
{% else %}
Built-in
{% endif %}
@@ -91,6 +101,89 @@
</div>
+<!-- pass the full user list here -->
+{% for user in object.all %}
+<input type="hidden" class="js-checkbox-users-list" value="{{user.id}}|{{user.user_fullname}}">
+{% endfor %}
+
+<div class="row" id="group-section" style="padding-left: 25px;width:70%;">
+
+ <h3 style="white-space: nowrap;">Group List ({{builtin_groups}})
+ <a class="btn btn-default navbar-btn" id="add_group">Add group</a>
+ <!--<button class="execute" id="add_group" style="display:inline-block;"> Add group: </button>-->
+ <input type="text" value="" style="width:16%;display:inline-block;" class="form-control" id="add-group-name" placeholder="Name for new group">
+ </h3>
+
+ <div class="row" id="edit_group_options" style="display:none;padding-left:25px;color:DarkCyan;">
+ <h3>Group Edit:
+ <a class="btn btn-default navbar-btn" style="color:DarkCyan;" id="edit-save" >Save</a>
+ <a class="btn btn-default navbar-btn" style="color:DarkCyan;" id="edit-cancel" >Cancel</a>
+ </h3>
+ <label style="width:100px;height:24px;">Group name:</label>
+ <input type="text" value="" style="width:25%;" class="form-control" id="new-group-name" placeholder="Name for the group">
+ <input type="text" style="display:none;" id="new-group-id" >
+ <br>
+ <label style="width:100px;height:24px;">User list:</label>
+ <div id="all-users" class="scrolling"></div>
+ <br>
+ <hr>
+ </div>
+
+ <table class="table table-striped table-condensed">
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>User</th>
+ <th>Manage User</th>
+ <th>Manage Group</th>
+ </tr>
+ </thead>
+
+ {% if groups.all %}
+ {% for group in groups.all %}
+ <tr>
+ <td>{{ group.name }} </td>
+ <td></td>
+ <td></td>
+ <td>
+ {% if group.name in builtin_groups %}
+ Built-in
+ {% else %}
+ <span id="group_'+{{group.id}}+'" class="js-group-name"></span>
+ <a id="edit_group">
+ <span class="glyphicon glyphicon-edit js-icon-pencil-config_var edit_group"
+ x-data="{{group.id}}|{{group.name}}|{% for user in group.user_set.all %}{{user.user_fullname}},{% endfor %}">
+ </span></a>
+ &nbsp;&nbsp;
+ <span class="glyphicon glyphicon-trash trash-group" x-data="{{group.id}}|{{group.name}}"></span>
+ {% endif %}
+ </td>
+ </tr>
+ {% for user in group.user_set.all %}
+ <tr>
+ <td></td>
+ <td>{{ user.user_fullname }} </td>
+ <td>
+ {% if group.name in builtin_groups %}
+ (Managed above)
+ {% else %}
+ <span class="glyphicon glyphicon-trash trash-user-from-group" x-data="{{group.id}}|{{group.name}}|{{user.id}}|{{user.user_fullname}}"></span>
+ {% endif %}
+ </td>
+ <td></td>
+ </tr>
+ {% endfor %}
+ {% endfor %}
+ {% else %}
+ <tr>
+ <td>No groups found</td>
+ </tr>
+ {% endif %}
+
+ </table>
+
+</div>
+
<!-- Javascript support -->
<script>
$(document).ready(function() {
@@ -137,8 +230,86 @@
}
});
- });
+ $('.edit_group').click(function() {
+ document.getElementById("new-group-name").value= $(this).attr('x-data').split('|')[1];
+ document.getElementById("new-group-id").value= $(this).attr('x-data').split('|')[0];
+ $("#edit_group_options").slideDown();
+ // build the user list: avoid false substring matches by including comma separators
+ var html = "";
+ var group_user_set = "," + $(this).attr('x-data').split('|')[2] + ",";
+ var users_list = document.getElementsByClassName('js-checkbox-users-list');
+ // Add the checked boxes first
+ for (var i = 0, length = users_list.length; i < length; i++) {
+ var status = '" >';
+ var user_id = users_list[i].value.split("|")[0];
+ var user_name = users_list[i].value.split("|")[1];
+ if (0 <= group_user_set.indexOf(","+user_name+",")) {
+ status = '" checked="checked">';
+ };
+ html += '<div class="checkbox"><label><input type="checkbox" class="checkbox-users" x-data="'+user_id+'" value="'+users_list[i].value+status+user_name+'</label></div>';
+ }
+ document.getElementById("all-users").innerHTML = html;
+ //document.getElementById("edit_group_options").focus();
+ document.getElementById("group-section").scrollIntoView();
+ });
+
+ $('#edit-save').click(function() {
+ $("#edit_group_options").slideUp();
+ var user_id_list = "";
+ $("input[type='checkbox']").each(function(){
+ var user_id = $(this).attr('x-data');
+ var ischecked = $(this).is(":checked");
+ if (ischecked) {
+ user_id_list = user_id_list + user_id + ',';
+ }
+ });
+ postCommitAjaxRequest({
+ "action" : 'submit-group-users',
+ "group_id" : document.getElementById("new-group-id").value,
+ "user_id_list" : user_id_list,
+ });
+ });
+
+ $('#edit-cancel').click(function() {
+ $("#edit_group_options").slideUp();
+ });
+
+ $('#add_group').click(function() {
+ var new_group_name = document.getElementById("add-group-name").value;
+ var result = confirm("Create new group '"+new_group_name+"'?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-group-create',
+ "group_name" : new_group_name,
+ });
+ };
+ });
+
+ $('.trash-group').click(function() {
+ var result = confirm("Are you sure you want to remove group '" + $(this).attr('x-data').split('|')[1] + "'?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-trashgroup',
+ "record_id" : $(this).attr('x-data').split('|')[0],
+ });
+ }
+ });
+ $('.trash-user-from-group').click(function() {
+ var group_id = $(this).attr('x-data').split('|')[0];
+ var group_name = $(this).attr('x-data').split('|')[1];
+ var user_id = $(this).attr('x-data').split('|')[2];
+ var user_name = $(this).attr('x-data').split('|')[3];
+ var result = confirm("Are you sure you want to remove user '" + user_name + "' from group '" + group_name + "'?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-trashusergroup',
+ "group_id" : group_id,
+ "record_id" : user_id,
+ });
+ }
+ });
+ });
</script>
diff --git a/lib/srtgui/templates/vulnerability.html b/lib/srtgui/templates/vulnerability.html
index cd174737..c8fdd995 100644
--- a/lib/srtgui/templates/vulnerability.html
+++ b/lib/srtgui/templates/vulnerability.html
@@ -1,5 +1,5 @@
{% extends "base.html" %}
-{% load projecttags %}
+{% load jobtags %}
{% block extraheadcontent %}
<style>
@@ -45,12 +45,25 @@
<div class="row">
<div class="col-md-12">
<div class="page-header build-data">
- <span class="srt_h1">Vulnerability {{object.get_long_name}} {% if not object.public %} <font color="red">[PRIVATE]</font> {% endif %}</span>
+ <span id="vul-name-container">
+ &nbsp;&nbsp;
+ <span id="vulnerability-name" class="srt_h1">Vulnerability {{object.get_long_name}}
+ {% if request.user.is_contributor %}&nbsp;&nbsp;<span class="glyphicon glyphicon-edit" id="vul-change-form-toggle"></span>{% endif %}
+ {% if not object.public %}&nbsp;&nbsp;<font color="red" >[PRIVATE]</font> {% endif %}
+ </span>
{% if request.user.is_creator %}
<span style="padding-left:30px;"><button id="select-quickedit" class="btn btn-default" type="button">Edit Status...</button></span>
<span style="padding-left:30px;"><button id="select-notification" class="btn btn-default" type="button">Create Notification ...</button></span>
<span style="padding-left:30px;"><button id="select-delete" class="btn btn-default" type="button" x-data="{{object.id}}">Delete</button></span>
{% endif %}
+ </span>
+ <form id="vul-name-change-form" class="form-inline" style="display: none;">
+ <div class="form-group">
+ <input class="form-control input-lg" type="text" id="vul-name-change-input" autocomplete="off" value="{{object.name}}">
+ </div>
+ <button id="vul-name-change-btn" class="btn btn-default btn-lg" type="button">Save</button>
+ <a href="#" id="vul-name-change-cancel" class="btn btn-lg btn-link">Cancel</a>
+ </form>
</div>
</div>
</div>
@@ -73,7 +86,8 @@
<dt>CVE Dictionary Entry:</dt>
<dd>
{% for vc in object.vulnerability_to_cve.all %}
- {% if not forloop.first %}| {% endif %}<a href="{% url 'cve' vc.cve.name %}">{{vc.cve.name}}</a>
+ {% if not forloop.first %}<p>{% endif %}<a href="{% url 'cve' vc.cve.name %}">{{vc.cve.name}}</a>
+ <span class="glyphicon glyphicon-trash detach-cve" id="detach_cve_'+{{vc.cve.id}}+'" x-data="{{vc.cve.id}}"></span>
{% endfor %}
</dd>
@@ -104,6 +118,15 @@
{% if not forloop.first %}| {% endif %}{{vc.cve.cvssV2_baseScore}},{{vc.cve.cvssV2_severity}} </a>
{% endfor %}
</dd>
+
+ {% if request.user.is_creator %}
+ <dt>Attach CVE:</dt>
+ <dd>
+ <input type="text" id="cve_name" name="cve_name" size="20" placeholder="(CVE name)">
+ <button class="execute btn btn-info" id="submit-attach-cve" style="margin-bottom: 5px; margin-top: 0px;">Attach CVE</button>
+ </dd>
+ {% endif %}
+
</dl>
</div>
</div>
@@ -148,21 +171,6 @@
</tr>
</thead>
- <table class="table table-striped table-condensed" data-testid="vuln-hyperlinks-table">
- <thead>
- <tr>
- <th>Product Name</th>
- <th>Investigation</th>
- <th>Status</th>
- <th>Outcome</th>
- <th>Defect</th>
- <th>Release Version</th>
- {% if request.user.is_creator %}
- <th>Manage</th>
- {% endif %}
- </tr>
- </thead>
-
{% if object.investigation_list %}
{% for v2i in object.investigation_list %}
<tr>
@@ -394,7 +402,9 @@
<thead>
<tr>
<th>User</th>
+<!--
<th>Manage</th>
+-->
</tr>
</thead>
@@ -409,11 +419,13 @@
{% if object.vulnerability_users.all %}
{% for u in object.vulnerability_users.all %}
<tr>
- <td>{{ u.user.name }}</td>
+ <td>{{ u.user.username }}</td>
+<!--
<td>
<span id="attachment_entry_'+{{u.id}}+'" class="js-config-var-name"></span>
<span class="glyphicon glyphicon-trash trash-useraccess" id="attachment_trash_'+{{u.id}}+'" x-data="{{u.id}}"></span>
</td>
+-->
</tr>
{% endfor %}
{% else %}
@@ -470,6 +482,13 @@ Created={{object.srt_created}} Updated={{object.srt_updated}}
var selected_quickedit=false;
var selected_notifyedit=false;
+ /* Vulnerability Name change support */
+ var vulNameForm = $("#vul-name-change-form");
+ var vulNameContainer = $("#vul-name-container");
+ var vulName = $(".vul-name");
+ var vulNameFormToggle = $("#vul-change-form-toggle");
+ var vulNameChangeCancel = $("#vul-name-change-cancel");
+
window.onload = function() {
$("input[name=status][value=" + {{ object.status }} + "]").prop('checked', true);
$("input[name=outcome][value=" + {{ object.outcome }} + "]").prop('checked', true);
@@ -488,8 +507,15 @@ Created={{object.srt_created}} Updated={{object.srt_updated}}
alert("error on request:\n" + data.error);
return;
}
- // reload the page with the updated tables
- location.reload(true);
+ // reload the page with the updated tables
+ if (('new_name' in data) && (0 == data.new_name.indexOf("url:"))) {
+ window.location.replace(data.new_name.replace("url:",""));
+ } else if (('new_name' in data) && ("" != data.new_name)) {
+ var new_url = "{% url 'vulnerability' 123 %}".replace("123",data.new_name);
+ window.location.replace(new_url);
+ } else {
+ location.reload(true);
+ }
}
function onCommitAjaxError(jqXHR, textstatus, error) {
@@ -703,7 +729,7 @@ Created={{object.srt_created}} Updated={{object.srt_updated}}
selected_quickedit=true;
$("#display-status").slideUp();
$("#details-quickedit").slideDown();
- document.getElementById("select-quickedit").innerText = "Close edit status...";
+ document.getElementById("select-quickedit").innerText = "Cancel edit status...";
$("#select-quickedit").addClass("blueborder");
document.getElementById("select-status-state").focus();
}
@@ -715,17 +741,36 @@ Created={{object.srt_created}} Updated={{object.srt_updated}}
var tags=$('#text-tags').val().trim();
var priority=$('#select-priority-state').val();
var status=$('#select-status-state').val();
+ var public=$('#select-public-state').val();
var outcome=$('#select-outcome-state').val();
var affected_components=$('#text-affected-components').val();
+ var description=$('#text-description').val();
+ /* Double check any public status changes */
+ {% if object.public %}
+ if ("0" == public) {
+ if (! confirm("Are you sure you want to make this Vulnerability and all its related records as PRIVATE?")) {
+ return
+ }
+ }
+ {% endif %}
+ {% if not object.public %}
+ if ("1" == public) {
+ if (! confirm("Are you sure you want to make this Vulnerability and all its related records as PUBLIC?")) {
+ return
+ }
+ }
+ {% endif %}
postCommitAjaxRequest({
"action" : 'submit-quickedit',
"note" : note,
"private_note" : private_note,
"tags" : tags,
"status" : status,
+ "public" : public,
"outcome" : outcome,
"priority" : priority,
"affected_components" : affected_components,
+ "description" : description,
});
});
@@ -734,13 +779,13 @@ Created={{object.srt_created}} Updated={{object.srt_updated}}
selected_notifyedit=false;
$("#details-notify-edit").slideUp();
$("#display-status").slideDown();
- document.getElementById("select-notification").innerText = "Create Notification ...";
+ document.getElementById("select-notification").innerText = "Create notification ...";
$("#select-notification").removeClass("blueborder");
} else {
selected_notifyedit=true;
$("#display-status").slideUp();
$("#details-notify-edit").slideDown();
- document.getElementById("select-notification").innerText = "Close notification";
+ document.getElementById("select-notification").innerText = "Cancel notification";
$("#select-notification").addClass("blueborder");
document.getElementById("select-category-notify").focus();
}
@@ -791,6 +836,47 @@ Created={{object.srt_created}} Updated={{object.srt_updated}}
}
});
+ /* Vulnerability name change functionality */
+ vulNameFormToggle.click(function(e){
+ e.preventDefault();
+ vulNameContainer.hide();
+ vulNameForm.fadeIn();
+ });
+ vulNameChangeCancel.click(function(e){
+ e.preventDefault();
+ vulNameForm.hide();
+ vulNameContainer.fadeIn();
+ });
+ $("#vul-name-change-btn").click(function(){
+ var newvulName = $("#vul-name-change-input").val();
+ postCommitAjaxRequest({
+ "action" : 'submit-newname',
+ "old_name" : '{{object.name}}',
+ "new_name" : newvulName,
+ });
+ });
+
+ $("#submit-attach-cve").click(function(){
+ var cve_name=$("#cve_name").val();
+ if ("" == cve_name) {
+ alert("No CVE name was entered");
+ return;
+ }
+ postCommitAjaxRequest({
+ "action" : 'submit-attach-cve',
+ "cve_name" : cve_name,
+ });
+ });
+ $('.detach-cve').click(function() {
+ var result = confirm("Are you sure you want to detach this CVE?");
+ if (result){
+ postCommitAjaxRequest({
+ "action" : 'submit-detach-cve',
+ "record_id" : $(this).attr('x-data'),
+ });
+ }
+ });
+
/* Set the report link */
$('#report_link').attr('href',"{% url 'report' request.resolver_match.url_name %}?record_list={{object.id}}");
});
diff --git a/lib/srtgui/templatetags/projecttags.py b/lib/srtgui/templatetags/jobtags.py
index 0c5efc29..4a987d99 100644..100755
--- a/lib/srtgui/templatetags/projecttags.py
+++ b/lib/srtgui/templatetags/jobtags.py
@@ -23,6 +23,7 @@ import os
from os.path import relpath
import re
import json as JsonLib
+from datetime import datetime, timedelta
from django import template
from django.template.defaultfilters import filesizeformat
@@ -342,3 +343,13 @@ def has_group(user, group_name):
group = Group.objects.get(name=group_name)
return group in user.groups.all()
+@register.filter(name='shift_timezone')
+def shift_timezone(datetime_str, hours_offset):
+ # do some calculation (offset + time passed)
+ try:
+ dt = datetime.strptime(datetime_str,'%Y-%m-%d %H:%M:%S')
+ dt += timedelta(hours=int(hours_offset))
+ return dt.strftime('%Y-%m-%d %H:%M:%S')
+ except:
+ return("TIME_ERROR:%s" % datetime_str)
+
diff --git a/lib/srtgui/templatetags/multi_tags.py b/lib/srtgui/templatetags/multi_tags.py
new file mode 100755
index 00000000..6a436825
--- /dev/null
+++ b/lib/srtgui/templatetags/multi_tags.py
@@ -0,0 +1,22 @@
+import os
+
+from django import template
+from django.utils.safestring import mark_safe
+
+ml_register = template.Library()
+
+@ml_register.filter(name = 'multitag')
+def multitag(tags):
+ """
+ Convert a comma-delimited list into HTML separate lines.
+ """
+ return mark_safe(tags.replace(',','<p>'))
+
+@ml_register.filter(name = 'get_dict_value')
+def get_dict_value(dictionary, key):
+ """ return the value of a dictionary key
+ """
+ try:
+ return dictionary[key]
+ except (KeyError, IndexError):
+ return ''
diff --git a/lib/srtgui/templatetags/project_url_tag.py b/lib/srtgui/templatetags/project_url_tag.py
deleted file mode 100644
index 51ccc560..00000000
--- a/lib/srtgui/templatetags/project_url_tag.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from django import template
-from django.urls import reverse
-
-register = template.Library()
-
-def project_url(parser, token):
- """
- Create a URL for a project's main page;
- for non-default projects, this is the configuration page;
- for the default project, this is the project builds page
- """
- try:
- tag_name, project = token.split_contents()
- except ValueError:
- raise template.TemplateSyntaxError(
- "%s tag requires exactly one argument" % tag_name
- )
- return ProjectUrlNode(project)
-
-class ProjectUrlNode(template.Node):
- def __init__(self, project):
- self.project = template.Variable(project)
-
- def render(self, context):
- try:
- project = self.project.resolve(context)
- if project.is_default:
- return reverse('projectbuilds', args=(project.id,))
- else:
- return reverse('project', args=(project.id,))
- except template.VariableDoesNotExist:
- return ''
-
-register.tag('project_url', project_url)
diff --git a/lib/srtgui/typeaheads.py b/lib/srtgui/typeaheads.py
index e32c16ad..800e9b0e 100644
--- a/lib/srtgui/typeaheads.py
+++ b/lib/srtgui/typeaheads.py
@@ -19,6 +19,8 @@
import subprocess
from srtgui.widgets import ToasterTypeAhead
+from orm.models import RecipeTable
+
from django.urls import reverse
from django.core.cache import cache
@@ -184,3 +186,27 @@ class GitRevisionTypeAhead(ToasterTypeAhead):
'detail': '[ %s ]' % str(rev)})
return results
+
+
+class RecipeTypeAhead(ToasterTypeAhead):
+ """ Typeahead for all the recipes """
+ def apply_search(self, search_term, cve, request):
+
+ recipes = RecipeTable.objects.all().order_by("recipe_name")
+
+ primary_results = recipes.filter(recipe_name__icontains=search_term)
+
+ results = []
+
+ for recipe in list(primary_results):
+
+ detail = ''
+ needed_fields = {
+ 'id': recipe.pk,
+ 'name': recipe.recipe_name,
+ 'detail': detail,
+ }
+
+ results.append(needed_fields)
+
+ return results
diff --git a/lib/srtgui/urls.py b/lib/srtgui/urls.py
index ef91f16b..45d15fde 100644
--- a/lib/srtgui/urls.py
+++ b/lib/srtgui/urls.py
@@ -16,11 +16,13 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-from django.conf.urls import url
+from django.urls import re_path as url
from django.views.generic import RedirectView
from srtgui import tables
from srtgui import views
+from srtgui import typeaheads
+from srtgui import widgets
urlpatterns = [
# landing page
@@ -34,12 +36,15 @@ urlpatterns = [
url(r'^cves/$',
tables.CvesTable.as_view(template_name="cves-toastertable.html"),
name='cves'),
+
+ # "cve_status" is passed by URL: redirect("/srtgui/select-cves/?cve_status=%d" % cve_select_status)
url(r'^select-cves/$',
tables.SelectCveTable.as_view(template_name="cves-select-toastertable.html"),
name='select-cves'),
url(r'^select-cves/(?P<cve_status>\d+)$',
tables.SelectCveTable.as_view(template_name="cves-select-toastertable.html"),
name='select-cves'),
+
url(r'^cve-create/$', views.cve_create, name="cve_create"),
url(r'^cve-alternates/(?P<cve_pk>\d+)$', views.cve_alternates, name="cve_alternates"),
@@ -107,6 +112,7 @@ urlpatterns = [
url(r'^report/(?P<page_name>\D+)$', views.report, name='report'),
+ # XHR URLs
url(r'^xhr_triage_commit/$', views.xhr_triage_commit,
name='xhr_triage_commit'),
@@ -135,6 +141,8 @@ urlpatterns = [
url(r'^xhr_publish/$', views.xhr_publish,
name='xhr_publish'),
+ # Management URLs
+
url(r'^manage/$', views.management, name='manage'),
url(r'^manage_cpes/$',
tables.ManageCpeTable.as_view(template_name="manage-cpes-toastertable.html"),
@@ -172,10 +180,43 @@ urlpatterns = [
tables.HistoryDefectTable.as_view(template_name="history-defect-toastertable.html"),
name='history_defect'),
+ # typeahead api end points
+ url(r'^xhr_recipetypeahead/recipes$',
+ typeaheads.RecipeTypeAhead.as_view(), name='xhr_recipetypeahead'),
+
+ #
+ # Job progress URLs
+ #
+
+ url(r'^joblog/(?P<job_pk>\d+)$', views.joblog, name='joblog'),
+ url(r'^mostrecentjobs$', widgets.MostRecentJobsView.as_view(),
+ name='most_recent_jobs'),
+ url(r'^xhr_maintenance_commit/$', views.xhr_maintenance_commit,
+ name='xhr_maintenance_commit'),
+ url(r'^xhr_jobrequest/$',
+ widgets.XhrJobRequest.as_view(),
+ name='xhr_jobrequest'),
+ url(r'^xhr_job_post/$', views.xhr_job_post,
+ name='xhr_job_post'),
+ url(r'^xhr_sources_commit/$', views.xhr_sources_commit,
+ name='xhr_sources_commit'),
+
+ url(r'^manage_jobs/(?P<foo_id>\d+)$',
+ tables.ManageJobsTable.as_view(template_name="manage-jobs-toastertable.html"),
+ name='manage_jobs'),
+
+ #
+ # Extra
+ #
+
+ url(r'^email_admin/$', views.email_admin, name='email_admin'),
+ url(r'^email_success/$', views.email_success, name='email_success'),
+
url(r'^guided_tour/$', views.guided_tour, name='guided_tour'),
url(r'^quicklink/$', views.quicklink, name='quicklink'),
+ url(r'^date_time_test/$', views.date_time_test, name='date_time_test'),
url(r'^tbd/$', views.tbd, name='tbd'),
# default redirection
diff --git a/lib/srtgui/views.py b/lib/srtgui/views.py
index d3601181..2cfe0e19 100644
--- a/lib/srtgui/views.py
+++ b/lib/srtgui/views.py
@@ -4,7 +4,7 @@
#
# Security Response Tool Implementation
#
-# Copyright (C) 2017-2018 Wind River Systems
+# Copyright (C) 2017-2023 Wind River Systems
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
@@ -23,15 +23,24 @@ import os
import traceback
import subprocess
from datetime import timedelta, datetime
+from datetime import timezone as datetime_timezone
from decimal import Decimal
import mimetypes
import json
import re
+import time
+import pytz
from django.db.models import Q
from django.shortcuts import render, redirect
from django.db.models.functions import Lower
-from orm.models import Cve, CveLocal, CveSource, CveHistory
+from django.contrib.auth.models import Group
+from django.urls import reverse, resolve
+from django.core.paginator import EmptyPage, PageNotAnInteger
+from django.http import HttpResponse
+from django.utils import timezone
+
+from orm.models import Cve, CveLocal, CveSource, CveHistory, CveAccess
from orm.models import Vulnerability, VulnerabilityHistory, CveToVulnerablility, VulnerabilityToInvestigation, VulnerabilityNotification, VulnerabilityAccess, VulnerabilityComments, VulnerabilityUploads
from orm.models import Investigation, InvestigationHistory, InvestigationToDefect, InvestigationComments, InvestigationNotification, InvestigationAccess, InvestigationUploads
from orm.models import SrtSetting, Product
@@ -41,21 +50,16 @@ from orm.models import Defect, DefectHistory, PublishPending, PublishSet
from orm.models import Notify, NotifyAccess, NotifyCategories
from orm.models import SRTool, Update
from orm.models import ErrorLog
-
+from orm.models import Job
from users.models import SrtUser, UserSafe
-
from srtgui.reports import ReportManager
from srtgui.api import readCveDetails, writeCveDetails, summaryCveDetails, execute_process
from srtgui.api import publishCalculate, publishReset, publishMarkNew, publishMarkModified, publishMarkNone
-from django.urls import reverse, resolve
-from django.core.paginator import EmptyPage, PageNotAnInteger
-from django.http import HttpResponse
-from django.utils import timezone
-
import logging
-SRT_BASE_DIR = os.environ['SRT_BASE_DIR']
+SRT_BASE_DIR = os.environ.get('SRT_BASE_DIR', '.')
+SRT_MAIN_APP = os.environ.get('SRT_MAIN_APP', '.')
logger = logging.getLogger("srt")
@@ -105,17 +109,28 @@ def managedcontextprocessor(request):
ret['srt_logo'] = SrtSetting.objects.get(name='SRTOOL_LOGO').value.split(',')
# Add optional local logo link
ret['srt_local_logo'] = SrtSetting.objects.get(name='SRTOOL_LOCAL_LOGO').value.split(',')
+ # Add optional SRTool mode
+ ret['srt_mode'] = ' (%s)' % os.environ['SRT_MODE'] if (('SRT_MODE' in os.environ) and os.environ['SRT_MODE']) else ''
return ret
# determine in which mode we are running in, and redirect appropriately
def landing(request):
- # we only redirect to projects page if there is a user-generated project
-# num_builds = Build.objects.all().count()
-# user_projects = Project.objects.filter(is_default = False)
-# has_user_project = user_projects.count() > 0
+ # Django sometimes has a race condition with this view executing
+ # for the master app's landing page HTML which can lead to context
+ # errors, so hard enforce the default re-direction
+ if SRT_MAIN_APP and (SRT_MAIN_APP != "srtgui"):
+ return redirect(f"/{SRT_MAIN_APP}/landing/")
- context = {}
+ # Append the list of landing page extensions
+ landing_extensions_table = []
+ for landing_extension in SrtSetting.objects.filter(name__startswith='LANDING_LINK').order_by('name'):
+ landing_extensions_table.append(landing_extension.value.split('|'))
+
+ context = {
+ 'landing_extensions_table' : landing_extensions_table,
+ 'this_landing' : 'srtgui',
+ }
return render(request, 'landing.html', context)
@@ -469,6 +484,7 @@ def management(request):
defects_inprogress = defects_inprogress.count()
context = {
+ 'mru' : Job.get_recent(),
'cve_total' : Cve.objects.all().count(),
'cve_new' : Cve.objects.filter(status=Cve.NEW).count(),
# 'cve_open' : Cve.objects.filter( Q(status=Cve.INVESTIGATE) & Q(status=Cve.VULNERABLE) ).count(),
@@ -503,19 +519,30 @@ def management(request):
return render(request, 'management.html', context)
def maintenance(request):
+ _log("MAINTENANCE: %s" % request)
# does this user have permission to see this record?
if not UserSafe.is_creator(request.user):
return redirect(landing)
- context = {
- 'errorlog_total' : ErrorLog.objects.all().count(),
- 'history_cve_total' : CveHistory.objects.all().count(),
- 'history_vulnerability_total' : VulnerabilityHistory.objects.all().count(),
- 'history_investigation_total' : InvestigationHistory.objects.all().count(),
- 'defect_investigation_total' : DefectHistory.objects.all().count(),
- }
- return render(request, 'maintenance.html', context)
+ if request.method == "GET":
+ context = {
+ 'errorlog_total' : ErrorLog.objects.all().count(),
+ 'history_cve_total' : CveHistory.objects.all().count(),
+ 'history_vulnerability_total' : VulnerabilityHistory.objects.all().count(),
+ 'history_investigation_total' : InvestigationHistory.objects.all().count(),
+ 'defect_investigation_total' : DefectHistory.objects.all().count(),
+ 'mru' : Job.get_recent(),
+ 'remote_backup_path' : SrtSetting.get_setting('SRT_REMOTE_BACKUP_PATH',''),
+ }
+ return render(request, 'maintenance.html', context)
+ elif request.method == "POST":
+ _log("EXPORT_POST:MAINTENANCE: %s" % request)
+ if request.POST["action"] == "submit-remote-backup-path":
+ SrtSetting.set_setting('SRT_REMOTE_BACKUP_PATH',request.POST["text-remote-backup-path"].strip()),
+ return redirect(maintenance)
+ else:
+ return render(request, "unavailable_artifact.html", context={})
def cve(request, cve_pk, active_tab="1"):
if request.method == "GET":
@@ -531,10 +558,17 @@ def cve(request, cve_pk, active_tab="1"):
_log("CVE_ERROR(%s)(%s):" % (cve_pk,e))
return redirect(landing)
- # does this user have permission to see this record?
+ # Does this user have permission to see this record?
if (not cve_object.public) and (not UserSafe.is_admin(request.user)):
- _log("CVE_ERROR_PERMISSIONS:(%s)" % request.user)
- return redirect(landing)
+ try:
+ cveaccess = CveAccess.objects.get(cve=cve_object,user=request.user)
+ except:
+ cveaccess = None
+ if not cveaccess:
+ _log("CVE_ERROR_PERMISSIONS:(%s)" % request.user)
+ return redirect(landing)
+ else:
+ _log("CVE_PASS_PERMISSIONS:(%s)" % request.user)
# Set up the investigation link
investigation_records = Investigation.objects.filter(name=cve_object.name)
@@ -555,7 +589,7 @@ def cve(request, cve_pk, active_tab="1"):
# Always pre-pend a summary page
tab_states[chr(cve_index)] = 'active'
cveDetails,cve_html = summaryCveDetails(cve_object,cve_sources)
- cve_list_table.append([cveDetails,tab_states[chr(cve_index)],'Summary',cve_html])
+ cve_list_table.append([cveDetails,tab_states[chr(cve_index)],'Summary',cve_html,''])
cve_index += 1
# Add the source/edit tabs
@@ -569,18 +603,26 @@ def cve(request, cve_pk, active_tab="1"):
if active_tab == cs.datasource.name:
active_tab = chr(cve_index)
if ('Edit' == active_tab) and ('Local' == cs.datasource.name):
- #tab_states[chr(cve_index)] = 'active'
- tab_states[chr(cve_index)] = ''
- cve_list_table.append([readCveDetails(cve_object,cs.datasource),tab_states[chr(cve_index)],'Edit',{}])
+ if False:
+ tab_states[chr(cve_index)] = ''
+ else:
+ # Force the 'Edit' tab to start active
+ tab_states[chr(cve_index)] = 'active'
+ # Force the 'Summary' tab to start inactive
+ tab_states[chr(ord('1'))] = ''
+ cve_list_table[0][1] = ''
+ cve_list_table.append([readCveDetails(cve_object,cs.datasource),tab_states[chr(cve_index)],'Edit',{},''])
else:
tab_states[chr(cve_index)] = ''
#tab_states[chr(cve_index)] = 'active' if (active_tab == chr(cve_index)) else ''
- cve_list_table.append([readCveDetails(cve_object,cs.datasource),tab_states[chr(cve_index)],cs.datasource.name,{}])
+ tab_name = cs.datasource.name
+ cve_list_table.append([readCveDetails(cve_object,cs.datasource),tab_states[chr(cve_index)],tab_name,{},cs.datasource.id])
cve_index += 1
if 0 == len(cve_sources):
_log("CVE_0_Sources??:(%s,%s)" % (cve_pk, active_tab))
- tab_states['1'] = 'active'
- cve_list_table.append([readCveDetails(cve_object,None),tab_states['1'],'(No Source)',{}])
+ tab_states['1'] = ''
+ details = readCveDetails(cve_object,None)
+ cve_list_table.append([readCveDetails(cve_object,None),tab_states['1'],'No_Source',{},''])
# Check to make sure active_tab was applied
for tab in tab_states.keys():
@@ -618,10 +660,18 @@ def cve(request, cve_pk, active_tab="1"):
if not request.POST.get('cve-edit','').startswith('Save'):
return redirect(cve, cve_object.id, "Summary")
+
# does this user have permission to see this record?
if (not cve_object.public) and (not UserSafe.is_admin(request.user)):
- _log("CVE_ERROR_PERMISSIONS:(%s)" % request.user)
- return redirect(landing)
+ try:
+ cveaccess = CveAccess.objects.get(cve=cve_object,user=request.user)
+ except:
+ cveaccess = None
+ if not cveaccess:
+ _log("CVE_ERROR_PERMISSIONS:(%s)" % request.user)
+ return redirect(landing)
+ else:
+ _log("CVE_PASS_PERMISSIONS:(%s)" % request.user)
# update the local CVE record
writeCveDetails(cve_object.name,request)
@@ -648,15 +698,23 @@ def cve_edit(request, cve_pk):
cve_source_object,created = CveSource.objects.get_or_create(cve=cve_object,datasource=source)
return cve(request, cve_object.name, active_tab="Edit")
-def cve_create(request):
+def _create_local_cve():
# Create the local CVE edit record
new_cve_name = CveLocal.new_cve_name()
cve_object = Cve.objects.create(name=new_cve_name,name_sort=get_name_sort(new_cve_name))
+ cve_object.save()
cve_local_object = CveLocal.objects.create(name=new_cve_name)
+ cve_local_object.save()
# Add the source mapping
source = DataSource.objects.get(name='Local')
cve_source_object = CveSource.objects.create(cve=cve_object,datasource=source)
- # Open the new CVE
+ cve_source_object.save()
+ return cve_object,cve_local_object
+
+def cve_create(request):
+ # Create the local CVE edit record
+ cve_object,cve_local_object = _create_local_cve()
+ # Open the new CVE page
return redirect(cve, cve_object.id, "Local")
@@ -678,7 +736,15 @@ def vulnerability(request, vulnerability_pk):
# does this user have permission to see this record?
if (not vulnerability_object.public) and (not UserSafe.is_admin(request.user)):
- return redirect(landing)
+ try:
+ vul_access = VulnerabilityAccess.objects.get(vulnerability=vulnerability_object,user=request.user)
+ except:
+ vul_access = None
+ if not vul_access:
+ _log("VUL_ERROR_PERMISSIONS:(%s)" % request.user)
+ return redirect(landing)
+ else:
+ _log("VUL_PASS_PERMISSIONS:(%s)" % request.user)
context = {
'object' : vulnerability_object,
@@ -757,6 +823,19 @@ def investigation(request, investigation_pk):
except:
return redirect(landing)
+ # does this user have permission to see this record?
+ if (not investigation_object.public) and (not UserSafe.is_admin(request.user)):
+ try:
+ inv_access = InvestigationAccess.objects.get(investigation=investigation_object,user=request.user)
+ except:
+ inv_access = None
+ if not inv_access:
+ _log("INV_ERROR_PERMISSIONS:(%s)" % request.user)
+ return redirect(landing)
+ else:
+ _log("INV_PASS_PERMISSIONS:(%s)" % request.user)
+
+
### TO-DO: replace with dynamic lookahead instead of static huge list
defects = Defect.objects.all()
@@ -881,7 +960,8 @@ def sources(request):
object = DataSource.objects.all()
context = {
- 'object' : object,
+ 'object' : object,
+ 'mru' : Job.get_recent(),
}
return render(request, template, context)
@@ -902,7 +982,7 @@ def login(request):
try:
### USER CONTROL
- user = SrtUser.objects.get(name=user_name)
+ user = SrtUser.objects.get(username=user_name)
request.session['srt_user_id'] = user.id
request.session.modified = True
_log("LOGIN_POST_SET:%s,%s" % (user.name,user.id))
@@ -929,6 +1009,8 @@ def users(request):
context = {
'object' : object,
+ 'groups' : Group.objects.all().order_by(Lower('name')),
+ 'builtin_groups' : ('Reader','Contributor','Creator','Admin'),
}
return render(request, template, context)
@@ -1022,7 +1104,6 @@ def publish_diff_snapshot(request):
if not UserSafe.is_creator(request.user):
return redirect(landing)
- main_app = SrtSetting.get_setting('SRT_MAIN_APP','yp')
if request.method == "GET":
# Prepare available snapshots
@@ -1095,6 +1176,7 @@ def publish_diff_snapshot(request):
'snapshot_frequency_list' : snapshot_frequency_list,
'snap_frequency_select' : snap_frequency_select,
+ 'mru' : Job.get_recent(),
}
return render(request, 'publish_diff_snapshot.html', context)
elif request.method == "POST":
@@ -1149,7 +1231,7 @@ def publish_diff_snapshot(request):
publishCalculate(date_start,date_stop)
return redirect('publish')
if 'export' == action:
- return redirect('/%s/report/publish' % main_app)
+ return redirect('/%s/report/publish' % SRT_MAIN_APP)
return redirect('publish')
def publish_diff_history(request):
@@ -1157,7 +1239,6 @@ def publish_diff_history(request):
if not UserSafe.is_creator(request.user):
return redirect(landing)
- main_app = SrtSetting.get_setting('SRT_MAIN_APP','yp')
if request.method == "GET":
# Prepare available snapshots
@@ -1284,7 +1365,7 @@ def publish_diff_history(request):
publishCalculate(date_start,date_stop)
return redirect('publish')
if 'export' == action:
- return redirect('/%s/report/publish' % main_app)
+ return redirect('/%s/report/publish' % SRT_MAIN_APP)
return redirect('publish')
@@ -1351,6 +1432,10 @@ def _create_defect(investigation,reason,defect_reason,domain_components,affected
# Assign the defect the same priority as the Investigation
priority = investigation.get_priority_text
+ # Protect Jira from undefine priorities
+ if priority == SRTool.priority_text(SRTool.UNDEFINED):
+ _log("WARNING:_create_defect:FIX_PRIORITY:'%s' to '%s" % (priority,SRTool.priority_text(SRTool.LOW)))
+ priority = SRTool.priority_text(SRTool.LOW)
_log("_create_defect:%s:%s:%s" % (investigation.name,priority,links))
# Offer a default defect summary
@@ -1755,6 +1840,7 @@ def _submit_notification(request):
NotifyAccess.objects.get_or_create(notify=notify, user=user)
_log("xhr_notifications5")
+
def xhr_cve_commit(request):
_log("xhr_cve_commit(%s)" % request.POST)
if not 'action' in request.POST:
@@ -1764,9 +1850,13 @@ def xhr_cve_commit(request):
action = request.POST['action']
history_update = []
new_name = ''
+ error_text = "ok"
+ username = UserSafe.user_name(request.user)
+
if 'submit-quickedit' == action:
priority = int(request.POST['priority'])
status = int(request.POST['status'])
+ public = (1 == int(request.POST['public']))
note = request.POST['note'].strip()
private_note = request.POST['private_note'].strip()
tags = request.POST['tags'].strip()
@@ -1817,13 +1907,33 @@ def xhr_cve_commit(request):
history_update.append(Update.ACKNOWLEDGE_DATE % (SRTool.date_ymd_text(cve.acknowledge_date),SRTool.date_ymd_text(acknowledge_date)))
cve.acknowledge_date = acknowledge_date
- cve.save()
- if 'submit-notification' == action:
+ # Process implications of 'public' change
+ if (cve.public != public):
+ history_update.append(Update.PUBLIC % (cve.public,public))
+ cve.public = public
+ # Insure newly private record has at least this user
+ if not public:
+ cve_access,created = CveAccess.objects.get_or_create(cve=cve, user=request.user)
+ if created:
+ cve_access.save()
+ # If we are about to propagate, save current state first and once
+ cve.save()
+ cve.propagate_private()
+ else:
+ # No propagation, normal save
+ cve.save()
+ elif 'submit-notification' == action:
# Note: no history update
_submit_notification(request)
- if 'submit-newname' == action:
+ elif 'submit-newname' == action:
old_name = request.POST['old_name']
- new_name = request.POST['new_name']
+ new_name_input = request.POST['new_name'].strip()
+ new_name = ''
+ for i in range(len(new_name_input)):
+ if not new_name_input[i] in ('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-0123456789_ '):
+ new_name += '_'
+ else:
+ new_name += new_name_input[i]
try:
# Is name already used?
Cve.objects.get(name=new_name)
@@ -1839,7 +1949,7 @@ def xhr_cve_commit(request):
cveLocal = CveLocal.objects.get(name=old_name)
cveLocal.name = new_name
cveLocal.save()
- if 'submit-create-vulnerability' == action:
+ elif 'submit-create-vulnerability' == action:
_log("SUBMIT-CREATE-VULNERABILITY")
vname = Vulnerability.new_vulnerability_name()
vulnerability = Vulnerability.objects.create(
@@ -1849,13 +1959,18 @@ def xhr_cve_commit(request):
priority = cve.priority,
comments = cve.comments,
packages = cve.packages,
+ public = cve.public,
)
vulnerability.save()
+ # If private, add users
+ if not cve.public:
+ for cve_private_access in CveAccess.objects.filter(cve=cve,user=request.user):
+ VulnerabilityAccess.objects.create(vulnerability=vulnerability,user=cve_private_access.user)
history_update.append(Update.ATTACH_INV % (vname))
cve2vul = CveToVulnerablility.objects.create(cve = cve,vulnerability = vulnerability)
cve2vul.save()
_log("SUBMIT-CREATE-VULNERABILITY:%s,%s,%s" % (cve.id,vulnerability.id,cve2vul.id))
- if 'submit-attach-vulnerability' == action:
+ elif 'submit-attach-vulnerability' == action:
_log("SUBMIT-CREATE-VULNERABILITY")
vname = request.POST['vul_name'].strip()
try:
@@ -1866,20 +1981,97 @@ def xhr_cve_commit(request):
history_update.append(Update.ATTACH_INV % (vname))
cve2vul = CveToVulnerablility.objects.create(cve = cve,vulnerability = vulnerability)
cve2vul.save()
+ update_comment = "%s%s" % (Update.UPDATE_STR % Update.SOURCE_USER,Update.ATTACH_CVE % cve.name)
+ vul_hist = VulnerabilityHistory.objects.create(vulnerability_id=vulnerability.id, comment=update_comment, date=datetime.now().strftime('%Y-%m-%d'), author=username)
+ vul_hist.save()
_log("SUBMIT-CREATE-VULNERABILITY:%s,%s,%s" % (cve.id,vulnerability.id,cve2vul.id))
- if 'submit-delete-cve' == action:
+ elif 'submit-detach-vulnerability' == action:
+ record_id = request.POST['record_id']
+ vulnerability = Vulnerability.objects.get(id=record_id)
+ c2v = CveToVulnerablility.objects.get(vulnerability=vulnerability,cve=cve)
+ c2v.delete()
+ update_comment = "%s%s" % (Update.UPDATE_STR % Update.SOURCE_USER,Update.DETACH_CVE % cve.name)
+ vul_hist = VulnerabilityHistory.objects.create(vulnerability_id=vulnerability.id, comment=update_comment, date=datetime.now().strftime('%Y-%m-%d'), author=username)
+ vul_hist.save()
+ history_update.append(Update.DETACH_VUL % vulnerability.name)
+ elif 'submit-delete-cve' == action:
_log("SUBMIT-DELETE-CVE(%s)" % cve.name)
#history_update.append(Update.ATTACH_INV % (vname))
+
+ # Try remove the datasource map first
+ try:
+ cvesource = CveSource.objects.get(cve=cve)
+ if cvesource:
+ cvesource.delete()
+ except:
+ # NO CveSource record
+ pass
+
+ # First delete the Cve record (and its related records automatically)
+ cve_name = cve.name
cve.delete()
_log("SUBMIT-DELETED-CVE(%s)!" % cve.name)
+ # Now remove any related cvelocal records
+ # CveLocal records are keyed by name, since they are created dynamically form a local edit
+ try:
+ cvelocal = CveLocal.objects.get(name=cve_name)
+ if cvelocal:
+ cvelocal.delete()
+ except:
+ # NO CveLocal record
+ pass
new_name = 'url:/srtgui/cves'
+ elif 'submit-adduseraccess' == action:
+ users = request.POST['users']
+ usernames = []
+ for user_id in users.split(','):
+ usernames.append(SrtUser.objects.get(pk=user_id).username)
+ CveAccess.objects.get_or_create(cve=cve, user_id=user_id)
+ history_update.append(Update.ATTACH_ACCESS % ','.join(usernames))
+ cve.propagate_private()
+ elif 'submit-trashuseraccess' == action:
+ record_id = request.POST['record_id']
+ access_record = CveAccess.objects.get(id=record_id)
+ history_update.append(Update.DETACH_ACCESS % access_record.user.username)
+ access_record.delete()
+ cve.propagate_private()
+
+ elif 'submit-merge-cve' == action:
+ cve_merge_name = request.POST['cve_merge_name']
+ try:
+ cve_merge = Cve.objects.get(name=cve_merge_name)
+ # We found it, but does the user have access to it?
+ # TODO
+
+ # Merge/create the cvelocal data
+ pass
+
+ # Save the results
+ pass
+
+ # Delete the local CVE in favor if the merged CVE?
+ pass
+
+ # Jump to the new CVE
+ new_name = cve_merge_name
+ history_update.append(Update.MERGE_CVE % cve.name)
+ cve = cve_merge
+
+ except Exception as e:
+ error_text = "ERROR: unknown CVE name '%s'" % cve_merge_name
+ _log("ERROR:CVE_MERGE_NAME:%s" % e)
+
+ else:
+ error_text = "ERROR: unknown action '%s'" % action
+
+ _log("XHR_CVE_COMMIT:new_name=%s" % new_name)
+
return_data = {
- "error": "ok",
+ "error": error_text,
"new_name" : new_name,
}
- username = UserSafe.user_name(request.user)
if history_update:
update_comment = "%s%s" % (Update.UPDATE_STR % Update.SOURCE_USER,';'.join(history_update))
CveHistory.objects.create(cve_id=cve.id, comment=update_comment, date=datetime.now().strftime('%Y-%m-%d'), author=username)
@@ -1937,6 +2129,7 @@ def xhr_vulnerability_commit(request):
action = request.POST['action']
v_id = request.POST['vulnerability_id']
username = UserSafe.user_name(request.user)
+ new_name = ''
try:
history_update = []
if 'submit-quickedit' == action:
@@ -1945,8 +2138,10 @@ def xhr_vulnerability_commit(request):
tags = request.POST['tags'].strip()
priority = int(request.POST['priority'])
status = int(request.POST['status'])
+ public = (1 == int(request.POST['public']))
outcome = int(request.POST['outcome'])
affected_components = request.POST['affected_components'].strip()
+ description = request.POST['description'].strip()
v = Vulnerability.objects.get(id=v_id)
if (v.priority != priority):
history_update.append(Update.PRIORITY % (SRTool.priority_text(v.priority),SRTool.priority_text(priority)))
@@ -1969,8 +2164,68 @@ def xhr_vulnerability_commit(request):
if (affected_components != v.packages):
history_update.append(Update.AFFECTED_COMPONENT % (v.packages,affected_components))
v.packages = affected_components
- v.save()
- if 'submit-addproduct' == action:
+ if (description != v.description):
+ history_update.append(Update.DESCRIPTION)
+ v.description = description
+
+ # Process implications of 'public' change
+ _log("V2C:PRIVATE0:%s to %s" % (v.public,public))
+ if (public != v.public):
+ history_update.append(Update.PUBLIC % (v.public,public))
+ v.public = public
+ # Insure newly private record has at least this user
+ if not public:
+ vul_access,created = VulnerabilityAccess.objects.get_or_create(vulnerability=v, user=request.user)
+ if created:
+ vul_access.save()
+ # Since we are about to propagate, save current state first and once
+ v.save()
+ _log("V2C:PRIVATE1:%s" % v.public)
+ # Propagate the 'public' change via the parent CVEs (if any)
+ for c2v in CveToVulnerablility.objects.filter(vulnerability=v):
+ _log("V2C:PRIVATE2:%s" % c2v.cve.name)
+ # If now private, insure parent CVE has this user
+ if not public:
+ cve_access,created = CveAccess.objects.get_or_create(cve=c2v.cve, user=request.user)
+ if created:
+ cve_access.save()
+ c2v.cve.public = public
+ c2v.cve.save()
+ c2v.cve.propagate_private()
+ else:
+ # No propagation, normal save
+ v.save()
+ elif 'submit-newname' == action:
+ v = Vulnerability.objects.get(id=v_id)
+ old_name = request.POST['old_name']
+ new_name_input = request.POST['new_name'].strip()
+ new_name = ''
+ for i in range(len(new_name_input)):
+ if not new_name_input[i] in ('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-0123456789_'):
+ new_name += '_'
+ else:
+ new_name += new_name_input[i]
+ try:
+ # Is name already used?
+ is_existing_vul = Vulnerability.objects.get(name=new_name)
+ return HttpResponse(json.dumps({"error":"name '%s' is already used\n" % new_name}), content_type = "application/json")
+ except:
+ _log("NewName3:%s -> %s" % (old_name,new_name))
+ # Apply this unique name to CVE
+ v.name = new_name
+ v.save()
+ # Move any attached documents
+ path_old = os.path.join(SRT_BASE_DIR, "downloads/%s" % old_name)
+ path_new = os.path.join(SRT_BASE_DIR, "downloads/%s" % new_name)
+ doc_found = False
+ for doc in VulnerabilityUploads.objects.filter(vulnerability=v):
+ doc_found = True
+ doc.path = doc.path.replace(path_old,path_new)
+ doc.save()
+ if doc_found:
+ os.rename(path_old, path_new)
+ history_update.append(Update.NEW_NAME % (old_name,new_name))
+ elif 'submit-addproduct' == action:
products = request.POST['products']
investigation_names = []
vulnerability_obj = Vulnerability.objects.get(id=v_id)
@@ -1988,28 +2243,32 @@ def xhr_vulnerability_commit(request):
product = product_obj,
comments = vulnerability_obj.comments,
packages = vulnerability_obj.packages,
+ public = vulnerability_obj.public,
)
vul2inv = VulnerabilityToInvestigation.objects.create(vulnerability=vulnerability_obj,investigation=investigation_obj)
vul2inv.save()
investigation_names.append(iname)
+ # Assert part CVE access rights
+ for c2v in CveToVulnerablility.objects.filter(vulnerability=vulnerability_obj):
+ c2v.cve.propagate_private()
history_update.append(Update.ATTACH_INV % ','.join(investigation_names))
- if 'submit-trashinvestigation' == action:
+ elif 'submit-trashinvestigation' == action:
inv_id = request.POST['record_id']
investigation_obj = Investigation.objects.get(pk=inv_id)
vul2inv = VulnerabilityToInvestigation.objects.filter(investigation=investigation_obj)
vul2inv.delete()
history_update.append(Update.DETACH_INV % (investigation_obj.name))
investigation_obj.delete()
- if 'submit-newcomment' == action:
+ elif 'submit-newcomment' == action:
comment = request.POST['comment']
VulnerabilityComments.objects.create(vulnerability_id=v_id, comment=comment, date=datetime.today().strftime('%Y-%m-%d'), author=username)
#NOTE: No History for this
- if 'submit-trashcomment' == action:
+ elif 'submit-trashcomment' == action:
record_id = request.POST['record_id']
comment = VulnerabilityComments.objects.get(id=record_id)
comment.delete()
#NOTE: No History for this
- if 'submit-trashattachment' == action:
+ elif 'submit-trashattachment' == action:
record_id = request.POST['record_id']
upload = VulnerabilityUploads.objects.get(id=record_id)
try:
@@ -2018,45 +2277,114 @@ def xhr_vulnerability_commit(request):
pass
history_update.append(Update.DETACH_DOC % (upload.path))
upload.delete()
- if 'submit-addusernotify' == action:
+ elif 'submit-addusernotify' == action:
users = request.POST['users']
usernames = []
for user_id in users.split(','):
- usernames.append(SrtUser.objects.get(pk=user_id).name)
+ usernames.append(SrtUser.objects.get(pk=user_id).username)
VulnerabilityNotification.objects.get_or_create(vulnerability_id=v_id, user_id=user_id)
history_update.append(Update.ATTACH_USER_NOTIFY % ','.join(usernames))
- if 'submit-trashusernotification' == action:
+ elif 'submit-trashusernotification' == action:
record_id = request.POST['record_id']
notification_record = VulnerabilityNotification.objects.get(id=record_id)
- removed_user = SrtUser.objects.get(pk=notification_record.user_id).name
+ removed_user = SrtUser.objects.get(pk=notification_record.user_id).username
notification_record.delete()
history_update.append(Update.DETACH_USER_NOTIFY % removed_user)
- if 'submit-adduseraccess' == action:
+ elif 'submit-adduseraccess' == action:
users = request.POST['users']
usernames = []
for user_id in users.split(','):
- usernames.append(SrtUser.objects.get(pk=user_id).name)
+ usernames.append(SrtUser.objects.get(pk=user_id).username)
VulnerabilityAccess.objects.get_or_create(vulnerability_id=v_id, user_id=user_id)
history_update.append(Update.ATTACH_ACCESS % ','.join(usernames))
- if 'submit-trashuseraccess' == action:
+ elif 'submit-trashuseraccess' == action:
record_id = request.POST['record_id']
access_record = VulnerabilityAccess.objects.get(id=record_id)
+ history_update.append(Update.DETACH_ACCESS % access_record.user.username)
access_record.delete()
- history_update.append(Update.DETACH_ACCESS % username)
- if 'submit-notification' == action:
+ elif 'submit-notification' == action:
_submit_notification(request)
#NOTE: No History for this
- if 'submit-trashvulnerability' == action:
+ elif 'submit-trashvulnerability' == action:
record_id = request.POST['record_id']
vulnerability_obj = Vulnerability.objects.get(pk=record_id)
# history_update.append(Update.DETACH_VUL % vulnerability_obj.name)
vulnerability_obj.delete()
+ elif 'submit-attach-cve' == action:
+ vulnerability_obj = Vulnerability.objects.get(pk=v_id)
+ cve_name_input = request.POST['cve_name']
+ # Sanitize the CVE name
+ cve_name = ''
+ for i in range(len(cve_name_input)):
+ if not cve_name_input[i] in ('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-0123456789_ '):
+ cve_name += '_'
+ else:
+ cve_name += cve_name_input[i]
+ try:
+ cve_obj = Cve.objects.get(name=cve_name)
+ # Does the user have permission to see this CVE?
+ if (not cve_obj.public) and (not UserSafe.is_admin(request.user)):
+ try:
+ cveaccess = CveAccess.objects.get(cve=cve_obj,user=request.user)
+ except:
+ cveaccess = None
+ if not cveaccess:
+ _log("CVE_ATTACHE_ERROR_PERMISSIONS:(%s)" % request.user)
+ return HttpResponse(json.dumps( {"error":"Error: this CVE name is reserved"} ), content_type = "application/json")
+ except:
+ # Create local CVE with this name
+ cve_obj,cve_local_object = _create_local_cve()
+ old_name = cve_obj.name
+ cve_obj.description = vulnerability_obj.description
+ cve_obj.name = cve_name
+ cve_obj.save()
+ cve_local_object.description = vulnerability_obj.description
+ cve_local_object.save()
+ # Apply the new name to CveLocal
+ cveLocal = CveLocal.objects.get(name=old_name)
+ cveLocal.name = cve_name
+ cveLocal.save()
+
+ history_cve_update = []
+ if not vulnerability_obj.public:
+ history_cve_update.append(Update.PUBLIC % (cve_obj.public,vulnerability_obj.public))
+ cve_obj.public = vulnerability_obj.public
+ # Insure newly private record has at least this user
+ cve_access,created = CveAccess.objects.get_or_create(cve=cve_obj, user=request.user)
+ cve_access.save()
+ cve_obj.propagate_private()
+ cve_obj.save()
+
+ # Attach the CVE to the Vulnerability
+ c2v,create = CveToVulnerablility.objects.get_or_create(vulnerability=vulnerability_obj,cve=cve_obj)
+ c2v.save()
+ # Add history to CVE
+ username = UserSafe.user_name(request.user)
+ history_cve_update.append(Update.ATTACH_INV % (vulnerability_obj.name))
+ update_comment = "%s%s" % (Update.UPDATE_STR % Update.SOURCE_USER,';'.join(history_cve_update))
+ cve_hist = CveHistory.objects.create(cve_id=cve_obj.id, comment=update_comment, date=datetime.now().strftime('%Y-%m-%d'), author=username)
+ cve_hist.save()
+ history_update.append(Update.ATTACH_CVE % cve_obj.name)
+ elif 'submit-detach-cve' == action:
+ vulnerability_obj = Vulnerability.objects.get(pk=v_id)
+ record_id = request.POST['record_id']
+ cve_obj = Cve.objects.get(id=record_id)
+ c2v = CveToVulnerablility.objects.get(vulnerability=vulnerability_obj,cve=cve_obj)
+ c2v.delete()
+ update_comment = "%s%s" % (Update.UPDATE_STR % Update.SOURCE_USER,Update.DETACH_VUL % vulnerability_obj.name)
+ cve_hist = CveHistory.objects.create(cve_id=cve_obj.id, comment=update_comment, date=datetime.now().strftime('%Y-%m-%d'), author=username)
+ cve_hist.save()
+ history_update.append(Update.DETACH_CVE % cve_obj.name)
+ else:
+ # Action not found
+ return HttpResponse(json.dumps( {"error": "ERROR:unknown action '%s'" % action} ), content_type = "application/json")
if history_update:
update_comment = "%s%s" % (Update.UPDATE_STR % Update.SOURCE_USER,';'.join(history_update))
VulnerabilityHistory.objects.create(vulnerability_id=v_id, comment=update_comment, date=datetime.now().strftime('%Y-%m-%d'), author=username)
return_data = {
"error": "ok",
+ "new_name" : new_name,
}
return HttpResponse(json.dumps( return_data ), content_type = "application/json")
except Exception as e:
@@ -2231,7 +2559,7 @@ def xhr_investigation_commit(request):
history_update.append(Update.AFFECTED_COMPONENT % (invst.packages,affected_components))
invst.packages = affected_components
invst.save()
- if 'submit-attachdefectlist' == action:
+ elif 'submit-attachdefectlist' == action:
defects = request.POST['defects']
product_id = Investigation.objects.get(id=invst_id).product_id
defect_names = []
@@ -2239,7 +2567,7 @@ def xhr_investigation_commit(request):
defect_names.append(Defect.objects.get(pk=defect_id).name)
InvestigationToDefect.objects.get_or_create(investigation_id=invst_id, defect_id=defect_id)
history_update.append(Update.ATTACH_DEV % ','.join(defect_names))
- if 'submit-attachdefect' == action:
+ elif 'submit-attachdefect' == action:
query = request.POST['query'].upper()
product_id = Investigation.objects.get(id=invst_id).product_id
# Courtesy removal of URL (or other) prefix
@@ -2271,31 +2599,40 @@ def xhr_investigation_commit(request):
defect.srt_status = invst.status
defect.save()
history_update.append(Update.ATTACH_DEV % defect.name)
- if 'submit-createdefect' == action:
+ elif 'submit-createdefect' == action:
investigation = Investigation.objects.get(id=invst_id)
defect_reason = request.POST['defect_reason']
components = request.POST['components']
priority = request.POST['priority']
+ try:
+ # if explicit selected priority, reset Investigation to that
+ priority = int(priority)
+ if priority != investigation.priority:
+ investigation.priority = priority
+ investigation.save()
+ except Exception as e:
+ _log("WARINING:defect_create:priority issue:'%s'" % priority)
+
affected_components = request.POST['affected_components'].strip()
defect_name,created = _create_defect(investigation,'',defect_reason,components,affected_components,username)
history_update.append(Update.ATTACH_DEV % defect_name)
xhr_note = defect_name
- if 'submit-detachdefect' == action:
+ elif 'submit-detachdefect' == action:
defect_name = request.POST['defect']
product_id = Investigation.objects.get(id=invst_id).product_id
defect_id = Defect.objects.get(name=defect_name).id
InvestigationToDefect.objects.get(investigation_id=invst_id, defect_id=defect_id).delete()
history_update.append(Update.DETACH_DEV % defect_name)
- if 'submit-newcomment' == action:
+ elif 'submit-newcomment' == action:
comment = request.POST['comment']
InvestigationComments.objects.create(investigation_id=invst_id, comment=comment, date=datetime.today().strftime('%Y-%m-%d'), author=username)
#NOTE: No History for this
- if 'submit-trashcomment' == action:
+ elif 'submit-trashcomment' == action:
record_id = request.POST['record_id']
comment = InvestigationComments.objects.get(id=record_id)
comment.delete()
#NOTE: No History for this
- if 'submit-trashattachment' == action:
+ elif 'submit-trashattachment' == action:
record_id = request.POST['record_id']
upload = InvestigationUploads.objects.get(id=record_id)
try:
@@ -2304,39 +2641,45 @@ def xhr_investigation_commit(request):
pass
history_update.append(Update.DETACH_DOC % (upload.path))
upload.delete()
- if 'submit-addusernotify' == action:
+ elif 'submit-addusernotify' == action:
users = request.POST['users']
usernames = []
for user_id in users.split(','):
- usernames.append(SrtUser.objects.get(pk=user_id).name)
+ usernames.append(SrtUser.objects.get(pk=user_id).username)
InvestigationNotification.objects.get_or_create(investigation_id=invst_id, user_id=user_id)
history_update.append(Update.ATTACH_USER_NOTIFY % ','.join(usernames))
- if 'submit-trashusernotification' == action:
+ elif 'submit-trashusernotification' == action:
record_id = request.POST['record_id']
notification_record = InvestigationNotification.objects.get(id=record_id)
- removed_user = SrtUser.objects.get(pk=notification_record.user_id).name
+ removed_user = SrtUser.objects.get(pk=notification_record.user_id).username
history_update.append(Update.DETACH_USER_NOTIFY % removed_user)
notification_record.delete()
- if 'submit-adduseraccess' == action:
+ elif 'submit-adduseraccess' == action:
users = request.POST['users']
usernames = []
for user_id in users.split(','):
- usernames.append(SrtUser.objects.get(pk=user_id).name)
+ usernames.append(SrtUser.objects.get(pk=user_id).username)
InvestigationAccess.objects.get_or_create(investigation_id=invst_id, user_id=user_id)
history_update.append(Update.ATTACH_ACCESS % ','.join(usernames))
- if 'submit-trashuseraccess' == action:
+ elif 'submit-trashuseraccess' == action:
record_id = request.POST['record_id']
access_record = InvestigationAccess.objects.get(id=record_id)
- history_update.append(Update.DETACH_ACCESS % username)
+ history_update.append(Update.DETACH_ACCESS % access_record.user.username)
access_record.delete()
- if 'submit-notification' == action:
+ elif 'submit-notification' == action:
_submit_notification(request)
#NOTE: No History for this
- if 'submit-trashinvestigation' == action:
+ elif 'submit-trashinvestigation' == action:
record_id = request.POST['record_id']
investigation_obj = Investigation.objects.get(pk=record_id)
# history_update.append(Update.DETACH_INV % investigation_obj.name)
investigation_obj.delete()
+ else:
+ return_data = {
+ "error": "ERROR:unknown action '%s'" % action,
+ "new_name" : new_name,
+ }
+ return HttpResponse(json.dumps( return_data ), content_type = "application/json")
if history_update:
update_comment = "%s%s" % (Update.UPDATE_STR % Update.SOURCE_USER,';'.join(history_update))
@@ -2354,8 +2697,6 @@ def xhr_investigation_commit(request):
def xhr_publish(request):
_log("xhr_publish(%s)" % request.POST)
- main_app = SrtSetting.get_setting('SRT_MAIN_APP','yp')
-
def remove_mark(mark,line):
pos1 = line.find(mark)
if -1 == pos1:
@@ -2395,18 +2736,18 @@ def xhr_publish(request):
if (not top_dir) and (snap_date_top == backup_date) and ('Now' != backup_mode):
top_dir = 'backups/%s' % backup_dir
- _log('Publish:./bin/%s/srtool_publish.py --srt2update %s' % (main_app,base_dir))
- report_returncode,report_stdout,report_error = execute_process('./bin/%s/srtool_publish.py' % main_app,'--srt2update',base_dir)
+ _log('Publish:./bin/%s/srtool_publish.py --srt2update %s' % (SRT_MAIN_APP,base_dir))
+ report_returncode,report_stdout,report_error = execute_process('./bin/%s/srtool_publish.py' % SRT_MAIN_APP,'--srt2update',base_dir)
if 0 != report_returncode:
return_data = {"error": "Error: base dir prep:%s:%s" % (report_error,report_stdout),}
return HttpResponse(json.dumps( return_data ), content_type = "application/json")
- _log('Publish:./bin/%s/srtool_publish.py --srt2update %s' % (main_app,top_dir))
- report_returncode,report_stdout,report_error = execute_process('./bin/%s/srtool_publish.py' % main_app,'--srt2update',top_dir)
+ _log('Publish:./bin/%s/srtool_publish.py --srt2update %s' % (SRT_MAIN_APP,top_dir))
+ report_returncode,report_stdout,report_error = execute_process('./bin/%s/srtool_publish.py' % SRT_MAIN_APP,'--srt2update',top_dir)
if 0 != report_returncode:
return_data = {"error": "Error: top dir prep:%s:%s" % (report_error,report_stdout),}
return HttpResponse(json.dumps( return_data ), content_type = "application/json")
- _log('Publish:./bin/'+main_app+'/srtool_publish.py --validate-update-svns --previous '+base_dir+' --current '+top_dir+' --start '+snap_date_start+' --stop '+snap_date_stop)
- report_returncode,report_stdout,report_error = execute_process('./bin/%s/srtool_publish.py' % main_app,
+ _log('Publish:./bin/'+SRT_MAIN_APP+'/srtool_publish.py --validate-update-svns --previous '+base_dir+' --current '+top_dir+' --start '+snap_date_start+' --stop '+snap_date_stop)
+ report_returncode,report_stdout,report_error = execute_process('./bin/%s/srtool_publish.py' % SRT_MAIN_APP,
'--validate-update-svns','--previous',base_dir,'--current',top_dir,
'--start',snap_date_start,'--stop',snap_date_stop)
if 0 != report_returncode:
@@ -2420,9 +2761,51 @@ def xhr_publish(request):
SrtSetting.set_setting('publish_snap_last_calc',publish_snap_last_calc)
_log('Publish:Done!')
+
+ if 'export-snapshot-progress' == action:
+ snap_date_base = request.POST['snap_date_base']
+ snap_date_top = request.POST['snap_date_top']
+ snap_date_start = request.POST['snap_date_start']
+ snap_date_stop = request.POST['snap_date_stop']
+ _log("xhr_publish:export-snapshot:%s,%s,%s,%s" % (snap_date_base,snap_date_top,snap_date_start,snap_date_stop))
+
+ SrtSetting.set_setting('publish_snap_date_base',snap_date_base)
+ SrtSetting.set_setting('publish_snap_date_top',snap_date_top)
+ SrtSetting.set_setting('publish_snap_date_start',snap_date_start)
+ SrtSetting.set_setting('publish_snap_date_stop',snap_date_stop)
+
+ backup_returncode,backup_stdout,backup_result = execute_process('bin/common/srtool_backup.py','--list-backups-db')
+ base_dir = ''
+ top_dir = ''
+ for i,line in enumerate(backup_stdout.decode("utf-8").splitlines()):
+ # Week|backup_2019_19|2019-05-18|12:51:51|Saturday, May 18 2019
+ backup_mode,backup_dir,backup_date,backup_time,backup_day = line.split('|')
+ if (not base_dir) and (snap_date_base == backup_date):
+ base_dir = 'backups/%s' % backup_dir
+ if (not top_dir) and (snap_date_top == backup_date) and ('Now' != backup_mode):
+ top_dir = 'backups/%s' % backup_dir
+
+ _log('PublishProgress:./bin/%s/srtool_publish.py --srt2update %s' % (SRT_MAIN_APP,base_dir))
+
+ command = [
+ './bin/%s/srtool_publish.py' % SRT_MAIN_APP,
+ '--validate-update-svns-progress','--previous',base_dir,'--current',top_dir,
+ '--start',snap_date_start,'--stop',snap_date_stop,
+ ' --progress'
+ ]
+ Job.start('Update svns progress','Create SVNS diff file',' '.join(command),'','update_logs/run_svns_job.log',job_id=2)
+
+ publish_snap_last_calc = 'Base:%s, Top:%s, Start:%s, Stop:%s, On:%s' % (
+ snap_date_base,snap_date_top,snap_date_start,snap_date_stop,
+ datetime.today().strftime("%Y-%m-%d %H:%M:%S")
+ )
+ SrtSetting.set_setting('publish_snap_last_calc',publish_snap_last_calc)
+
+ _log('PublishProgress:Done!')
+
elif 'submit-trashreport' == action:
report_name = request.POST['report_name']
- os.remove('data/%s/%s' % (main_app,report_name))
+ os.remove('data/%s/%s' % (SRT_MAIN_APP,report_name))
else:
srtool_today_time = datetime.today()
srtool_today = datetime.today().strftime("%Y-%m-%d")
@@ -2494,31 +2877,290 @@ def xhr_publish(request):
return HttpResponse(json.dumps({"error":str(e) + "\n"}), content_type = "application/json")
+def attach_cve_alternates(cve,force_nist_update=True):
+ # Attach all matching CVE sources
+ #_log("Alternate1:%s" % (cve.name))
+ for ds in DataSource.objects.filter(data="cve"):
+ #_log("Alternate2:%s:%s:%s:%s:" % (ds.key,ds.cve_filter,cve.name,ds.cve_filter))
+ if ds.cve_filter and cve.name.startswith(ds.cve_filter):
+ try:
+ cve_source_object,created = CveSource.objects.get_or_create(cve=cve,datasource=ds)
+ except:
+ ### WORKAROUND TODO TOFIX
+ cve_source_object = CveSource.objects.filter(cve=cve,datasource=ds).first()
+ created = False
+ #_log("Alternate CVE source %s for %s (created=%s)" % (ds.key,cve.name,created))
+
+ # Force update the CVE summary data from sources
+ if force_nist_update:
+ result_returncode,result_stdout,result_stderr = execute_process(
+ './bin/nist/srtool_nist.py',
+ '--update-cve-list',
+ cve.name,
+ '--force'
+ )
+ #_log("CVE_ALT_REFRESH=%s|%s|%s" % (result_returncode,result_stdout,result_stderr))
+
def cve_alternates(request, cve_pk):
try:
cve_object = Cve.objects.get(pk=cve_pk)
except Exception as e:
_log("CVE_ERROR(%s):" % e)
return redirect(landing)
-
# Attach all matching CVE sources
- _log("Alternate1:%s" % (cve_object.name))
- for ds in DataSource.objects.filter(data="cve"):
- _log("Alternate2:%s" % (ds.key))
- if ds.cve_filter and cve_object.name.startswith(ds.cve_filter):
- cve_source_object,created = CveSource.objects.get_or_create(cve=cve_object,datasource=ds)
- _log("Alternate CVE source %s for %s (created=%s)" % (ds.key,cve_object.name,created))
+ attach_cve_alternates(cve_object)
+ return redirect(cve, cve_pk)
- # Force update the CVE summary data from sources
- result_returncode,result_stdout,result_stderr = execute_process(
- './bin/nist/srtool_nist.py',
- '--update-cve-list',
- cve_object.name,
- '--force'
- )
- _log("CVE_ALT_REFRESH=%s|%s|%s" % (result_returncode,result_stdout,result_stderr))
+def xhr_maintenance_commit(request):
+ _log("xhr_maintenance_commit(%s)" % request.POST)
+ if not 'action' in request.POST:
+ return HttpResponse(json.dumps({"error":"missing action\n"}), content_type = "application/json")
+ try:
+ if request.POST["action"] == "<some_action>":
+ pass
- return redirect(cve, cve_pk)
+ return_data = {
+ "error": "ok",
+ }
+ _log("xhr_maintenance_commit:SUCCESS")
+ return HttpResponse(json.dumps( return_data ), content_type = "application/json")
+
+ except Exception as e:
+ _log("xhr_triage_commit:no(%s)" % e)
+ return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
+
+def xhr_sources_commit(request):
+ _log("xhr_sources_commit(%s)" % request.POST)
+ if not 'action' in request.POST:
+ return HttpResponse(json.dumps({"error":"missing action\n"}), content_type = "application/json")
+ try:
+ error_message = "ok";
+ data_message = "";
+ if request.POST["action"] == "submit-run-update-job":
+ ds_id = int(request.POST['id'])
+ datasource = DataSource.objects.get(id=ds_id)
+ #Job.start(name,description,command,options='',log_file=None,job_id=1):
+ name = datasource.name
+ description = datasource.description
+ options = ''
+ # Force update to execute now
+ command = datasource.update + ' --force'
+ _log("SUBMIT-RUN-UPDATE-JOB:Job.start(%s,%s,%s,%s)" % (name,description,command,options))
+ with open(f"{SRT_BASE_DIR}/update_logs/master_log.txt", "a") as update_log:
+ update_log.write("SRTOOL_UPDATE_MANUAL:%s:%s:%s:\n" % (datetime.now(),datasource.description,command))
+ Job.start(name,description,command,options)
+
+ elif request.POST["action"] == "submit-toggle-enable":
+ ds_id = int(request.POST['id'])
+ datasource = DataSource.objects.get(id=ds_id)
+ if 'DISABLE ' in datasource.attributes:
+ datasource.attributes = datasource.attributes.replace('DISABLE ','')
+ datasource.attributes = 'ENABLE ' + datasource.attributes
+ else:
+ datasource.attributes = 'DISABLE ' + datasource.attributes
+ datasource.attributes = datasource.attributes.replace('ENABLE ','')
+ datasource.save()
+ error_message = 'no_refresh'
+ data_message = '%d=%s' % (datasource.id,datasource.attributes)
+
+ else:
+ error_message = "ERROR:unknown action '%s'" % request.POST["action"]
+
+ return_data = {
+ "error": error_message,
+ "data_message": data_message,
+ }
+ _log("xhr_sources_commit:SUCCESS")
+ return HttpResponse(json.dumps( return_data ), content_type = "application/json")
+
+ except Exception as e:
+ _log("xhr_triage_commit:no(%s)" % e)
+ return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
+
+def xhr_job_post(request):
+ _log("xhr_job_post(%s)2" % request.POST)
+ if not 'action' in request.POST:
+ return HttpResponse(json.dumps({"error":"missing action\n"}), content_type = "application/json")
+ try:
+ if request.POST["action"] == "submit-job":
+ command = request.POST.get('command', 'NoCmnd')
+ name = request.POST.get('name', 'NoName')
+ options = request.POST.get('options', '')
+ Job.start(name,'Submit Job',command,options,'update_logs/Job.start_user.log')
+ elif request.POST["action"] == "submit-testjob":
+ command = request.POST.get('command', 'NoCmnd')
+ name = request.POST.get('name', 'NoName')
+ options = request.POST.get('options', '')
+ Job.start(name,'This is a test',command,options,'update_logs/Job.start_user.log')
+ elif request.POST["action"] == "submit-testjob-j2":
+ command = request.POST.get('command', 'NoCmnd')
+ name = request.POST.get('name', 'NoName')
+ options = request.POST.get('options', '')
+ Job.start(name,'This is a test',command,options,'update_logs/Job.start_user.log',job_id=2)
+ elif request.POST["action"] == "submit-testjob-parent":
+ command = request.POST.get('command', 'NoCmnd')
+ name = request.POST.get('name', 'NoName')
+ options = request.POST.get('options', '')
+ # Preclear previously completed jobs from view
+ for job in Job.objects.all():
+ if not job.status in (Job.NOTSTARTED,Job.INPROGRESS):
+ job.status = Job.NOTSTARTED
+ job.save()
+ Job.start(name,'Parent/Children test',"./bin/common/srtool_job.py --test-parent-job",options,'update_logs/Job.start_user.log',job_id=9)
+ elif request.POST["action"] == "submit-trash-job":
+ record_id = int(request.POST.get('record_id', '0'))
+ if UserSafe.is_admin(request.user):
+ Job.objects.get(id=record_id).delete()
+ elif request.POST["action"] == "submit-clearjobs":
+ if UserSafe.is_admin(request.user):
+ Job.objects.all().delete()
+ else:
+ return_data = {
+ "error": "ERROR:unknown action '%s'" % request.POST["action"],
+ }
+ return HttpResponse(json.dumps( return_data ), content_type = "application/json")
+
+ return_data = {
+ "error": "ok",
+ }
+ _log("xhr_maintenance_commit:SUCCESS")
+ return HttpResponse(json.dumps( return_data ), content_type = "application/json")
+
+ except Exception as e:
+ _log("xhr_job_post:no(%s)" % e)
+ return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
+
+def joblog(request,job_pk):
+ if request.method == "GET":
+ _log("GET_JOBLOG:%s" % job_pk)
+ job, created = Job.objects.get_or_create(id=job_pk)
+ template = "joblog.html"
+ log_text = ''
+ log_file = job.log_file if (job.log_file and ('/' == job.log_file[0])) else os.path.join(SRT_BASE_DIR,job.log_file)
+ if job.log_file:
+ with open(os.path.join(SRT_BASE_DIR,log_file),'r') as file:
+ log_text = file.read() #Note: keep EOL chars
+ context = {
+ 'object' : job,
+ 'log_text' : log_text,
+ 'log_date' : time.asctime(time.localtime(os.path.getmtime(log_file))),
+ }
+ return render(request, template, context)
+ # No action if no log
+ return HttpResponse(json.dumps( {"error": "ok",} ), content_type = "application/json")
+ elif request.method == "POST":
+ _log("POST_JOBLOG: %s" % request)
+
+ if request.POST["action"] == "download-job-log":
+ try:
+ job = Job.objects.get(id=job_pk)
+ file_path = job.log_file
+ except:
+ # In case job was cleaned up but old link for log was still visible
+ file_path = ''
+ if file_path:
+ fsock = open(file_path, "rb")
+ file_name = os.path.basename(file_path)
+ content_type = MimeTypeFinder.get_mimetype(file_path)
+ response = HttpResponse(fsock, content_type = content_type)
+ disposition = 'attachment; filename="{}"'.format(file_name)
+ response['Content-Disposition'] = 'attachment; filename="{}"'.format(file_name)
+ _log("EXPORT_POST_Q{%s} %s || %s " % (response, response['Content-Disposition'], disposition))
+ return response
+ else:
+ return render(request, "unavailable_artifact.html", context={})
+
+ return redirect("/srtgui/joblog")
+
+ raise Exception("Invalid HTTP method for this page")
+
+def email_admin(request):
+ if request.method == "GET":
+ context = {
+ 'error_message' : '',
+ }
+ return render(request, 'email_admin.html', context)
+ elif request.method == "POST":
+ _log("EMAIL_ADMIN: %s" % request)
+
+ if request.POST["action"] == "submit":
+ request_type = request.POST.get('request-type', '')
+ user_name = request.POST.get('user-name', '').strip()
+ user_email = request.POST.get('user-email', '').strip()
+ message = request.POST.get('message', '').strip()
+ if (not user_name) or (not user_email):
+ return render(request, 'email_admin.html', {'error_message' : "Error:missing user name or email",})
+
+ email_list = []
+ for user in SrtUser.get_group_users('SRTool_Admins'):
+ if user.email:
+ email_list.append(user.email)
+ if not email_list:
+ return render(request, 'email_admin.html', {'error_message' : "Error:missing admin emails. Contact SRTool team",})
+# email_list.append(user_email)
+
+ email_temp_file = '.email.txt'
+ with open(email_temp_file, 'w') as file:
+ print("SRTool alert: %s for %s" % (request_type,user_name),file=file)
+ print("From: %s" % user_email,file=file)
+ for email in email_list:
+ print("To: %s" % email,file=file)
+ print("Subject: %s requests %s" % (user_name,request_type),file=file)
+ print("",file=file)
+ print("SRTool alert: %s" % request_type,file=file)
+ print("From: %s" % user_name,file=file)
+ print("Email: %s" % user_email,file=file)
+ print("",file=file)
+ print(message,file=file)
+
+ smtp_server = os.environ.get('SRT_EMAIL_SMTP', 'MISSING_SRT_EMAIL_SMTP')
+ email_command = ['git','send-email','--from='+user_email,'--thread','--quiet','--confirm=never',\
+ '--smtp-server',smtp_server,'--to=%s' % ','.join(email_list), email_temp_file]
+ email_returncode,email_stdout,email_stderr = execute_process(email_command)
+ if email_returncode:
+ return render(request, 'email_admin.html', {'error_message' : email_stderr,})
+ return redirect(email_success)
+
+ elif request.POST["action"] == "cancel":
+ return redirect('/')
+
+ else:
+ return render(request, 'email_admin.html', {'error_message' : "Error:no such action '%s'" % request.POST["action"]})
+
+ raise Exception("Invalid HTTP method for this page")
+
+def email_success(request):
+ if request.method == "GET":
+ context = {
+ }
+ return render(request, 'email_success.html', context)
+ elif request.method == "POST":
+ _log("EMAIL_SUCCESS: %s" % request)
+ if request.POST["action"] == "close":
+ return redirect('/')
+ return redirect('/')
+
+def date_time_test(request):
+ utc_dt = datetime.now(timezone.utc)
+ current_ala = utc_dt.astimezone(pytz.timezone('US/Pacific')).strftime(SRTool.DATETIME_FORMAT)
+ user_timezone_str = request.user.map_usertz_to_usertz_str()
+
+ # Replace with getting user_timezone_str from the user record
+ user_timezone = pytz.timezone(request.user.map_usertz_str_to_usertz(user_timezone_str))
+
+ epoch = time.time()
+ offset = utc_dt.astimezone(user_timezone).replace(tzinfo=None) - datetime.fromtimestamp(epoch)
+ local_time = utc_dt + offset
+ current_local = local_time.strftime(SRTool.DATETIME_FORMAT)
+
+ context = {
+ 'current_utc' : datetime.utcnow().strftime(SRTool.DATETIME_FORMAT),
+ 'current_ala' : current_ala,
+ 'current_local' : current_local,
+ 'timezone_list' : SrtUser.get_timezone_list(),
+ 'user_timezone' : user_timezone,
+ }
+ return render(request, 'date-time-test.html', context)
def tbd(request):
diff --git a/lib/srtgui/widgets.py b/lib/srtgui/widgets.py
index 5f5c54b1..ec2fbd42 100644
--- a/lib/srtgui/widgets.py
+++ b/lib/srtgui/widgets.py
@@ -4,7 +4,7 @@
#
# BitBake Toaster Implementation
#
-# Copyright (C) 2015 Intel Corporation
+# Copyright (C) 2023 Intel Corporation
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
@@ -21,6 +21,7 @@
from django.views.generic import View, TemplateView
from django.views.decorators.cache import cache_control
+from django.utils.decorators import method_decorator
from django.shortcuts import HttpResponse
from django.core.cache import cache
from django.core.paginator import Paginator, EmptyPage
@@ -29,8 +30,15 @@ from django.template import Context, Template
from django.template import VariableDoesNotExist
from django.template import TemplateSyntaxError
from django.core.serializers.json import DjangoJSONEncoder
+from django.urls import reverse, resolve
+from django.utils import timezone
+from django.http import JsonResponse
+
+from srtgui.templatetags.jobtags import json as template_json
+from srtgui.templatetags.jobtags import sectohms
from orm.models import SrtSetting, Cve
+from orm.models import Job
import types
import json
@@ -51,6 +59,38 @@ class NoFieldOrDataName(Exception):
# quick development/debugging support
from srtgui.api import _log
+################################################
+### Helper Routines
+
+def isbalanced(s):
+ c= 0
+ ans=False
+ list1 = []
+ list2 = []
+ strcheck = ""
+ lstcheck = []
+ for i in range(len(s)):
+ if s[i] == "(":
+ strcheck = strcheck + "_" + s[i]
+ lstcheck.append(i)
+ elif s[i] == ")" and "(" in strcheck:
+ list1 = strcheck.split("_")
+ list1.pop()
+ lstcheck.pop()
+ strcheck = "_".join(list1)
+ elif s[i] == ")" and "(" not in strcheck:
+ strcheck = strcheck + "_" + s[i]
+ lstcheck.append(i)
+ list1 = strcheck.split("_")
+ list2[:0] = s
+ if len(lstcheck) > 0 :
+ for i2 in lstcheck:
+ list2.pop(lstcheck[0])
+ return "".join(list2)
+
+################################################
+### ToasterTable
+
class ToasterTable(TemplateView):
def __init__(self, *args, **kwargs):
super(ToasterTable, self).__init__()
@@ -60,7 +100,7 @@ class ToasterTable(TemplateView):
self.queryset = None
self.columns = []
- _log("ToasterTable:%s,%s" % (args,kwargs))
+# _log("ToasterTable:%s,%s" % (args,kwargs))
# map from field names to Filter instances
self.filter_map = TableFilterMap()
@@ -71,8 +111,8 @@ class ToasterTable(TemplateView):
self.default_orderby = ""
# prevent HTTP caching of table data
- @cache_control(must_revalidate=True,
- max_age=0, no_store=True, no_cache=True)
+ @method_decorator(cache_control(must_revalidate=True,
+ max_age=0, no_store=True, no_cache=True))
def dispatch(self, request, *args, **kwargs):
return super(ToasterTable, self).dispatch(request, *args, **kwargs)
@@ -96,6 +136,17 @@ class ToasterTable(TemplateView):
def get(self, request, *args, **kwargs):
if request.GET.get('format', None) == 'json':
+ # Add all URL parameters to kwargs, specifically for the
+ # case of the Toaster table JSON 'get' for the 'filter' AJAX
+ # call which does not include the request header parameters
+ tableParams = self.request.GET.get('tableParams','')
+ for param in tableParams.split(','):
+ pos = param.find('=')
+ if 0 < pos:
+ name = param[:param.find('=')]
+ value = param[param.find('=')+1:]
+ kwargs[name] = value
+
self.setup_queryset(*args, **kwargs)
# Put the project id into the context for the static_data_template
if 'pid' in kwargs:
@@ -228,25 +279,89 @@ class ToasterTable(TemplateView):
TableFilterAction* before its filter is applied and may modify the
queryset returned by the filter
"""
+
self.setup_filters(**kwargs)
try:
- filter_name, action_name = filters.split(':')
+# filter_name, action_name = filters.split(':')
+ if len(filters.split(",")) < 2 :
+ filter_name, action_name = filters.split(':')
action_params = unquote_plus(filter_value)
except ValueError:
return
- if "all" in action_name:
- return
-
- table_filter = self.filter_map.get_filter(filter_name)
- action = table_filter.get_action(action_name)
- action.set_filter_params(action_params)
- self.queryset = action.filter(self.queryset)
+# if "all" in action_name:
+# return
+
+ FilterString = ""
+ CriteriaString = ""
+ lstactionlist = []
+ if self.request.session.get('filterkey'):
+ if False:
+ if self.request.session['filterkey'].split("~")[0] != "":
+ CriteriaString = self.request.session['filterkey']
+ CriteriaString = CriteriaString.split("~")[0]
+ FilterString = str(filters) + str('|') + str(CriteriaString)
+ FilterList = FilterString.split('|')
+
+
+ # if self.request.session['filterkey'].split("~")[0] != "":
+ if len(filters.split(",")) > 1:
+ # CriteriaString = self.request.session['filterkey']
+ # CriteriaString = CriteriaString.split("~")[0]
+ # for CriteriaItem in filters.split(","):
+ # if not CriteriaItem in FilterString:
+ # FilterString = str(CriteriaItem) + str('|') + str(CriteriaString)
+ FilterList = filters.split(",")
+
+ q1 = None
+ q2 = self.queryset
+ for filterItem in FilterList:
+ #if counter == 0:
+ table_filter1 = self.filter_map.get_filter(filterItem.split(":")[0])
+ action1 = table_filter1.get_action(filterItem.split(":")[1])
+ action1.set_filter_params(action_params)
+ q1 = action1.filter(q2)
+ q2 = q1
+ lstactionlist.append(filterItem.split(":")[1])
+ self.queryset = q1
+ else:
+ table_filter = self.filter_map.get_filter(filter_name)
+ action = table_filter.get_action(action_name)
+ action.set_filter_params(action_params)
+ self.queryset = action.filter(self.queryset)
+ FilterString = str(filters)
+ lstactionlist.append(action_name)
+ else:
+ table_filter = self.filter_map.get_filter(filter_name)
+ action = table_filter.get_action(action_name)
+ action.set_filter_params(action_params)
+ self.queryset = action.filter(self.queryset)
+ FilterString = str(filters)
+ lstactionlist.append(action_name)
+
+ _log("FOO:APPLY_FILTER:FILTER:%s" % action_params)
+
+ strquerystring = self.queryset.query.__str__()
+ qstring1 = strquerystring.replace ('AND', 'AND\n')
+ qstring2 = qstring1.replace ('OR', 'OR\n')
+ tar = re.findall(r"(?<==).+(?= AND)|(?<==)(?<==).+(?= OR )|(?<==).+(?=[)])|(?<==).+(?= OR)", qstring2)
+ for item in tar:
+ if len(re.findall(r"[A-Za-z]", item.strip())) != 0 :
+ item = isbalanced(item)
+ strquerystring = strquerystring.replace(item, '"'+item.strip()+'"' )
+ self.request.session['filterkey'] = str(FilterString) + str('~') + strquerystring
def apply_orderby(self, orderby):
# Note that django will execute this when we try to retrieve the data
- self.queryset = self.queryset.order_by(orderby)
+ if False:
+ # Use parent order field if present (for column computed from existing column)
+ order_by = re.sub(r'.*__parent_', '', orderby)
+# order_by = orderby
+ self.queryset = self.queryset.order_by(order_by)
+ else:
+ self.queryset = self.queryset.order_by(orderby)
+ # self.request.session['filterkey'] = str('~') + str(self.queryset.query)
def apply_search(self, search_term):
"""Creates a query based on the model's search_allowed_fields"""
@@ -285,9 +400,19 @@ class ToasterTable(TemplateView):
else:
search_queries = queries
+ _log("FOO:APPLY_SEARCH:FILTER:%s" % search_queries)
self.queryset = self.queryset.filter(search_queries)
-
- def apply_row_customization(self, row):
+ strquerystring = self.queryset.query.__str__()
+ qstring1 = strquerystring.replace ('AND', 'AND\n')
+ qstring2 = qstring1.replace ('OR', 'OR\n')
+ tar = re.findall(r"(?<==).+(?= AND)|(?<==)(?<==).+(?= OR )|(?<==).+(?=[)])|(?<==).+(?= OR)", qstring2)
+ for item in tar:
+ if len(re.findall(r"[A-Za-z]", item.strip())) != 0 :
+ item = isbalanced(item)
+ strquerystring = strquerystring.replace(item, '"'+item.strip()+'"' )
+ self.request.session['filterkey'] = str('~') + str(strquerystring)
+
+ def apply_row_customization(self, row, **kwargs):
""" function to implement in the subclass which supports
row data customization in the respective table handler """
return row
@@ -311,6 +436,11 @@ class ToasterTable(TemplateView):
orderby = request.GET.get("orderby", None)
nocache = request.GET.get("nocache", None)
+ # Test if clear filters from session
+ if filters == "":
+ if request.session.get('filterkey'):
+ del request.session['filterkey']
+
# Make a unique cache name
cache_name = self.__class__.__name__
@@ -339,6 +469,8 @@ class ToasterTable(TemplateView):
self.setup_columns(**kwargs)
+ self.request.session['nofilterkey'] = str('~') + str(self.queryset.query)
+
if search:
self.apply_search(search)
if filters:
@@ -428,7 +560,7 @@ class ToasterTable(TemplateView):
data['rows'].append(required_data)
# apply any row data customization override before converted to JSON
- data = self.apply_row_customization(data)
+ data = self.apply_row_customization(data, **kwargs)
data = json.dumps(data, indent=2, cls=DjangoJSONEncoder)
cache.set(cache_name, data, 60*30)
@@ -491,3 +623,136 @@ class ToasterTypeAhead(View):
pass
+class MostRecentJobsView(View):
+ def _was_yesterday_or_earlier(self, completed_on):
+ now = timezone.now()
+ delta = now - completed_on
+
+ if delta.days >= 1:
+ return True
+
+ return False
+
+ def get(self, request, *args, **kwargs):
+ """
+ Returns a list of jobs in JSON format.
+ """
+
+ recent_job_objs = Job.get_recent()
+ recent_jobs = []
+
+ for job_obj in recent_job_objs:
+## cancel_url = \
+## reverse('xhr_jobrequest', args=(job_obj.sprint.pk,))
+# cancel_url = \
+# reverse('xhr_jobrequest', )
+ cancel_url = \
+ ''
+
+ job = {}
+ job['id'] = job_obj.pk
+
+ tasks_complete_percentage = 0
+ if job_obj.status in (Job.SUCCESS, Job.ERRORS):
+ tasks_complete_percentage = 100
+ elif job_obj.status == Job.INPROGRESS:
+ tasks_complete_percentage = job_obj.completeper()
+
+ job['tasks_complete_percentage'] = tasks_complete_percentage
+
+ job['state'] = job_obj.get_status_text
+
+ job['errors'] = job_obj.errors
+
+ job['warnings'] = job_obj.warnings
+
+ if job_obj.completed_on and job_obj.started_on:
+ timespent = job_obj.completed_on - job_obj.started_on
+ job['jobtime'] = sectohms(timespent.total_seconds())
+ else:
+ job['jobtime'] = 0
+
+ job['cancel_url'] = cancel_url
+
+ job['job_targets_json'] = \
+ template_json(job_obj.name)
+
+ # convert completed_on time to user's timezone
+ if job_obj.completed_on:
+ completed_on = job_obj.completed_on
+
+ completed_on_template = '%H:%M'
+ if self._was_yesterday_or_earlier(completed_on):
+ completed_on_template = '%d/%m/%Y ' + completed_on_template
+ else:
+ completed_on_template = 'Today ' + completed_on_template
+ job['completed_on'] = completed_on.strftime(
+ completed_on_template)
+ else:
+ job['completed_on'] = 'In progress...'
+
+ job['targets'] = job_obj.message #current remote command
+
+ if job_obj.refresh:
+ # Right now a binary flag, later maybe a timeout counter
+ job['refresh'] = '1' #remote page refresh request
+ job_obj.refresh = 0
+ job_obj.save()
+ else:
+ job['refresh'] = '0'
+
+ recent_jobs.append(job)
+
+ return JsonResponse(recent_jobs, safe=False)
+
+class XhrJobRequest(View):
+
+ def error_response(error):
+ return JsonResponse({"error": error})
+
+ def get(self, request, *args, **kwargs):
+ return HttpResponse()
+
+ def post(self, request, *args, **kwargs):
+ """
+ Job control
+
+ Entry point: /xhr_jobrequest/<project_id>
+ Method: POST
+
+ Args:
+ id: id of job to change
+ jobCancel = job_request_id ...
+ jobDelete = id ...
+
+ Returns:
+ {"error": "ok"}
+ or
+ {"error": <error message>}
+ """
+
+ if 'jobCancel' in request.POST:
+ for i in request.POST['jobCancel'].strip().split(" "):
+ try:
+ job = Job.objects.get(pk=i)
+ job.cancel()
+ except Job.DoesNotExist:
+ return error_response('No such job request id %s' % i)
+
+ return JsonResponse({"error": 'ok'})
+
+ if 'jobDelete' in request.POST:
+ for i in request.POST['jobDelete'].strip().split(" "):
+ try:
+ Job.objects.select_for_update().get(
+ pk=i,
+ state__lte=Job.INPROGRESS).delete()
+
+ except Job.DoesNotExist:
+ pass
+ return error_response("ok")
+
+ response = HttpResponse()
+ response.status_code = 500
+ return response
+
diff --git a/lib/srtmain/management/commands/checksocket.py b/lib/srtmain/management/commands/checksocket.py
index 19e75cb5..803009bc 100644
--- a/lib/srtmain/management/commands/checksocket.py
+++ b/lib/srtmain/management/commands/checksocket.py
@@ -25,7 +25,7 @@ import errno
import socket
from django.core.management.base import BaseCommand, CommandError
-from django.utils.encoding import force_text
+from django.utils.encoding import force_str
DEFAULT_ADDRPORT = "0.0.0.0:8000"
@@ -63,7 +63,7 @@ class Command(BaseCommand):
if hasattr(err, 'errno') and err.errno in errors:
errtext = errors[err.errno]
else:
- errtext = force_text(err)
+ errtext = force_str(err)
raise CommandError(errtext)
self.stdout.write("OK")
diff --git a/lib/srtmain/settings.py b/lib/srtmain/settings.py
index 0607fe9a..abd115cb 100644
--- a/lib/srtmain/settings.py
+++ b/lib/srtmain/settings.py
@@ -22,8 +22,8 @@
# Django settings for SRT
import os
-
from django import VERSION as DJANGO_VERSION
+import yaml
DEBUG = True
@@ -40,27 +40,47 @@ ADMINS = (
MANAGERS = ADMINS
-SRT_SQLITE_DEFAULT_DIR = os.environ.get('SRT_BASE_DIR')
+SRT_BASE_DIR = os.environ.get('SRT_BASE_DIR', ".")
+with open(f"{SRT_BASE_DIR}/srt_dbconfig.yml", "r") as ymlfile:
+ SRT_DBCONFIG = yaml.safe_load(ymlfile)
+ SRT_DBSELECT = SRT_DBCONFIG['dbselect']
+ srt_dbconfig = SRT_DBCONFIG[SRT_DBSELECT]
+ srt_dbtype = srt_dbconfig['dbtype']
DATABASES = {
'default': {
- # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
'ENGINE': 'django.db.backends.sqlite3',
# DB name or full path to database file if using sqlite3.
- 'NAME': "%s/srt.sqlite" % SRT_SQLITE_DEFAULT_DIR,
+ 'NAME': f"{SRT_BASE_DIR}/{srt_dbconfig['path']}",
'USER': '',
'PASSWORD': '',
- #'HOST': '127.0.0.1', # e.g. mysql server
- #'PORT': '3306', # e.g. mysql port
},
# Sqlite database lock problem
'OPTIONS': {
'timeout': 20,
}
+} if srt_dbtype == "sqlite" else {
+ 'default': {
+ 'ENGINE': 'django.db.backends.mysql',
+ 'NAME': srt_dbconfig["name"],
+ 'USER': srt_dbconfig["user"],
+ 'PASSWORD': srt_dbconfig["passwd"],
+ 'HOST': srt_dbconfig["host"],
+ 'PORT': srt_dbconfig["port"],
+ },
+} if srt_dbtype == "mysql" else {
+ 'default': {
+ 'ENGINE': 'django.db.backends.postgresql_psycopg2',
+ 'NAME': srt_dbconfig["name"],
+ 'USER': srt_dbconfig["user"],
+ 'PASSWORD': srt_dbconfig["passwd"],
+ 'HOST': srt_dbconfig["host"],
+ 'PORT': srt_dbconfig["port"],
+ },
}
# Needed when Using sqlite especially to add a longer timeout for waiting
-# for the database lock to be released
+# for the database lock to be released
# https://docs.djangoproject.com/en/1.6/ref/databases/#database-is-locked-errors
if 'sqlite' in DATABASES['default']['ENGINE']:
DATABASES['default']['OPTIONS'] = { 'timeout': 20 }
@@ -180,7 +200,7 @@ TEMPLATES = [
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
- os.path.join(SRT_SQLITE_DEFAULT_DIR, 'lib/srtmain/templates'),
+ os.path.join(SRT_BASE_DIR, 'lib/srtmain/templates'),
],
'OPTIONS': {
'context_processors': [
@@ -278,7 +298,7 @@ INSTALLED_APPS = (
'srtgui',
'users',
)
-#print("DEBUG:INSTALLED_APPS:%s,%s" % (SRT_MAIN_APP,INSTALLED_APPS))
+##print("DEBUG:INSTALLED_APPS:%s,%s" % (SRT_MAIN_APP,INSTALLED_APPS))
INTERNAL_IPS = ['127.0.0.1', '192.168.2.28']
@@ -319,7 +339,7 @@ if os.environ.get('SRT_DEVEL', None) is not None:
SOUTH_TESTS_MIGRATE = False
-# We automatically detect and install applications here if
+# We automatically detect and install other applications here if
# they have a 'models.py' or 'views.py' file
import os
currentdir = os.path.dirname(__file__)
@@ -331,6 +351,7 @@ for t in os.walk(os.path.dirname(currentdir)):
if ("views.py" in t[2] or "models.py" in t[2]) and not modulename in INSTALLED_APPS:
INSTALLED_APPS = INSTALLED_APPS + (modulename,)
+##print("INSTALLED_APPS:%s" % ','.join(INSTALLED_APPS))
# A sample logging configuration. The only tangible logging
# performed by this configuration is to send an email to
@@ -401,3 +422,10 @@ EMAIL_FILE_PATH = os.path.join(SITE_ROOT, "sent_emails")
# Custom SRTool Users
AUTH_USER_MODEL = 'users.SrtUser'
+DEFAULT_AUTO_FIELD='django.db.models.AutoField'
+
+# SSL support (Django 4)
+SRT_CSRF_TRUSTED_ORIGINS = os.environ.get("SRT_CSRF_TRUSTED_ORIGINS", None)
+if SRT_CSRF_TRUSTED_ORIGINS is not None:
+ CSRF_TRUSTED_ORIGINS = SRT_CSRF_TRUSTED_ORIGINS.split(',')
+
diff --git a/lib/srtmain/urls.py b/lib/srtmain/urls.py
index 2f330154..6bdc1581 100644
--- a/lib/srtmain/urls.py
+++ b/lib/srtmain/urls.py
@@ -22,7 +22,7 @@
import os
from django import VERSION as DJANGO_VERSION
-from django.conf.urls import url
+from django.urls import re_path as url
from django.views.generic import RedirectView, TemplateView
from django.views.decorators.cache import never_cache
@@ -39,6 +39,9 @@ logger = logging.getLogger("srt")
from django.contrib import admin
admin.autodiscover()
+# Fetch the main app URL
+SRT_MAIN_APP = os.environ.get('SRT_MAIN_APP', 'srtgui')
+
urlpatterns = [
# Examples:
@@ -50,7 +53,7 @@ urlpatterns = [
url(r'^health$', TemplateView.as_view(template_name="health.html"), name='Toaster Health'),
# if no application is selected, we have the magic srtgui app here
- url(r'^$', never_cache(RedirectView.as_view(url='/srtgui/', permanent=True))),
+ url(r'^$', never_cache(RedirectView.as_view(url='/'+SRT_MAIN_APP+'/', permanent=True)), name='Default URL=/'+SRT_MAIN_APP+'/'),
]
import srtmain.settings
@@ -76,12 +79,12 @@ if DJANGO_VERSION >= (2,0):
# Uncomment the next lines to enable the admin:
path('admin/', admin.site.urls),
- # Main application
- path(SRT_MAIN_APP + '/', include(SRT_MAIN_APP + '.urls')),
# Default applications
path('srtgui/', include('srtgui.urls')),
path('users/', include('users.urls')),
path('users/', include('django.contrib.auth.urls')),
+ # Main application
+ path(SRT_MAIN_APP + '/', include(SRT_MAIN_APP + '.urls')),
] + urlpatterns
else:
urlpatterns = [
@@ -96,7 +99,26 @@ else:
url('^' + SRT_MAIN_APP + '/', include(SRT_MAIN_APP + '.urls')),
] + urlpatterns
-#print("DEBUG:INSTALLED_URL_PATTERNS:%s,%s" % (SRT_MAIN_APP,urlpatterns))
+
+# We automatically detect and install other applications here
+# (at a lower precedence) if they have a 'urls.py'
+currentdir = os.path.dirname(__file__)
+urlpatterns_str = str(urlpatterns)
+for t in os.walk(os.path.dirname(currentdir)):
+ modulename = os.path.basename(t[0])
+ if 'srtmain' == modulename:
+ # Avoid infinite recursion
+ continue
+ if "urls.py" in t[2]:
+ found = False
+ for url in urlpatterns:
+ if modulename+"/urls.py" in str(url):
+ found = True
+ if not found:
+# urlpatterns.append(path(modulename + '/', include(modulename + '.urls')))
+ urlpatterns.insert(0,path(modulename + '/', include(modulename + '.urls')))
+
+##print("DEBUG:INSTALLED_URL_PATTERNS:%s,%s" % (SRT_MAIN_APP,urlpatterns))
currentdir = os.path.dirname(__file__)
diff --git a/lib/srtmain/wsgi.py b/lib/srtmain/wsgi.py
index 6b468e4f..42259a79 100644
--- a/lib/srtmain/wsgi.py
+++ b/lib/srtmain/wsgi.py
@@ -17,12 +17,29 @@ framework.
"""
import os
+from dotenv import load_dotenv
# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
# if running multiple sites in the same mod_wsgi process. To fix this, use
# mod_wsgi daemon mode with each site in its own daemon process, or use
# os.environ["DJANGO_SETTINGS_MODULE"] = "Toaster.settings"
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "srtmain.settings")
+SRT_BASE_DIR = os.environ.get('SRT_BASE_DIR','.')
+PIDFILE = os.environ.get('PIDFILE','.')
+
+# quick development/debugging support
+def _log(msg):
+ f1=open(f"{SRT_BASE_DIR}/gunicorn_env.txt", 'w')
+ f1.write("|" + msg + "|\n" )
+ f1.close()
+
+# Spawn the updater, if not already running
+_log(str(os.environ))
+is_update_pid = os.path.isfile(f"{SRT_BASE_DIR}/.srtupdate.pid")
+if False and SRT_BASE_DIR and PIDFILE and (not is_update_pid):
+ cmnd = [f"{SRT_BASE_DIR}/bin/srt","start_update",f"update_follow_pid={PIDFILE}"]
+ _log(f"COMMAND:{cmnd}")
+ os.spawnv(os.P_NOWAIT, cmnd[0], cmnd)
# This application object is used by any WSGI server configured to use this
# file. This includes Django's development server, if the WSGI_APPLICATION
diff --git a/lib/users/migrations/0002_last_name.py b/lib/users/migrations/0002_last_name.py
new file mode 100644
index 00000000..11560e6a
--- /dev/null
+++ b/lib/users/migrations/0002_last_name.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.11 on 2020-11-25 05:02
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('users', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='srtuser',
+ name='last_name',
+ field=models.CharField(blank=True, max_length=150, verbose_name='last name'),
+ ),
+ ]
diff --git a/lib/users/migrations/0003_srtuser_timezone.py b/lib/users/migrations/0003_srtuser_timezone.py
new file mode 100644
index 00000000..d69f62d6
--- /dev/null
+++ b/lib/users/migrations/0003_srtuser_timezone.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.20 on 2021-05-07 18:18
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('users', '0002_last_name'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='srtuser',
+ name='timezone',
+ field=models.CharField(blank=True, max_length=32),
+ ),
+ ]
diff --git a/lib/users/migrations/0004_timezone_default.py b/lib/users/migrations/0004_timezone_default.py
new file mode 100755
index 00000000..1d53468c
--- /dev/null
+++ b/lib/users/migrations/0004_timezone_default.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2.11 on 2021-10-31 02:24
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('users', '0003_srtuser_timezone'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='srtuser',
+ name='timezone',
+ field=models.CharField(blank=True, default='US/Pacific', max_length=32, null=True),
+ ),
+ ]
diff --git a/lib/users/migrations/0005_alter_srtuser_first_name.py b/lib/users/migrations/0005_alter_srtuser_first_name.py
new file mode 100644
index 00000000..9aa275ba
--- /dev/null
+++ b/lib/users/migrations/0005_alter_srtuser_first_name.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.0 on 2023-01-30 18:58
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('users', '0004_timezone_default'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='srtuser',
+ name='first_name',
+ field=models.CharField(blank=True, max_length=150, verbose_name='first name'),
+ ),
+ ]
diff --git a/lib/users/models.py b/lib/users/models.py
index b59f9fee..60391303 100755
--- a/lib/users/models.py
+++ b/lib/users/models.py
@@ -8,6 +8,7 @@ from srtgui.api import _log
class SrtUser(AbstractUser):
# add additional fields in here
role = models.CharField(max_length=128, verbose_name='security role')
+ timezone = models.CharField(max_length=32, default='US/Pacific', null=True, blank=True)
def __str__(self):
return "%s,%s" % (self.email,self.role)
@@ -69,9 +70,84 @@ class SrtUser(AbstractUser):
group.user_set.add(self)
return group.name
return ",".join(groups)
+ @staticmethod
+ def get_people_users():
+ names_to_exclude = ['admin','Guest','SRTool','All']
+ return(SrtUser.objects.exclude(username__in=names_to_exclude))
+ @staticmethod
+ def get_group_users(group_name):
+ try:
+ group = Group.objects.get(name=group_name)
+ except:
+ return([])
+ return([user for user in group.user_set.all()])
@property
def get_group_perm(self):
return self.get_group_permissions()
+ DEFAULT_TIMEZONE_INDEX = 21
+ @staticmethod
+ def get_timezone_list():
+ #timezone_list.split(':') = [GMT offset][pytz entry(key:can use ezlookup against pytz list)][Example cities/regions]
+ timezone_list = [
+ '+14:Pacific/Kiritimati:',
+ '+13:Pacific/Apia:',
+ '+12:Pacific/Auckland:NZ/Standard',
+ '+11:Pacific/Guadalcanal:SB/Standard RU/Magadan',
+ '+10:Pacific/Port_Moresby:PG/Standard RU/Vladivostok',
+ '+09:Asia/Tokyo:JP/Standard RU/Yakutsk ID/Jakarta',
+ '+08:Asia/Singapore:SG/Standard HK/Standard MN/Ulanbaatar',
+ '+07:Asia/Bangkok:TH/Bangkok RU/Krasnoyarsk',
+ '+06:Asia/Dhaka:BD/Dhaka RU/Omsk',
+ '+05:Asia/Karachi:PK/Karachi RU/Yekaterinburg',
+ '+04:Asia/Dubai:AE/Dubai MU/Standard RU/Samara',
+ '+03:Europe/Moscow:RU/Moscow KE/Nairobi',
+ '+02:Europe/Berlin:RU/Kaliningrad EG/Cairo RW/Kigali',
+ '+01:Europe/Dublin:NG/Lagos IE/Dublin',
+ '+00:Atlantic/Reykjavik:LR/Monrovia PT/Azores MA/Casablancas',
+ '-01:Atlantic/Cape_Verde:CV/Praia',
+ '-02:Etc/GMT-2:',
+ '-03:America/Argentina/Buenos_Aires:AR/Buenos Aires SR/Commewijne',
+ '-04:US/Eastern:US/New York City PR/San Juan',
+ '-05:US/Central:US/Houston MX/Mexico City',
+ '-06:US/Mountain:Denver CA/Edmonton',
+ '-07:US/Pacific:Los Angeles CA/Vancouver MX/Tijuana',
+ '-08:Pacific/Pitcairn:PN/Adamstown',
+ '-09:America/Anchorage:US/Anchorage',
+ '-10:Pacific/Tahiti:PF/Tahiti',
+ '-11:Pacific/Pago_Pago:AS/Pago Pago US/Midway',
+ '-12:Pacific/Kwajalein:',
+ ]
+ return timezone_list
+
+ # Set user timezone string from long string
+ def map_usertz_str_to_usertz(self, long_str):
+ short_timezone = ""
+ for tz in SrtUser.get_timezone_list():
+ if tz == long_str:
+ short_timezone = tz.split(':')[1]
+ break
+ if not short_timezone:
+ short_timezone = SrtUser.get_timezone_list[SrtUser.DEFAULT_TIMEZONE_INDEX].split(':')[1]
+ self.timezone = short_timezone
+ return self.timezone
+
+ # Return long string format
+ def map_usertz_to_usertz_str(self):
+ for tz in SrtUser.get_timezone_list():
+ if self.timezone == tz.split(':')[1]:
+ return tz
+ break
+ return self.get_timezone_list()[SrtUser.DEFAULT_TIMEZONE_INDEX]
+
+ # Return offset from UTC -> jobtags.py
+ @property
+ def get_timezone_offset(self):
+ for tz in SrtUser.get_timezone_list():
+ if self.timezone == tz.split(':')[1]:
+ return int(tz.split(':')[0])
+ break
+ return int(self.get_timezone_list()[SrtUser.DEFAULT_TIMEZONE_INDEX].split(':')[0])
+
# Minimal and safe User object to pass to web pages (no passwords)
class UserSafe():
diff --git a/lib/users/templates/user_edit.html b/lib/users/templates/user_edit.html
index 26b18ea8..4d57ac97 100755
--- a/lib/users/templates/user_edit.html
+++ b/lib/users/templates/user_edit.html
@@ -1,8 +1,11 @@
<!-- templates/signup.html -->
{% extends 'base.html' %}
-{% block title %}{% if 'new_admin' == mode %}New User{% else %}Edit User Settings{% endif %}{% endblock %}
+{% load static %}
+{% load jobtags %}
+{% load humanize %}
+{% block title %}{% if 'new_admin' == mode %}New User{% else %}Edit User Settings{% endif %}{% endblock %}
{% block pagecontent %}
<div>
<h2>{% if 'new_admin' == mode %}New User{% else %}Edit User Settings{% endif %}</h2>
@@ -32,6 +35,16 @@
<dt>Role:</dt>
<dd><input type="text" placeholder="Edit role" name="user_role" size="80" value="{{user_role}}"></dd>
+ <!--Insert tz dropdown here-->
+ <dt>Timezone:</dt>
+ <dd>
+ <select name="timezone" id="select-timezone">
+ {% for tz in timezone_list %}
+ <option value="{{tz}}" {% if user_timezone == tz %}selected{% endif %}>{{tz}}</option>
+ {% endfor %}
+ </select>
+ </dd>
+
<dt>Group:</dt>
<dd>
{% if 'edit_user' == mode %}
@@ -42,6 +55,7 @@
<option value="Contributor" {% if 'Contributor' == group_name %}selected{% endif %}>Contributor</option>
<option value="Creator" {% if 'Creator' == group_name %}selected{% endif %}>Creator</option>
<option value="Admin" {% if 'Admin' == group_name %}selected{% endif %}>Admin</option>
+ <option value="SuperUser" {% if user_super %}selected{% endif %}>SuperUser</option>
</select>
{% endif %}
</dd>
diff --git a/lib/users/urls.py b/lib/users/urls.py
index 4c33cb18..f936d1d0 100755
--- a/lib/users/urls.py
+++ b/lib/users/urls.py
@@ -1,4 +1,4 @@
-from django.conf.urls import include, url
+from django.urls import re_path as url, include
from . import views
urlpatterns = [
@@ -9,6 +9,6 @@ urlpatterns = [
url(r'^edit_user/(?P<user_pk>\d+)$', views.edit_user, name="edit_user"),
url(r'^xhr_user_commit/$', views.xhr_user_commit, name='xhr_user_commit'),
-
+ url(r'^xhr_date_time_test/$', views.xhr_date_time_test, name='xhr_date_time_test'),
]
diff --git a/lib/users/views.py b/lib/users/views.py
index 62163822..c5cce77d 100755
--- a/lib/users/views.py
+++ b/lib/users/views.py
@@ -103,6 +103,9 @@ def edit_user(request,user_pk):
'user_last' : '' if not pk else srtuser.last_name,
'user_email' : '' if not pk else srtuser.email,
'user_role' : '' if not pk else srtuser.role,
+ 'user_super' : False if not pk else srtuser.is_superuser,
+ 'user_timezone' : SrtUser.get_timezone_list()[SrtUser.DEFAULT_TIMEZONE_INDEX] if not pk else srtuser.map_usertz_to_usertz_str(),
+ 'timezone_list': SrtUser.get_timezone_list(),
'group_name' : 'Reader' if not pk else srtuser.get_groups.split(',')[0],
'validation_errors' : '',
}
@@ -115,12 +118,14 @@ def edit_user(request,user_pk):
else:
return redirect('/')
+ # added user_tz to POST method
mode = request.POST.get('mode', '')
user_name = request.POST.get('user_name', '')
user_first = request.POST.get('user_first', '')
user_last = request.POST.get('user_last', '')
user_email = request.POST.get('user_email', '')
user_role = request.POST.get('user_role', '')
+ user_tz = request.POST.get('timezone', '')
user_group = request.POST.get('user_group', '')
user_pass1 = request.POST.get('user_pass1', '')
user_pass2 = request.POST.get('user_pass2', '')
@@ -151,12 +156,18 @@ def edit_user(request,user_pk):
'user_last' : user_last,
'user_email' : user_email,
'user_role' : user_role,
+ 'user_tz' : user_tz,
'group_name' : user_group,
'validation_errors' : validation_errors[2:],
}
return render(request, 'user_edit.html', context)
# Process the post
+ if 'SuperUser' == user_group:
+ user_group = 'Admin'
+ is_superuser = True
+ else:
+ is_superuser = False
if 'new_admin' == mode:
srtuser = SrtUser(username=user_name)
else:
@@ -168,6 +179,8 @@ def edit_user(request,user_pk):
srtuser.last_name = user_last
srtuser.email = user_email
srtuser.role = user_role
+ srtuser.is_superuser = is_superuser
+ srtuser.timezone = srtuser.map_usertz_str_to_usertz(user_tz)
srtuser.save()
# Update Group
if user_group and (user_group != srtuser.get_groups.split(',')[0]):
@@ -198,14 +211,70 @@ def xhr_user_commit(request):
action = request.POST['action']
history_comment = ''
try:
+ error_message = "ok";
if 'submit-trashuser' == action:
record_id = request.POST['record_id']
user = SrtUser.objects.get(pk=record_id).delete()
+
+ elif 'submit-trashgroup' == action:
+ record_id = request.POST['record_id']
+ group = Group.objects.get(pk=record_id).delete()
+
+ elif 'submit-trashusergroup' == action:
+ group_id = int(request.POST.get('group_id','0'))
+ record_id = request.POST['record_id']
+ group = Group.objects.get(pk=group_id)
+ srtuser = SrtUser.objects.get(pk=record_id)
+ group.user_set.remove(srtuser)
+ ret=group.save()
+
+ elif 'submit-group-users' == action:
+ group_id = int(request.POST.get('group_id','0'))
+ user_id_list = request.POST['user_id_list']
+ group = Group.objects.get(pk=group_id)
+ # Add new users
+ for user_id in user_id_list.split(','):
+ if user_id:
+ srtuser = SrtUser.objects.get(id=int(user_id))
+ group.user_set.add(srtuser)
+ group.save()
+ # Remove old users
+ for srtuser in group.user_set.all():
+ if not str(srtuser.id) in user_id_list:
+ group.user_set.remove(srtuser)
+ group.save()
+
+ elif 'submit-group-create' == action:
+ group_name = request.POST['group_name'].strip()
+ group,created = Group.objects.get_or_create(name=group_name)
+ group.save()
+
+ else:
+ error_message = "ERROR:unknown action '%s'" % request.POST["action"]
+
return_data = {
- "error": "ok",
+ "error": error_message,
}
return HttpResponse(json.dumps( return_data ), content_type = "application/json")
except Exception as e:
_log("xhr_user_commit:no(%s)" % e)
return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
+
+def xhr_date_time_test(request):
+ _log("xhr_date_time_test(%s)" % request.POST)
+ if not 'action' in request.POST:
+ return HttpResponse(json.dumps({"error":"missing action\n"}), content_type = "application/json")
+
+ action = request.POST['action']
+ history_comment = ''
+ try:
+ if 'submit-timezone' == action:
+ timezone = request.POST['timezone']
+ return_data = {
+ "error": "ok",
+ }
+ return HttpResponse(json.dumps( return_data ), content_type = "application/json")
+ except Exception as e:
+ _log("xhr_date_time_test:no(%s)" % e)
+ return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
diff --git a/lib/yp/reports.py b/lib/yp/reports.py
index dca99d10..565c2dc0 100755
--- a/lib/yp/reports.py
+++ b/lib/yp/reports.py
@@ -35,11 +35,11 @@ from srtgui.reports import Report, ReportManager, ProductsReport, ManagementRepo
from django.db.models import Q, F
from django.db import Error
-from srtgui.templatetags.projecttags import filtered_filesizeformat
+from srtgui.templatetags.jobtags import filtered_filesizeformat
logger = logging.getLogger("srt")
-SRT_BASE_DIR = os.environ['SRT_BASE_DIR']
+SRT_BASE_DIR = os.environ.get('SRT_BASE_DIR', '.')
SRT_REPORT_DIR = '%s/reports' % SRT_BASE_DIR
# quick development/debugging support
diff --git a/lib/yp/templates/landing.html b/lib/yp/templates/landing.html
new file mode 100755
index 00000000..0a488398
--- /dev/null
+++ b/lib/yp/templates/landing.html
@@ -0,0 +1,93 @@
+{% extends "base.html" %}
+
+{% load static %}
+{% load jobtags %}
+{% load humanize %}
+
+{% block title %} Welcome to SRTool{% endblock %}
+{% block pagecontent %}
+ <div class="row">
+ <div class="col-md-7" style="padding-left: 50px;">
+ <h1>Security Response Tool (SRTool)</h1>
+ <p>A web interface to SRTool CVE investigations ({{this_landing}})</p>
+ </div>
+ </div>
+ <div class="row">
+ <div class="jumbotron well-transparent">
+
+ <div class="col-md-6">
+ <div>
+ <table class="table table-striped table-condensed" data-testid="landing-hyperlinks-table">
+ <thead>
+ <tr>
+ <th>Table</th>
+ <th>Description</th>
+ </tr>
+ </thead>
+
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'cvechecker_audits' %}">CVE Check Audits</a></td>
+ <td>CVE Check Audits</td>
+ </tr>
+
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'cves' %}">CVE's</a></td>
+ <td>Common Vulnerability Enumeration</td>
+ </tr>
+
+<!-- <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'investigations' %}">Investigations</a></td>
+ <td>SRTool Investigations (product level)</td>
+ </tr>
+
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'defects' %}">Defects</a></td>
+ <td>SRTool Defects</td>
+ </tr>
+
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'cpes_srtool' %}">Package CPE's</a></td>
+ <td>Affected packages (Common Platform Enumeration)</td>
+ </tr>
+
+-->
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'cwes' %}">CWE's</a></td>
+ <td>Common Weakness Enumeration</td>
+ </tr>
+
+ <tr>
+ <td> <a class="btn btn-info btn-lg" href="{% url 'products' %}">Products</a></td>
+ <td>SRTool Products<td>
+ </tr>
+
+ {% for ext_url,ext_title,ext_description in landing_extensions_table %}
+ <tr>
+ <td> <a class="btn btn-info btn-lg" href="{% url ext_url %}">{{ext_title}}</a></td>
+ <td>{{ext_description}}<td>
+ </tr>
+ {% endfor %}
+
+ {% if request.user.is_creator %}
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'manage' %}">Management</a></td>
+ <td>Triage CVE's, Create Vulnerabilities, Manage Users</td>
+ </tr>
+ {% endif %}
+
+ </table>
+ </div>
+
+ </div>
+
+ <div class="col-md-6">
+ <div align="center"><a class="btn btn-primary btn-lg" href="{% url 'guided_tour' %}">Click here to take a Guided Tour!</a></div>
+ <p />
+ <p />
+ <img alt="CVE preview" class="img-thumbnail" src="{% static 'img/cve_splash.png' %}"/>
+ </div>
+
+ </div>
+ </div>
+
+{% endblock %}
diff --git a/lib/yp/templates/management.html b/lib/yp/templates/management.html
new file mode 100755
index 00000000..013a9d87
--- /dev/null
+++ b/lib/yp/templates/management.html
@@ -0,0 +1,199 @@
+{% extends "base.html" %}
+
+{% load static %}
+{% load jobtags %}
+{% load humanize %}
+
+{% block title %} Manage Resources {% endblock %}
+{% block pagecontent %}
+
+<div class="row">
+ <!-- Breadcrumbs -->
+ <div class="col-md-12">
+ <ul class="breadcrumb" id="breadcrumb">
+ <li><a href="{% url 'landing' %}">Home</a></li><span class="divider">&rarr;</span>
+ <li>Management</a>
+ </ul>
+ </div>
+</div>
+
+<div class="row">
+ <div class="col-md-7" style="padding-left: 50px;">
+ <h1>Management</h1>
+ </div>
+</div>
+
+<div class="row">
+ <div class="col-md-12">
+ {% with mru=mru %}
+ {% include 'mrj_section.html' %}
+ {% endwith %}
+ </div>
+</div>
+
+<div class="row">
+ <div class="jumbotron well-transparent">
+
+ <div class="col-md-6">
+ <div>
+ <table class="table table-striped table-condensed" data-testid="landing-hyperlinks-table">
+ <thead>
+ <tr>
+ <th>Action</th>
+ <th>Description</th>
+ </tr>
+ </thead>
+
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'cvechecker_import_manager' %}">Import Manager</a></td>
+ <td>Manage the CVE Check report import modes</td>
+ </tr>
+
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'triage_cves' %}">Triage CVE's</a></td>
+ <td>Triage the CVE's ({{cve_new}})</td>
+ </tr>
+
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'manage_notifications' %}">Pending notifications</a></td>
+ <td>Triage the pending notifications ({{notification_total}})</td>
+ </tr>
+
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'manage_report' %}">Summary Report</a></td>
+ <td>Report on the over all response system status</td>
+ </tr>
+
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'publish' %}">Publish Reports</a></td>
+ <td>Process items to be published from the SRTool</td>
+ </tr>
+
+ {% if request.user.is_admin %}
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'users' %}">Manage Users</a></td>
+ <td>Add, edit, and remove users</td>
+ </tr>
+
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'sources' %}?nocache=1">Manage Sources</a></td>
+ <td>Manage source list, perform manual pulls</td>
+ </tr>
+
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'maintenance' %}?nocache=1">Maintenance</a></td>
+ <td>Maintenance utilities ({{errorlog_total}})</td>
+ </tr>
+
+ <tr>
+ <td><a class="btn btn-info btn-lg" href="{% url 'cvechecker_clear_jobs' %}">Clear Jobs</a></td>
+ <td>Clear the Jobs table of all entries</td>
+ </tr>
+
+ {% endif %}
+
+ </table>
+ </div>
+
+ </div>
+
+ <div class="col-md-5">
+ <b>Quick Info</b>
+ <div class="well">
+ <dl class="dl-horizontal">
+ <dt>CVE's: Total Count =</dt>
+ <dd>
+ <a href="{% url 'cves' %}"> {{cve_total}} </a>
+ </dd>
+ <dt>Pending triaged =</dt>
+ <dd>
+ <a href="{% url 'cves' %}?limit=25&page=1&orderby=name&filter=is_status:new&default_orderby=name&filter_value=on&"> {{cve_new}} </a>
+ </dd>
+ <dt>Investigate =</dt>
+ <dd>
+ <a href="{% url 'cves' %}?limit=25&page=1&orderby=name&filter=is_status:investigate&default_orderby=name&filter_value=on&"> {{cve_investigate}} </a>
+ </dd>
+ <dt>Vulnerable =</dt>
+ <dd>
+ <a href="{% url 'cves' %}?limit=25&page=1&orderby=name&filter=is_status:vulnerable&default_orderby=name&filter_value=on&"> {{cve_vulnerable}} </a>
+ </dd>
+ <dt>Not Vulnerable =</dt>
+ <dd>
+ <a href="{% url 'cves' %}?limit=25&page=1&orderby=name&filter=is_status:not_vulnerable&default_orderby=name&filter_value=on&"> {{cve_not_vulnerable}} </a>
+ </dd>
+ <dt>Vulnerabilities: Total Count =</dt>
+ <dd>
+ <a href="{% url 'vulnerabilities' %}"> {{vulnerability_total}} </a>
+ </dd>
+ <dt>Open =</dt>
+ <dd>
+ <a href="{% url 'vulnerabilities' %}?limit=25&page=1&orderby=name&filter=is_outcome:open&default_orderby=name&filter_value=on&"> {{vulnerability_open}} </a>
+ </dd>
+ <dt>Critical active =</dt>
+ <dd>
+ <a href="{% url 'vulnerabilities' %}?limit=25&page=1&orderby=name&filter=is_priority:critical&default_orderby=name&filter_value=on&" %}> {{vulnerability_critical}} </a>
+ </dd>
+ <dt>High active =</dt>
+ <dd>
+ <a href="{% url 'vulnerabilities' %}?limit=25&page=1&orderby=name&filter=is_priority:high&default_orderby=name&filter_value=on&" %}> {{vulnerability_high}} </a>
+ </dd>
+ <dt>Medium active =</dt>
+ <dd>
+ <a href="{% url 'vulnerabilities' %}?limit=25&page=1&orderby=name&filter=is_priority:medium&default_orderby=name&filter_value=on&" %}> {{vulnerability_medium}} </a>
+ </dd>
+
+ <dt>Investigations: Total Count =</dt>
+ <dd>
+ <a href="{% url 'investigations' %}" %}> {{investigation_total}} </a>
+ </dd>
+ <dt>Open =</dt>
+ <dd>
+ <a href="{% url 'investigations' %}?limit=25&page=1&orderby=name&filter=is_outcome:open&default_orderby=name&filter_value=on&" %}> {{investigation_open}} </a>
+ </dd>
+ <dt>Critical active =</dt>
+ <dd>
+ <a href="{% url 'investigations' %}?limit=25&page=1&orderby=name&filter=is_priority:critical&default_orderby=name&filter_value=on&" %}> {{investigation_critical}} </a>
+ </dd>
+ <dt>High active =</dt>
+ <dd>
+ <a href="{% url 'investigations' %}?limit=25&page=1&orderby=name&filter=is_priority:high&default_orderby=name&filter_value=on&" %}> {{investigation_high}} </a>
+ </dd>
+ <dt>Medium active =</dt>
+ <dd>
+ <a href="{% url 'investigations' %}?limit=25&page=1&orderby=name&filter=is_priority:medium&default_orderby=name&filter_value=on&" %}> {{investigation_medium}} </a>
+ </dd>
+
+ <dt>Defects: Total Count =</dt>
+ <dd>
+ <a href="{% url 'defects' %}" %}> {{defect_total}} </a>
+ </dd>
+ <dt>Open =</dt>
+ <dd>
+ <a href="{% url 'defects' %}?limit=25&page=1&orderby=-priority&filter=is_srt_outcome:open&default_orderby=name&filter_value=on&" %}> {{defect_open}} </a>
+ </dd>
+ <dt>InProgress =</dt>
+ <dd>
+ <a href="{% url 'defects' %}?limit=25&page=1&orderby=-priority&filter=is_defect_status:in_progress&default_orderby=name&filter_value=on&" %}> {{defect_inprogress}} </a>
+ </dd>
+ <dt>P1 active =</dt>
+ <dd>
+ <a href="{% url 'defects' %}?limit=25&page=1&orderby=-priority&filter=is_defect_priority:critical&default_orderby=name&filter_value=on&" %}> {{defect_p1}} </a>
+ </dd>
+ <dt>P2 active =</dt>
+ <dd>
+ <a href="{% url 'defects' %}?limit=25&page=1&orderby=-priority&filter=is_defect_priority:high&default_orderby=name&filter_value=on&" %}> {{defect_p2}} </a>
+ </dd>
+
+ <dt>Packages: Affected=</dt>
+ <dd>
+ <a href="{% url 'cpes_srtool' %}" %}> {{package_total}} </a>
+ </dd>
+
+ </dl>
+ </div>
+ </div>
+
+ </div>
+</div>
+
+{% endblock %}
diff --git a/lib/yp/templates/yp_hello.html b/lib/yp/templates/yp_hello.html
index 15ab9a7b..95ee8e43 100755
--- a/lib/yp/templates/yp_hello.html
+++ b/lib/yp/templates/yp_hello.html
@@ -1,7 +1,7 @@
{% extends "base.html" %}
{% load static %}
-{% load projecttags %}
+{% load jobtags %}
{% load humanize %}
{% block title %} Yocto Project {% endblock %}
diff --git a/lib/yp/urls.py b/lib/yp/urls.py
index 494de9ae..d60360b7 100755
--- a/lib/yp/urls.py
+++ b/lib/yp/urls.py
@@ -1,12 +1,14 @@
-from django.conf.urls import include, url
+from django.urls import re_path as url,include
+from django.views.generic import RedirectView
from . import views
urlpatterns = [
- url(r'^hello/$', views.yp_hello, name='yp_hello'),
-
- url(r'^$', views.yp_hello, name='yp_default'),
+ # landing page
+ url(r'^landing/$', views.landing, name='landing'),
url(r'^report/(?P<page_name>\D+)$', views.report, name='report'),
url(r'^manage_report/$', views.manage_report, name='manage_report'),
+ # default redirection
+ url(r'^$', RedirectView.as_view(url='landing', permanent=True)),
]
diff --git a/lib/yp/views.py b/lib/yp/views.py
index 3310e7e6..6f722479 100755
--- a/lib/yp/views.py
+++ b/lib/yp/views.py
@@ -19,12 +19,15 @@
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+import os
+
#from django.urls import reverse_lazy
#from django.views import generic
from django.http import HttpResponse, HttpResponseNotFound, JsonResponse, HttpResponseRedirect
from django.shortcuts import render, redirect
from users.models import SrtUser, UserSafe
+from orm.models import SrtSetting
from srtgui.views import MimeTypeFinder
from yp.reports import YPReportManager
@@ -33,10 +36,29 @@ from yp.reports import YPReportManager
# quick development/debugging support
from srtgui.api import _log
-def yp_hello(request):
- context = {}
- _log("Note:yp_hello")
- return render(request, 'yp_hello.html', context)
+SRT_BASE_DIR = os.environ.get('SRT_BASE_DIR', '.')
+SRT_MAIN_APP = os.environ.get('SRT_MAIN_APP', '.')
+
+# determine in which mode we are running in, and redirect appropriately
+def landing(request):
+
+ # Django sometimes has a race condition with this view executing
+ # for the master app's landing page HTML which can lead to context
+ # errors, so hard enforce the default re-direction
+ if SRT_MAIN_APP and (SRT_MAIN_APP != "yp"):
+ return redirect(f"/{SRT_MAIN_APP}/landing/")
+
+ # Append the list of landing page extensions
+ landing_extensions_table = []
+ for landing_extension in SrtSetting.objects.filter(name__startswith='LANDING_LINK').order_by('name'):
+ landing_extensions_table.append(landing_extension.value.split('|'))
+
+ context = {
+ 'landing_extensions_table' : landing_extensions_table,
+ 'this_landing' : 'yp',
+ }
+
+ return render(request, 'landing.html', context)
def report(request,page_name):
if request.method == "GET":