feat(ui): Show inventory details if they exist

!8 closes #2
This commit is contained in:
2024-05-20 16:45:32 +09:30
parent c52fd0802e
commit e93ce07d88
14 changed files with 295 additions and 43 deletions

View File

@ -1,6 +1,8 @@
from django import forms
from django.db.models import Q
from app import settings
from itam.models.device import DeviceOperatingSystem
@ -16,3 +18,13 @@ class Update(forms.ModelForm):
'operating_system_version',
]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['_created'] = forms.DateTimeField(
label="Install Date",
input_formats=settings.DATETIME_FORMAT,
initial=kwargs['instance'].installdate,
disabled=True
)

View File

@ -0,0 +1,38 @@
# Generated by Django 5.0.6 on 2024-05-20 06:36
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('itam', '0005_alter_operatingsystemversion_name_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='devicesoftware',
options={'ordering': ['-action', 'software']},
),
migrations.AddField(
model_name='deviceoperatingsystem',
name='installdate',
field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='Install Date'),
),
migrations.AddField(
model_name='devicesoftware',
name='installed',
field=models.DateTimeField(blank=True, null=True, verbose_name='Install Date'),
),
migrations.AddField(
model_name='devicesoftware',
name='installedversion',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='installedversion', to='itam.softwareversion'),
),
migrations.AlterField(
model_name='devicesoftware',
name='action',
field=models.CharField(blank=True, choices=[('1', 'Install'), ('0', 'Remove')], default=None, max_length=1, null=True),
),
]

View File

@ -89,25 +89,27 @@ class Device(DeviceCommonFieldsName):
}
for software in softwares:
if software.action:
if int(software.action) == 1:
if int(software.action) == 1:
state = 'present'
state = 'present'
elif int(software.action) == 0:
elif int(software.action) == 0:
state = 'absent'
state = 'absent'
software_action = {
"name": software.software.slug,
"state": state
}
software_action = {
"name": software.software.slug,
"state": state
}
if software.version:
software_action['version'] = software.version.name
if software.version:
software_action['version'] = software.version.name
config['software'] = config['software'] + [ software_action ]
config['software'] = config['software'] + [ software_action ]
return config
@ -116,6 +118,12 @@ class Device(DeviceCommonFieldsName):
class DeviceSoftware(DeviceCommonFields):
""" A way for the device owner to configure software to install/remove """
class Meta:
ordering = [
'-action',
'software'
]
class Actions(models.TextChoices):
INSTALL = '1', 'Install'
@ -128,7 +136,6 @@ class DeviceSoftware(DeviceCommonFields):
default = None,
null = False,
blank= False
)
software = models.ForeignKey(
@ -137,13 +144,14 @@ class DeviceSoftware(DeviceCommonFields):
default = None,
null = False,
blank= False
)
action = models.CharField(
max_length=1,
choices=Actions,
default=None,
null=True,
blank = True,
)
version = models.ForeignKey(
@ -152,7 +160,22 @@ class DeviceSoftware(DeviceCommonFields):
default = None,
null = True,
blank= True
)
installedversion = models.ForeignKey(
SoftwareVersion,
related_name = 'installedversion',
on_delete=models.CASCADE,
default = None,
null = True,
blank= True
)
installed = models.DateTimeField(
verbose_name = 'Install Date',
null = True,
blank = True
)
@ -184,3 +207,10 @@ class DeviceOperatingSystem(DeviceCommonFields):
null = False,
blank = False,
)
installdate = models.DateTimeField(
verbose_name = 'Install Date',
null = True,
blank = True,
default = None,
)

View File

@ -68,13 +68,16 @@
<div id="Software" class="tabcontent">
<h3>Software</h3>
<hr>
Installed Software: {{ installed_software }}
<input type="button" value="Add Software Action" onclick="window.location='{% url 'ITAM:_device_software_add' device.id %}';">
<table>
<thead>
<th>Name</th>
<th>Category</th>
<th>Action</th>
<th>Version</th>
<th>Desired Version</th>
<th>Installed Version</th>
<th>Installed</th>
<th>&nbsp;</th>
</thead>
@ -82,22 +85,44 @@
{% for software in softwares %}
<tr>
<td><a href="{% url 'ITAM:_software_view' pk=software.software_id %}">{{ software.software }}</a></td>
<td><a href="{% url 'ITAM:_device_software_view' device_id=device.id pk=software.id %}">{{ software.get_action_display }}</a></td>
<td>{{ software.software.category }}</td>
<td>
{% if software.version %}
{{ software.version }}
{% url 'ITAM:_device_software_view' device_id=device.id pk=software.id as icon_link %}
{% if software.get_action_display == 'Install' %}
{% include 'icons/success_text.html.j2' with icon_text=software.get_action_display icon_link=icon_link %}
{% elif software.get_action_display == 'Remove'%}
{% include 'icons/cross_text.html.j2' with icon_text=software.get_action_display icon_link=icon_link %}
{% else %}
Any
{% include 'icons/add_link.html.j2' with icon_text='Add' icon_link=icon_link %}
{% endif %}
</td>
<td>
{% include 'icons/issue_link.html.j2' with issue=2 %}
{% if software.version %}
{{ software.version }}
{% else %}
-
{% endif %}
</td>
<td>
{% if software.installedversion %}
{{ software.installedversion }}
{% else %}
-
{% endif %}
</td>
<td>
{% if software.installed %}
{{ software.installed }}
{% else %}
-
{% endif %}
</td>
<td>&nbsp;</td>
</tr>
{% endfor %}
{% else %}
<td colspan="5">Nothing Found {% include 'icons/issue_link.html.j2' with issue=2 %}</td>
<td colspan="5">Nothing Found</td>
{% endif %}
</table>
</div>

