remove outdated documentation

literate programming is a nice idea. However IMO it's too much overhead
especially for larger project. I would rather focus on writing tests.
This commit is contained in:
Andreas Zweili 2019-08-30 23:22:19 +02:00
parent 63553a14b4
commit e52ae11e44
2 changed files with 0 additions and 961 deletions

View File

@ -45,8 +45,3 @@ supported.
I would like to use this to show the usable space in a RAID system.
- [ ] calculate the used space on a host
Means calculate the size all the VMs would use if they were thick.
## Developemenet
For a detailed documentation of the source have a look at the
[documentation](https://git.2li.ch/Nebucatnetzer/network_inventory/src/branch/master/docs/docs.org).

View File

@ -1,956 +0,0 @@
#+TITLE: Andreas Zweili
#+AUTHOR: Andreas Zweili
#+LaTeX_HEADER: \input{/home/andreas/git_repos/notes/settings/latex/style}
#+SETUPFILE: ~/git_repos/notes/settings/html_theme/setup/theme-readtheorg.setup
* inventory/models.py
Models define the database layout in a django application. Each class
represents a table in the database. A lot the models in this project
are very small because they exist mainly to keep the information unified.
** Device
The "Device" class is only used as an abstract class.
#+BEGIN_SRC python :tangle ../network_inventory/inventory/models.py
from django.db import models
class Device(models.Model):
name = models.CharField(max_length=50)
description = models.TextField()
def __str__(self):
return self.name
#+END_SRC
** GeneralDevice
The "GeneralDevice" model is used to describe devices which are very
simple or I don't have much control over.
#+BEGIN_SRC python :tangle ../network_inventory/inventory/models.py :padline 2
class GeneralDevice(Device):
def __str__(self):
return self.name
#+END_SRC
** Weekday, DayOfMonth and Month
These models contain all the days of the week the days in a month and
all month in a year.
#+BEGIN_SRC python :tangle ../network_inventory/inventory/models.py :padline 2
class HoursInDay(models.Model):
name = models.IntegerField()
def __str__(self):
return str(self.name)
class Meta:
verbose_name_plural = "Hours"
ordering = ['name']
class MinutesInHour(models.Model):
name = models.IntegerField()
def __str__(self):
return str(self.name)
class Meta:
verbose_name_plural = "Minutes"
ordering = ['name']
class Weekday(models.Model):
name = models.CharField(max_length=50)
value = models.IntegerField()
def __str__(self):
return self.name
class Meta:
ordering = ['value']
class DayOfMonth(models.Model):
name = models.IntegerField()
def __str__(self):
return str(self.name)
class Meta:
verbose_name_plural = "Days of Month"
ordering = ['name']
class Month(models.Model):
name = models.CharField(max_length=50)
value = models.IntegerField()
def __str__(self):
return self.name
class Meta:
ordering = ['value']
#+END_SRC
** RamType, Ram
"RamType" and "Ram" are ment to specify a ram module. "RamType" stands
for the DDR verions.
#+BEGIN_SRC python :tangle ../network_inventory/inventory/models.py :padline 2
class RamType(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Meta:
verbose_name_plural = "Types of RAM Modules"
class Ram(models.Model):
type = models.ForeignKey(RamType, on_delete=models.CASCADE)
size_in_gb = models.IntegerField()
ecc = models.BooleanField(default=False)
def __str__(self):
return '{} {} GB'.format(self.type, self.size_in_gb)
class Meta:
verbose_name_plural = "RAM Modules"
#+END_SRC
** DiskType, DiskSize, Disk
This three models together represent the various disk types I'm using.
The idea is that you define the type then enter a common sizes you're
using and then connect everything together in the "Disk" model.
This way you have a set of disks you can "insert" into "Computer" models.
#+BEGIN_SRC python :tangle ../network_inventory/inventory/models.py :padline 2
class DiskType(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Meta:
verbose_name_plural = "Types of disks"
ordering = ['name']
class DiskSize(models.Model):
size = models.IntegerField()
def __str__(self):
return str(self.size) + " GB"
class Meta:
verbose_name_plural = "Disk sizes"
ordering = ['size']
class Disk(models.Model):
type = models.ForeignKey(DiskType, on_delete=models.CASCADE)
size_in_gb = models.ForeignKey(DiskSize, on_delete=models.CASCADE)
def __str__(self):
return '{} {}'.format(self.type, self.size_in_gb)
class Meta:
ordering = ['type']
#+END_SRC
** Architecture, CpuManufacturer and Cpu
"Architecture", "CpuManufacturer" and "Cpu" are the models which
together specifiy the properties of a CPU.
#+BEGIN_SRC python :tangle ../network_inventory/inventory/models.py :padline 2
class Architecture(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class CpuManufacturer(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Meta:
verbose_name_plural = "CPU Manufacturers"
class Cpu(models.Model):
name = models.CharField(max_length=50)
manufacturer = models.ForeignKey(CpuManufacturer, on_delete=models.PROTECT)
number_of_cores = models.IntegerField()
frequency = models.FloatField()
architecture = models.ForeignKey(Architecture, on_delete=models.PROTECT)
def __str__(self):
return self.name
class Meta:
verbose_name_plural = "CPUs"
#+END_SRC
** OperatingSystem
A simple model to save operating system names.
#+BEGIN_SRC python :tangle ../network_inventory/inventory/models.py :padline 2
class OperatingSystem(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Meta:
verbose_name_plural = "Operating Systems"
#+END_SRC
** Raid
A model to store the various RAID configurations.
#+BEGIN_SRC python :tangle ../network_inventory/inventory/models.py :padline 2
class Raid(models.Model):
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Meta:
verbose_name_plural = "Types of RAID"
#+END_SRC
** Computer
This model represents a complete computer, server or virtual machine.
It's inheritated from the "Device" model. So that one can link it to a
warranty.
#+BEGIN_SRC python :tangle ../network_inventory/inventory/models.py :padline 2
class Computer(Device):
os = models.ForeignKey(OperatingSystem, on_delete=models.PROTECT)
cpu = models.ManyToManyField(Cpu, through='ComputerCpuRelation')
ram = models.ManyToManyField(Ram, through='ComputerRamRelation')
ip = models.GenericIPAddressField()
disks = models.ManyToManyField(Disk, through='ComputerDiskRelation')
host = models.ForeignKey('self', null=True, blank=True,
on_delete=models.PROTECT)
def __str__(self):
return str(self.name)
class Meta:
ordering = ['ip']
#+END_SRC
** ComputerDiskRelation, ComputerRamRelation and ComputerCpuRelation
These models are required to link RAM modules, disks and CPUs to a computer.
Without these models it wouldn't be possible to specifiy the used amount.
#+BEGIN_SRC python :tangle ../network_inventory/inventory/models.py :padline 2
class ComputerDiskRelation(models.Model):
disk = models.ForeignKey(Disk, on_delete=models.CASCADE)
computer = models.ForeignKey(Computer, on_delete=models.CASCADE)
amount = models.IntegerField()
raid = models.ForeignKey(Raid, null=True, blank=True,
on_delete=models.PROTECT)
def __str__(self):
return self.computer.name
class Meta:
verbose_name_plural = "Disks in Computer"
class ComputerRamRelation(models.Model):
ram = models.ForeignKey(Ram, on_delete=models.CASCADE)
computer = models.ForeignKey(Computer, on_delete=models.CASCADE)
amount = models.IntegerField()
def __str__(self):
return self.computer.name
class Meta:
verbose_name_plural = "RAM Modules in Computer"
class ComputerCpuRelation(models.Model):
cpu = models.ForeignKey(Cpu, on_delete=models.CASCADE)
computer = models.ForeignKey(Computer, on_delete=models.CASCADE)
amount = models.IntegerField()
def __str__(self):
return self.computer.name
class Meta:
verbose_name_plural = "CPUs in Computer"
#+END_SRC
** Warranty
As the name suggests this model is for storing warranty informations.
In addition it has an attribute for a file so that one can attach a
scan of the warranty paper.
#+BEGIN_SRC python :tangle ../network_inventory/inventory/models.py :padline 2
class Warranty(models.Model):
device = models.ForeignKey(Device, on_delete=models.CASCADE)
files = models.FileField()
valid_until = models.DateField()
def __str__(self):
return self.device
class Meta:
verbose_name_plural = "Warranties"
#+END_SRC
** CronJob
This model represents a cron job running on a host. It contains all
the information that one would write in a crontab file.
#+BEGIN_SRC python :tangle ../network_inventory/inventory/models.py :padline 2
class CronJob(models.Model):
name = models.CharField(max_length=50)
host = models.ForeignKey(Computer, on_delete=models.CASCADE)
command = models.CharField(max_length=50)
minutes = models.ForeignKey(MinutesInHour, on_delete=models.CASCADE,
null=True, blank=True,)
hours = models.ForeignKey(HoursInDay, on_delete=models.CASCADE,
null=True, blank=True,)
weekday = models.ForeignKey(Weekday, on_delete=models.CASCADE,
null=True, blank=True,)
day = models.ForeignKey(DayOfMonth, on_delete=models.CASCADE,
null=True, blank=True,)
month = models.ForeignKey(Month, on_delete=models.CASCADE,
null=True, blank=True,)
def __str__(self):
return self.name
class Meta:
verbose_name_plural = "Cron Jobs"
#+END_SRC
* inventory/admin.py
The admin file specifies which models are visible and in which way the
get shown in the admin interface.
We have to import each model we want to use.
#+BEGIN_SRC python :tangle ../network_inventory/inventory/admin.py
from django.contrib import admin
from .models import (GeneralDevice, RamType, Ram, DiskType, DiskSize,
Disk, Architecture, CpuManufacturer,
Cpu, OperatingSystem, Raid, Computer,
ComputerDiskRelation,
ComputerCpuRelation,
ComputerRamRelation, Warranty, CronJob)
#+END_SRC
** InLine classes
I made an inline class for RAM, disks and CPUs and extended the
"Computer" admin form with them. This makes it easier to add theme to
a computer.
#+BEGIN_SRC python :tangle ../network_inventory/inventory/admin.py :padline 2
class RamInLine(admin.StackedInline):
model = ComputerRamRelation
extra = 0
verbose_name_plural = 'RAM Modules'
class DiskInLine(admin.StackedInline):
model = ComputerDiskRelation
extra = 0
verbose_name_plural = 'Disks'
class CpusInLine(admin.StackedInline):
model = ComputerCpuRelation
extra = 0
verbose_name_plural = 'CPUs'
#+END_SRC
** Extended Admin Interfaces
This section contains the classes which extend the admin interface for
certain models in cases where the default form might not work for the task.
*** ComputerAdmin
The "ComputerAdmin" class extends the default "Computer" admin
interface.
For one it adds some columns to the list and adds three inline models
which allows to add them directly from the "Computer" form.
#+BEGIN_SRC python :tangle ../network_inventory/inventory/admin.py :padline 2
class ComputerAdmin(admin.ModelAdmin):
list_display = ('name', 'ip', 'host')
inlines = (CpusInLine, RamInLine, DiskInLine,)
#+END_SRC
*** CronJobAdmin
The "CronJobAdmin" extends the cron job admin list with two columns to
show to which host they belong.
#+BEGIN_SRC python :tangle ../network_inventory/inventory/admin.py :padline 2
class CronJobAdmin(admin.ModelAdmin):
list_display = ('name', 'host')
#+END_SRC
** Registering models
In order for the models to show up in the admin interface we have to
register them in addition to importing them in the admin.py file.
In addition we have to define which admin form they should use if we
want to use something different than the default one.
#+BEGIN_SRC python :tangle ../network_inventory/inventory/admin.py :padline 2
admin.site.register(GeneralDevice)
admin.site.register(RamType)
admin.site.register(Ram)
admin.site.register(DiskType)
admin.site.register(DiskSize)
admin.site.register(Disk)
admin.site.register(Architecture)
admin.site.register(CpuManufacturer)
admin.site.register(Cpu)
admin.site.register(OperatingSystem)
admin.site.register(Raid)
admin.site.register(Computer, ComputerAdmin)
admin.site.register(Warranty)
admin.site.register(CronJob, CronJobAdmin)
#+END_SRC
* inventory/views.py
Views are used to get information from the database and send them to
the templates.
#+BEGIN_SRC python :tangle ../network_inventory/inventory/views.py
from django.shortcuts import get_object_or_404, render
from django.views.generic import ListView
from .models import (GeneralDevice, Computer, CronJob,
ComputerRamRelation,
ComputerDiskRelation,
ComputerCpuRelation)
def device_details(request, device_id):
device = get_object_or_404(GeneralDevice, pk=device_id)
return render(request, 'inventory/device_details.html',
{'device': device})
def computer_details(request, computer_id):
computer = get_object_or_404(Computer, pk=computer_id)
disks_list = ComputerDiskRelation.objects.filter(computer=computer_id)
ram = ComputerRamRelation.objects.get(computer=computer_id)
cpu = ComputerCpuRelation.objects.get(computer=computer_id)
cronjob_list = CronJob.objects.filter(host=computer_id)
return render(request, 'inventory/computer_details.html',
{'computer': computer,
'disks_list': disks_list,
'ram': ram,
'cpu': cpu,
'cronjob_list': cronjob_list})
def cronjob_details(request, cronjob_id):
cronjob = get_object_or_404(CronJob, pk=cronjob_id)
return render(request, 'inventory/cronjob_details.html',
{'cronjob': cronjob})
class ComputerList(ListView):
model = Computer
template_name = 'inventory/computer_list.html'
class CronJobList(ListView):
model = CronJob
template_name = 'inventory/cronjob_list.html'
class DeviceList(ListView):
model = GeneralDevice
context_object_name = 'device_list'
template_name = 'inventory/device_list.html'
#+END_SRC
* URLs
The urls.py files contain the definitions for the URLs. This means you
can define how your various pages get accessed.
** network_inventory/urls.py
This is the main URLs file. All the urls.py files need to get
registered in here in order to work.
#+BEGIN_SRC python :tangle ../network_inventory/network_inventory/urls.py
"""network_inventory URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.conf.urls import include, url
from django.contrib import admin
from django.urls import path
urlpatterns = [
url(r'', include('inventory.urls')),
path('admin/', admin.site.urls),
]
#+END_SRC
** inventory/urls.py
Contains the url definitions for the inventory application. For the
moment the inventory is my only application so all the URLs will start
from the root.
#+BEGIN_SRC python :tangle ../network_inventory/inventory/urls.py
from django.conf.urls import url
from inventory import views
urlpatterns = [
url(r'^$', views.ComputerList.as_view(), name='computers'),
url(r'^device/(?P<device_id>[0-9]+)/$',
views.device_details,
name='device'),
url(r'^computer/(?P<computer_id>[0-9]+)/$',
views.computer_details,
name='computer'),
url(r'^cronjob/(?P<cronjob_id>[0-9]+)/$',
views.cronjob_details,
name='cronjob'),
url(r'^cronjobs/$', views.CronJobList.as_view(), name='cronjobs'),
url(r'^devices/$', views.DeviceList.as_view(), name='devices'),
]
#+END_SRC
* Templates
Templates define the look of the application and where things get
positioned on the page.
** inventory.css
The inventory.css file get's loaded in the base.html file. It provides
some generel theming over the application.
The theme used in the inventory css is called "sakura earthly". It's
repository can be found here: https://github.com/oxalorg/sakura
I modified the theme slightly. I changed the font to sans-serif
because it makes in the inventory more sense. In addition I'm
displaying all lines in a table.
#+BEGIN_SRC css :tangle ../network_inventory/static/inventory/css/inventory.css
/* Sakura.css v1.0.0
* ================
* Minimal css theme.
* Project: https://github.com/oxalorg/sakura
*/
/* Body */
html {
font-size: 62.5%;
font-family: sans-serif; }
body {
font-size: 1.8rem;
line-height: 1.618;
max-width: 38em;
margin: auto;
color: #4a4a4a;
background-color: #f9f9f9;
padding: 13px; }
@media (max-width: 684px) {
body {
font-size: 1.53rem; } }
@media (max-width: 382px) {
body {
font-size: 1.35rem; } }
h1, h2, h3, h4, h5, h6 {
line-height: 1.1;
font-family: Verdana, Geneva, sans-serif;
font-weight: 700;
overflow-wrap: break-word;
word-wrap: break-word;
-ms-word-break: break-all;
word-break: break-word;
-ms-hyphens: auto;
-moz-hyphens: auto;
-webkit-hyphens: auto;
hyphens: auto; }
h1 {
font-size: 2.35em; }
h2 {
font-size: 2.00em; }
h3 {
font-size: 1.75em; }
h4 {
font-size: 1.5em; }
h5 {
font-size: 1.25em; }
h6 {
font-size: 1em; }
footer {
font-size: 0.5em; }
small, sub, sup {
font-size: 75%; }
hr {
border-color: #338618; }
a {
text-decoration: none;
color: #338618; }
a:hover {
color: #5e5e5e;
border-bottom: 2px solid #4a4a4a; }
ul {
padding-left: 1.4em; }
li {
margin-bottom: 0.4em; }
blockquote {
font-style: italic;
margin-left: 1.5em;
padding-left: 1em;
border-left: 3px solid #338618; }
img {
max-width: 100%; }
/* Pre and Code */
pre {
background-color: #C7E3BE;
display: block;
padding: 1em;
overflow-x: auto; }
code {
font-size: 0.9em;
padding: 0 0.5em;
background-color: #C7E3BE;
white-space: pre-wrap; }
pre > code {
padding: 0;
background-color: transparent;
white-space: pre; }
/* Tables */
table {
text-align: justify;
width: 100%;
border-collapse: collapse; }
td, th {
padding: 0.5em;
border: 1px solid #C7E3BE; }
/* Buttons, forms and input */
input, textarea {
border: 1px solid #4a4a4a; }
input:focus, textarea:focus {
border: 1px solid #338618; }
textarea {
width: 100%; }
.button, button, input[type="submit"], input[type="reset"], input[type="button"] {
display: inline-block;
padding: 5px 10px;
text-align: center;
text-decoration: none;
white-space: nowrap;
background-color: #338618;
color: #f9f9f9;
border-radius: 1px;
border: 1px solid #338618;
cursor: pointer;
box-sizing: border-box; }
.button[disabled], button[disabled], input[type="submit"][disabled], input[type="reset"][disabled], input[type="button"][disabled] {
cursor: default;
opacity: .5; }
.button:focus, .button:hover, button:focus, button:hover, input[type="submit"]:focus, input[type="submit"]:hover, input[type="reset"]:focus, input[type="reset"]:hover, input[type="button"]:focus, input[type="button"]:hover {
background-color: #5e5e5e;
border-color: #5e5e5e;
color: #f9f9f9;
outline: 0; }
textarea, select, input[type] {
color: #4a4a4a;
padding: 6px 10px;
/* The 6px vertically centers text on FF, ignored by Webkit */
margin-bottom: 10px;
background-color: #C7E3BE;
border: 1px solid #C7E3BE;
border-radius: 4px;
box-shadow: none;
box-sizing: border-box; }
textarea:focus, select:focus, input[type]:focus {
border: 1px solid #338618;
outline: 0; }
input[type="checkbox"]:focus {
outline: 1px dotted #338618; }
label, legend, fieldset {
display: block;
margin-bottom: .5rem;
font-weight: 600; }
#+END_SRC
** base.html
The base.html file is the basis for all other html files and gets
extended by them.
#+BEGIN_SRC html :tangle ../network_inventory/inventory/templates/inventory/base.html
{% load staticfiles %}
<!DOCTYPE html>
<head>
<link rel="stylesheet" href="{% static 'inventory/css/inventory.css' %}">
<title>Network Inventory</title>
<meta charset="utf-8">
</head>
<body>
<a href="{% url 'computers' %}">Computers</a> |
<a href="{% url 'devices' %}">Devices</a> |
<a href="{% url 'cronjobs' %}">Cron Jobs</a>
<h1>{% block section_title %}Device Inventory{% endblock %}</h1>
{% block content %}{% endblock %}
<footer>
{% block footer %}
<p class="copyright">Created by Andreas Zweili licensed under GPL v3.0</p>
{% endblock %}
</footer>
<script src={% static 'inventory/js/sorttable.js' %}></script>
</body>
</html>
#+END_SRC
** computer_list.html
computer_list.html is the landing page of the project. It gives a list
overview over the active computers. In addition it shows some useful
information about the devices like IP addresses and similar
information.
#+BEGIN_SRC html :tangle ../network_inventory/inventory/templates/inventory/computer_list.html
{% extends "inventory/base.html" %}
{% block section_title %}List of Computers{% endblock %}
{% block content %}
{% if computer_list %}
<table class="sortable">
<tr>
<th>Hostname</th>
<th>IP</th>
</tr>
{% for computer in computer_list %}
<tr>
<td><a href="{% url 'computer' computer.id %}">{{ computer.name }}</a></td>
<td>{{ computer.ip }}</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% endblock %}
#+END_SRC
** device_list.html
device_list.html provides an overview about all the general devices in
addition to a link to detail page of each device.
#+BEGIN_SRC html :tangle ../network_inventory/inventory/templates/inventory/device_list.html
{% extends "inventory/base.html" %}
{% block section_title %}List of General Devices{% endblock %}
{% block content %}
{% if device_list %}
<ul>
{% for device in device_list %}
<li><a href="{% url 'device' device.id %}">{{ device.name }}</a></li>
{% endfor %}
</ul>
{% endif %}
{% endblock %}
#+END_SRC
** cronjob_list.html
cronjob_list.html provides an overview about all the cron jobs
including the time they are running and on which computer. In addtion
it provides a link to the detail page of each cron job.
#+BEGIN_SRC html :tangle ../network_inventory/inventory/templates/inventory/cronjob_list.html
{% extends "inventory/base.html" %}
{% block section_title %}List of Cron Jobs{% endblock %}
{% block content %}
{% if cronjob_list %}
<table class="sortable">
<tr>
<th>Name</th>
<th>Hostname</th>
<th>Minute</th>
<th>Hour</th>
<th>Day of Week</th>
<th>Day of Month</th>
<th>Month</th>
</tr>
{% for cronjob in cronjob_list %}
<tr>
<td><a href="{% url 'cronjob' cronjob.id %}">{{ cronjob.name }}</a></td>
<td>{{ cronjob.host }}</td>
<td>{{ cronjob.minutes }}</td>
<td>{{ cronjob.hours }}</td>
<td>{{ cronjob.weekday }}</td>
<td>{{ cronjob.day }}</td>
<td>{{ cronjob.month }}</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% endblock %}
#+END_SRC
** device_details.html
#+BEGIN_SRC html :tangle ../network_inventory/inventory/templates/inventory/device_details.html
{% extends "inventory/base.html" %}
{% block section_title %}{{ device.name }}{% endblock %}
{% block content %}
<h3>Description</h3>
<p>{{ device.description }}</p>
{% endblock %}
#+END_SRC
** computer_details.html
The computer details show all the known information about the selected computer.
#+BEGIN_SRC html :tangle ../network_inventory/inventory/templates/inventory/computer_details.html
{% extends "inventory/base.html" %}
{% block section_title %}{{ computer.name }}{% endblock %}
{% block content %}
<h3>Description</h3>
<p>{{ computer.description }}</p>
<p><b>OS: </b>{{ computer.os }}</p>
<p><b>CPU: </b>{{ cpu.amount }}x {{ cpu.cpu }}</p>
<p><b>RAM Modules: </b>{{ ram.amount }}x {{ ram.ram }}</p>
<p><b>IP: </b>{{ computer.ip }}</p>
{% if disks_list %}
<p><b>Disks:</b></p>
<p>
<ul>
{% for disk in disks_list %}
<li>{{ disk.amount }}x {{ disk.disk }}</li>
{% endfor %}
</ul>
</p>
{% endif %}
{% if computer.host %}
<p><b>Host:</b> <a href="{% url 'computer' computer.host.id %}">{{ computer.host }}</a></p>
{% else %}
<p><b>Host:</b> None</p>
{% endif %}
#+END_SRC
The list of cron jobs running on the computer get's only displayed if
there are any cron jobs.
#+BEGIN_SRC html :tangle ../network_inventory/inventory/templates/inventory/computer_details.html :padline 0
{% if cronjob_list %}
<p><b>Cron Jobs:</b></p>
<p>
<ul>
{% for cronjob in cronjob_list %}
<li><a href="{% url 'cronjob' cronjob.id %}">{{ cronjob.name }}</a></li>
{% endfor %}
</ul>
</p>
{% endif %}
{% endblock %}
#+END_SRC
** cronjob_details.html
The cron job details page shows all the information related to a cron job.
#+BEGIN_SRC html :tangle ../network_inventory/inventory/templates/inventory/cronjob_details.html
{% extends "inventory/base.html" %}
{% block section_title %}{{ cronjob.name }}{% endblock %}
{% block content %}
<h3>Description</h3>
<p>
<b>Host:</b> <a href="{% url 'computer' cronjob.host.id %}">{{ cronjob.host }}</a>
</p>
<p>
<b>Command:</b><br/>
<code>{{ cronjob.command }}</code>
</p>
<table>
<tr>
<th>Minute</th>
<th>Hour</th>
<th>Day of Week</th>
<th>Day of Month</th>
<th>Month</th>
</tr>
<tr>
<td>{{ cronjob.minutes }}</td>
<td>{{ cronjob.hours }}</td>
<td>{{ cronjob.weekday }}</td>
<td>{{ cronjob.day }}</td>
<td>{{ cronjob.month }}</td>
</tr>
</table>
{% endblock %}
#+END_SRC