View File

@ -121,7 +121,13 @@
<td><a href="{% url 'ITAM:_device_view' pk=install.device_id %}">{{ install.device }}</a></td>
<td>{{ install.organization }}</td>
<td>{{ install.version }}</td>
<td>{% include 'icons/issue_link.html.j2' with issue=2 %}</td>
<td>
{% if install.installdate %}
{{ install.installdate }}
{% else %}
-
{% endif %}
</td>
<td>&nbsp;</td>
</tr>
{% endfor %}

View File

@ -69,7 +69,7 @@
{% for version in software_versions %}
<tr>
<td><a href="{% url 'ITAM:_software_version_view' software_id=software.id pk=version.id %}">{{ version.name }}</a></td>
<td>{% include 'icons/issue_link.html.j2' with issue=2 %}</td>
<td>{{ version.installs }}</td>
<td>{% include 'icons/issue_link.html.j2' with issue=3 %}</td>
<td>&nbsp;</td>
</tr>
@ -115,8 +115,8 @@
<th>Device</th>
<th>Organization</th>
<th title="Not Set/Install/Remove">Action</th>
<th>Version</th>
<th title="Date Software Installed">Installed</th>
<th>Installed Version</th>
<th title="Date Software Installed">Install Date</th>
<th>&nbsp;</th>
</thead>
{% if device_software %}
@ -130,17 +130,23 @@
{% elif device.get_action_display == 'Remove'%}
{% include 'icons/cross_text.html.j2' with icon_text=device.get_action_display %}
{% else %}
{{ device.get_action_display }}
-
{% endif %}
</td>
<td>
{% if device.version %}
{{ device.version }}
{% if device.installedversion %}
{{ device.installedversion }}
{% else %}
Any
-
{% endif %}
</td>
<td>{% include 'icons/issue_link.html.j2' with issue=2 %}</td>
<td>
{% if device.installed %}
{{ device.installed }}
{% else %}
-
{% endif %}
</td>
<td>&nbsp;</td>
</tr>
{% endfor %}

View File

@ -10,6 +10,7 @@
<table class="data">
<tr>
<th>Name</th>
<th>Category</th>
<th>Created</th>
<th>Modified</th>
<th>Organization</th>
@ -18,6 +19,7 @@
{% for software in softwares %}
<tr>
<td><a href="{% url 'ITAM:_software_view' pk=software.id %}">{{ software.name }}</a></td>
<td>{{ software.category }}</td>
<td>{{ software.created }}</td>
<td>{{ software.modified }}</td>
<td>{% if software.is_global %}Global{% else %}{{ software.organization }}{% endif %}</td>

View File

@ -2,12 +2,14 @@ import json
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.db.models import Q
from django.http import HttpResponseRedirect
from django.views import generic
from access.mixin import OrganizationPermission
from access.models import Organization
from ..models.device import Device, DeviceSoftware, DeviceOperatingSystem
from ..models.software import Software
from itam.forms.device_softwareadd import SoftwareAdd
from itam.forms.device_softwareupdate import SoftwareUpdate
@ -53,6 +55,9 @@ class View(OrganizationPermission, generic.UpdateView):
context_object_name = "device"
paginate_by = 10
def get_context_data(self, **kwargs):
@ -74,7 +79,9 @@ class View(OrganizationPermission, generic.UpdateView):
context['operating_system'] = OperatingSystemForm(prefix='operating_system')
softwares = DeviceSoftware.objects.filter(device=self.kwargs['pk'])
softwares = DeviceSoftware.objects.filter(device=self.kwargs['pk'])[:50]
context['installed_software'] = len(DeviceSoftware.objects.filter(device=self.kwargs['pk']))
context['softwares'] = softwares
config = self.object.get_configuration(self.kwargs['pk'])
@ -134,6 +141,7 @@ class SoftwareView(OrganizationPermission, generic.UpdateView):
form.instance.organization_id = device.organization.id
form.instance.device_id = self.kwargs['device_id']
return super().form_valid(form)
@ -141,6 +149,14 @@ class SoftwareView(OrganizationPermission, generic.UpdateView):
return f"/itam/device/{self.kwargs['device_id']}/"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['content_title'] = 'Edit Software Action'
return context
class Add(PermissionRequiredMixin, OrganizationPermission, generic.CreateView):
@ -194,7 +210,25 @@ class SoftwareAdd(PermissionRequiredMixin, OrganizationPermission, generic.Creat
device = Device.objects.get(pk=self.kwargs['pk'])
form.instance.organization_id = device.organization.id
form.instance.device_id = self.kwargs['pk']
return super().form_valid(form)
software = Software.objects.get(pk=form.instance.software.id)
if DeviceSoftware.objects.get(device=device, software=software):
software_version = DeviceSoftware.objects.get(
device=device,
software=software
)
software_version.action = form.instance.action
software_version.save()
return HttpResponseRedirect(self.get_success_url())
else:
return super().form_valid(form)
def get_form_kwargs(self):
@ -212,7 +246,7 @@ class SoftwareAdd(PermissionRequiredMixin, OrganizationPermission, generic.Creat
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['content_title'] = 'Add Device'
context['content_title'] = 'Add Software Action'
return context

View File

@ -1,5 +1,5 @@
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.db.models import Q
from django.db.models import Count, Q
from django.views import generic
from access.mixin import OrganizationPermission
@ -43,16 +43,33 @@ class View(OrganizationPermission, generic.UpdateView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
software_versions = SoftwareVersion.objects.filter(software=self.kwargs['pk'])
software_versions = SoftwareVersion.objects.filter(
software=self.kwargs['pk']
).annotate(
installs=Count("installedversion")
)
context['software_versions'] = software_versions
context['content_title'] = self.object.name
if self.request.user.is_superuser:
context['device_software'] = DeviceSoftware.objects.filter(software=self.kwargs['pk']).order_by('device', 'organization')
context['device_software'] = DeviceSoftware.objects.filter(
software=self.kwargs['pk']
).order_by(
'device',
'organization'
)
elif not self.request.user.is_superuser:
context['device_software'] = DeviceSoftware.objects.filter(Q(device__in=self.user_organizations(), software=self.kwargs['pk'])).order_by('name', 'organization')
context['device_software'] = DeviceSoftware.objects.filter(
Q(device__in=self.user_organizations(),
software=self.kwargs['pk'])
).order_by(
'name',
'organization'
)
return context

View File

@ -54,6 +54,33 @@ span.icon-text {
display: inline-block;
}
span.icon-text a {
text-decoration: unset;
color: inherit;
}
span.icon-text a:visited {
text-decoration: unset;
color: inherit;
}
span.add {
color: #315f9c;
}
span.icon-add {
fill: #315f9c;
height: 30px;
line-height: 30px;
margin: 0px;
margin-bottom: -7px;
padding: 0px;
vertical-align: middle;
display: inline-block;
}
span.success {
color: #319c3a;
}
@ -69,7 +96,6 @@ span.icon-success {
display: inline-block;
}
span.cross {
color: #9c3131;
}
@ -101,9 +127,9 @@ span.icon-change {
}
/* span.issue {
span.issue {
color: #fc6d26;
} */
}
span.icon-issue {
fill: #fc6d26;

View File

@ -0,0 +1,12 @@
<span class="icon-text add">
<span class="icon-add">
<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px">
<path d="M440-280h80v-160h160v-80H520v-160h-80v160H280v80h160v160Zm40 200q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Z"/>
</svg>
</span>
{% if icon_link %}
<a href="{{ icon_link }}">{{ icon_text }}</a>
{% else %}
{{ icon_text }}
{% endif %}
</span>

View File

@ -4,5 +4,9 @@
<path d="m480-429 116 116q11 11 25.5 10.5T647-314q11-11 11-25.5t-11-25.46L531-480.5l116-115.54q11-10.96 11-25.46T647-647q-11-11-25.5-11T596-647L480-531 364-647q-11-11-25-11t-25 11q-11 11-11 25.5t10.91 25.5L429-480 313-364q-11 11-10.5 25t11.5 25q11 11 25.5 11t25.41-10.91L480-429Zm.28 333Q401-96 331-126t-122.5-82.5Q156-261 126-330.96t-30-149.5Q96-560 126-629.5q30-69.5 82.5-122T330.96-834q69.96-30 149.5-30t149.04 30q69.5 30 122 82.5T834-629.28q30 69.73 30 149Q864-401 834-331t-82.5 122.5Q699-156 629.28-126q-69.73 30-149 30Z" />
</svg>
</span>
{{ icon_text }}
{% if icon_link %}
<a href="{{ icon_link }}">{{ icon_text }}</a>
{% else %}
{{ icon_text }}
{% endif %}
</span>

View File

@ -4,5 +4,9 @@
<path class="tick" d="m424-408-86-86q-11-11-28-11t-28 11q-11 11-11 28t11 28l114 114q12 12 28 12t28-12l226-226q11-11 11-28t-11-28q-11-11-28-11t-28 11L424-408Zm56 328q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Z"></path>
</svg>
</span>
{{ icon_text }}
{% if icon_link %}
<a href="{{ icon_link }}">{{ icon_text }}</a>
{% else %}
{{ icon_text }}
{% endif %}
</span>