Compare commits

...

10 Commits

Author SHA1 Message Date
Jon
8d071c68df chore: add Merge/Pull request template
#226
2024-08-14 01:52:22 +09:30
Jon
3b1691ff62 docs(roadmap): update completed features
#226
2024-08-14 01:46:21 +09:30
Jon
a77c43d213 docs(base): detail view template
. #24 #226 closes #22
2024-08-14 01:33:48 +09:30
Jon
086959b431 refactor(itim): services now use details template
. #22 #226
2024-08-14 01:23:17 +09:30
Jon
3f117f9d83 feat(base): create detail view templates
purpose is to aid in the development of a detail form

#22 #24 #226
2024-08-14 00:02:25 +09:30
Jon
6a23845a4f docs: initial adding of template page
#22
2024-08-13 15:03:10 +09:30
Jon
b9c6d04e04 chore: add services navigation icon
!43 #69
2024-08-13 13:23:45 +09:30
Jon
32c0027ecf fix(itim): ensure that the service template config is also rendered as part of device config
!43 #69
2024-08-13 13:23:45 +09:30
Jon
dae52e8646 docs: fluff the port and services
!43 closes #69
2024-08-13 13:23:45 +09:30
Jon
890a5651a0 fix(itim): dont render link if no device
!43 #69
2024-08-13 13:23:45 +09:30
26 changed files with 855 additions and 346 deletions

39
.github/pull_request_template.md vendored Normal file
View File

@ -0,0 +1,39 @@
### :books: Summary
<!-- your summary here emojis ref: https://github.com/yodamad/gitlab-emoji -->
### :link: Links / References
<!--
using a list as any links to other references or links as required. if relevant, describe the link/reference
Include any issues or related merge requests. Note: dependent MR's also to be added to "Merge request dependencies"
-->
### :construction_worker: Tasks
- [ ] Add your tasks here if required (delete)
<!-- dont remove tasks below strike through including the checkbox by enclosing in double tidle '~~' -->
- [ ] :firecracker: Contains breaking-change Any Breaking change(s)?
_Breaking Change must also be notated in the commit that introduces it and in [Conventional Commit Format](https://www.conventionalcommits.org/en/v1.0.0/)._
- [ ] :notebook: Release notes updated
- [ ] :blue_book: Documentation written
_All features to be documented within the correct section(s). Administration, Development and/or User_
- [ ] :checkered_flag: Milestone assigned
- [ ] :test_tube: [Unit Test(s) Written](https://nofusscomputing.com/projects/centurion_erp/development/testing/)
_ensure test coverage delta is not less than zero_
- [ ] :page_facing_up: Roadmap updated

View File

@ -35,3 +35,5 @@
- [ ] [Unit Test(s) Written](https://nofusscomputing.com/projects/centurion_erp/development/testing/)
_ensure test coverage delta is not less than zero_
- [ ] :page_facing_up: Roadmap updated

View File

@ -75,6 +75,13 @@ class ChangeView(View, generic.UpdateView):
external_links_query = None
if 'tab' in self.request.GET:
context['open_tab'] = str(self.request.GET.get("tab")).lower()
else:
context['open_tab'] = None
if self.model._meta.model_name == 'device':

View File

@ -288,10 +288,10 @@ class Device(DeviceCommonFieldsName, SaveHistory):
for service in services:
if service.config:
if service.config_variables:
service_config:dict = {
service.config_key_variable: service.config
service.config_key_variable: service.config_variables
}
config.update(service_config)

View File

@ -1,8 +1,11 @@
from django import forms
from django.forms import ValidationError
from django.urls import reverse
from itim.models.services import Service
from app import settings
from core.forms.common import CommonModelForm
@ -79,3 +82,81 @@ class ServiceForm(CommonModelForm):
return cleaned_data
class DetailForm(ServiceForm):
tabs: dict = {
"details": {
"name": "Details",
"slug": "details",
"sections": [
{
"layout": "double",
"left": [
'name',
'config_key_variable',
'template',
'organization',
'c_created',
'c_modified'
],
"right": [
'model_notes',
]
}
]
},
"rendered_config": {
"name": "Rendered Config",
"slug": "rendered_config",
"sections": [
{
"layout": "single",
"fields": [
'config_variables',
],
"json": [
'config_variables'
]
}
]
}
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['config_variables'] = forms.fields.JSONField(
widget = forms.Textarea(
attrs = {
"cols": "80",
"rows": "100"
}
),
label = 'Rendered Configuration',
initial = self.instance.config_variables,
)
self.fields['c_created'] = forms.DateTimeField(
label = 'Created',
input_formats=settings.DATETIME_FORMAT,
disabled = True,
initial = self.instance.created,
)
self.fields['c_modified'] = forms.DateTimeField(
label = 'Modified',
input_formats=settings.DATETIME_FORMAT,
disabled = True,
initial = self.instance.modified,
)
self.tabs['details'].update({
"edit_url": reverse('ITIM:_service_change', args=(self.instance.pk,))
})

View File

@ -223,10 +223,13 @@ class Service(TenancyObject):
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
self.config_key_variable = self.config_key_variable.lower()
if self.config_key_variable:
self.config_key_variable = self.config_key_variable.lower()
super().save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
def __str__(self):
return self.name

View File

@ -1,271 +1,75 @@
{% extends 'base.html.j2' %}
{% extends 'detail.html.j2' %}
{% load json %}
{% load markdown %}
{% block content %}
<script>
{% block tabs %}
function openCity(evt, cityName) {
var i, tabcontent, tablinks;
<div id="details" class="content-tab">
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
{% include 'content/section.html.j2' with tab=form.tabs.details %}
tablinks = document.getElementsByClassName("tablinks");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
<hr />
document.getElementById(cityName).style.display = "block";
evt.currentTarget.className += " active";
}
<div style="display: block; width: 100%;">
<h3>Ports</h3>
<table>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
{% if item.port.all and not item.template %}
{% for port in item.port.all %}
<tr>
<td><a href="{% url 'Settings:_port_view' item.pk %}">{{ port }}</a></td>
<td>{{ port.description }}</td>
</tr>
{% endfor %}
{% elif not item.port.all and item.template %}
{% for port in item.template.port.all %}
<tr>
<td><a href="{% url 'Settings:_port_view' item.pk %}">{{ port }}</a></td>
<td>{{ port.description }}</td>
</tr>
{% endfor%}
{% else %}
<tr>
<td colspan="2"> Nothing Found</td>
</tr>
{% endif %}
</table>
</div>
</script>
<div style="display: block; width: 100%;">
<h3>Dependent Services</h3>
<table>
<tr>
<th>Name</th>
<th>Organization</th>
</tr>
{% if item.dependent_service.all %}
{% for service in item.dependent_service.all %}
<tr>
<td><a href="{% url 'ITIM:_service_view' service.pk %}">{{ service }}</a></td>
<td>{{ service.organization }}</td>
</tr>
{% endfor%}
{% else %}
<tr>
<td colspan="2"> Nothing Found</td>
</tr>
{% endif %}
</table>
</div>
<style>
.detail-view-field {
display: unset;
height: 30px;
line-height: 30px;
padding: 0px 20px 40px 20px;
}
.detail-view-field label {
display: inline-block;
font-weight: bold;
width: 200px;
margin: 10px;
height: 30px;
line-height: 30px;
}
.detail-view-field span {
display: inline-block;
width: 340px;
margin: 10px;
border-bottom: 1px solid #ccc;
height: 30px;
line-height: 30px;
}
pre {
word-wrap: break-word;
white-space: pre-wrap;
}
</style>
<div class="tab">
<button onclick="window.location='{% url 'ITIM:Services' %}';"
style="vertical-align: middle; padding: auto; margin: 0px">
<svg xmlns="http://www.w3.org/2000/svg" height="25px" viewBox="0 -960 960 960" width="25px"
style="vertical-align: middle; margin: 0px; padding: 0px border: none; " fill="#6a6e73">
<path d="m313-480 155 156q11 11 11.5 27.5T468-268q-11 11-28 11t-28-11L228-452q-6-6-8.5-13t-2.5-15q0-8 2.5-15t8.5-13l184-184q11-11 27.5-11.5T468-692q11 11 11 28t-11 28L313-480Zm264 0 155 156q11 11 11.5 27.5T732-268q-11 11-28 11t-28-11L492-452q-6-6-8.5-13t-2.5-15q0-8 2.5-15t8.5-13l184-184q11-11 27.5-11.5T732-692q11 11 11 28t-11 28L577-480Z" />
</svg>Back to Services</button>
<button id="defaultOpen" class="tablinks" onclick="openCity(event, 'Details')">Details</button>
<button class="tablinks" onclick="openCity(event, 'Rendered_Config')">Rendered Config</button>
{% if perms.assistance.change_service %}
<button class="tablinks" onclick="openCity(event, 'Notes')">Notes</button>
{% endif %}
</div>
<form method="post">
<div id="Details" class="tabcontent">
<h3>Details</h3>
{% csrf_token %}
<div id="rendered_config" class="content-tab">
<div style="align-items:flex-start; align-content: center; display: flexbox; width: 100%">
{% include 'content/section.html.j2' with tab=form.tabs.rendered_config %}
<div style="display: inline; width: 40%; margin: 30px;">
<div class="detail-view-field">
<label>{{ form.name.label }}</label>
<span>{{ form.name.value }}</span>
</div>
<div class="detail-view-field">
<label>{{ form.config_key_variable.label }}</label>
<span>{{ form.config_key_variable.value }}</span>
</div>
{% if form.template.value or form.is_template.value %}
<div class="detail-view-field">
<label>{{ form.is_template.label }}</label>
<span>
{% if form.is_template.value %}
{{ form.is_template.value }}
{% else %}
<a href="{% url 'ITIM:_service_view' item.template.pk %}">{{ item.template }}</a>
{% endif %}
</span>
</div>
{% endif %}
<div class="detail-view-field">
<label>{{ form.organization.label }}</label>
<span>
{% if form.organization.value %}
{{ item.organization }}
{% else %}
&nbsp;
{% endif %}
</span>
</div>
<div class="detail-view-field">
<label>Created</label>
<span>{{ item.created }}</span>
</div>
<div class="detail-view-field">
<label title="{% if item.cluster %}{{ form.cluster.help_text }}{% else %}{{ form.device.help_text }}{% endif %}">
{% if item.cluster %}
Cluster
{% else %}
Device
{% endif %}
</label>
<span>
{% if item.cluster %}
{{ item.cluster }}
{% else %}
<a href="{% url 'ITAM:_device_view' item.device.id %}">{{ item.device }}</a>
{% endif %}
</span>
</div>
<div class="detail-view-field">
<label>Modified</label>
<span>{{ item.modified }}</span>
</div>
</div>
<div style="display: inline; width: 40%; margin: 30px; text-align: left;">
<div>
<label
style="font-weight: bold; width: 100%; border-bottom: 1px solid #ccc; display: block; text-align: inherit;">{{ form.model_notes.label }}</label>
<div style="display: inline-block; text-align: left;">
{% if form.model_notes.value %}
{{ form.model_notes.value | markdown | safe }}
{% else %}
&nbsp;
{% endif %}
</div>
</div>
</div>
</div>
<input type="button" value="Edit" onclick="window.location='{% url 'ITIM:_service_change' item.pk %}';">
<hr />
<div style="display: block; width: 100%;">
<h3>Ports</h3>
<table>
<tr>
<th>Name</th>
<th>Description</th>
</tr>
{% if item.port.all and not item.template %}
{% for port in item.port.all %}
<tr>
<td><a href="{% url 'Settings:_port_view' item.pk %}">{{ port }}</a></td>
<td>{{ port.description }}</td>
</tr>
{% endfor %}
{% elif not item.port.all and item.template %}
{% for port in item.template.port.all %}
<tr>
<td><a href="{% url 'Settings:_port_view' item.pk %}">{{ port }}</a></td>
<td>{{ port.description }}</td>
</tr>
{% endfor%}
{% else %}
<tr>
<td colspan="2"> Nothing Found</td>
</tr>
{% endif %}
</table>
</div>
<div style="display: block; width: 100%;">
<h3>Dependent Services</h3>
<table>
<tr>
<th>Name</th>
<th>Organization</th>
</tr>
{% if item.dependent_service.all %}
{% for service in item.dependent_service.all %}
<tr>
<td><a href="{% url 'ITIM:_service_view' service.pk %}">{{ service }}</a></td>
<td>{{ service.organization }}</td>
</tr>
{% endfor%}
{% else %}
<tr>
<td colspan="2"> Nothing Found</td>
</tr>
{% endif %}
</table>
</div>
<div style="display: block; width: 100%;">
<h3>Service Config</h3>
<br>
<textarea cols="90" rows="30" readonly>{{ item.config | json_pretty }}</textarea>
</div>
<br>
<script>
document.getElementById("defaultOpen").click();
</script>
</div>
<div id="Rendered_Config" class="tabcontent">
<h3>
Rendered Config
</h3>
<div>
<textarea cols="90" rows="30" readonly>{{ item.config_variables | json_pretty }}</textarea>
</div>
</div>
{% if perms.assistance.change_knowledgebase %}
<div id="Notes" class="tabcontent">
<h3>
Notes
</h3>
{{ notes_form }}
<input type="submit" name="{{ notes_form.prefix }}" value="Submit" />
<div class="comments">
{% if notes %}
{% for note in notes %}
{% include 'note.html.j2' %}
{% endfor %}
{% endif %}
</div>
</div>
{% endif %}
</form>
</div>
{% endblock %}

View File

@ -6,7 +6,7 @@ from core.forms.comment import AddNoteForm
from core.models.notes import Notes
from core.views.common import AddView, ChangeView, DeleteView, IndexView
from itim.forms.services import ServiceForm
from itim.forms.services import ServiceForm, DetailForm
from itim.models.services import Service
from settings.models.user_settings import UserSettings
@ -136,7 +136,7 @@ class View(ChangeView):
context_object_name = "item"
form_class = ServiceForm
form_class = DetailForm
model = Service

View File

@ -65,6 +65,119 @@ input[type=submit] {
height: 30px;
margin: 10px;
}
/* Style the navigation tabs at the top of a content page */
.content-navigation-tabs {
display: block;
overflow: hidden;
border-bottom: 1px solid #ccc;
/* background-color: #f1f1f1; */
width: 100%;
text-align: left;
padding: 0px;
margin: 0px
}
.content-navigation-tabs-link {
border: 0px;
margin: none;
padding: none;
}
/* Style the buttons that are used to open the tab content */
.content-navigation-tabs button {
display: inline;
background-color: inherit;
float: left;
border: none;
outline: none;
cursor: pointer;
margin: 0px;
padding: 0px;
padding: 14px 16px;
transition: 0.3s;
font-size: inherit;
color: #6a6e73;
}
/* Change background color of buttons on hover */
.content-navigation-tabs button:hover {
/* background-color: #ddd; */
border-bottom: 3px solid #ccc;
}
/* Create an active/current tablink class */
.content-navigation-tabs button.active {
/* background-color: #ccc; */
border-bottom: 3px solid #177ee6;
}
/* Style content for each tab */
.content-tab {
width: 100%;
display: none;
padding-bottom: 0px;
border: none;
border-top: none;
}
.content-tab hr {
border: none;
border-top: 1px solid #ccc;
}
.content-tab pre {
word-wrap: break-word;
white-space: pre-wrap;
}
/* Style for section fields on details page */
.detail-view-field {
display: unset;
height: 30px;
line-height: 30px;
padding: 0px 20px 40px 20px;
}
.detail-view-field label {
display: inline-block;
font-weight: bold;
width: 200px;
margin: 10px;
height: 30px;
line-height: 30px;
}
.detail-view-field span {
display: inline-block;
width: 340px;
margin: 10px;
border-bottom: 1px solid #ccc;
height: 30px;
line-height: 30px;
}
/*******************************************************************************
EoF refactored
@ -124,61 +237,6 @@ input[type=checkbox]:checked::after {
/* Style the tab */
.tab {
display: block;
overflow: hidden;
border-bottom: 1px solid #ccc;
/* background-color: #f1f1f1; */
width: 100%;
text-align: left;
padding: 0px;
margin: 0px
}
.tablinks {
border: 0px;
margin: none;
padding: none;
}
/* Style the buttons that are used to open the tab content */
.tab button {
display: inline;
background-color: inherit;
float: left;
border: none;
outline: none;
cursor: pointer;
margin: 0px;
padding: 0px;
padding: 14px 16px;
transition: 0.3s;
font-size: inherit;
color: #6a6e73;
}
/* Change background color of buttons on hover */
.tab button:hover {
/* background-color: #ddd; */
border-bottom: 3px solid #ccc;
}
/* Create an active/current tablink class */
.tab button.active {
/* background-color: #ccc; */
border-bottom: 3px solid #177ee6;
}
/* Style the tab content */
.tabcontent {
width: 100%;
display: none;
/* padding: 6px 12px; */
padding-bottom: 0px;
border: none;
border-top: none;
}
table {
width: 100%;

View File

@ -0,0 +1,16 @@
function openContentNavigationTab(evt, TabName) {
var i, tabcontent, tablinks;
tabcontent = document.getElementsByClassName("content-tab");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
tablinks = document.getElementsByClassName("content-navigation-tabs-link");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
document.getElementById(TabName).style.display = "block";
evt.currentTarget.className += " active";
}

View File

@ -14,6 +14,7 @@
<link rel="stylesheet" href="{% static 'base.css' %}">
<link rel="stylesheet" href="{% static 'code.css' %}">
<link rel="stylesheet" href="{% static 'content.css' %}">
<script src="{% static 'functions.js' %}"></script>
{% endif %}
</head>

View File

@ -0,0 +1,72 @@
{% load json %}
{% load markdown %}
{% if field.widget_type == 'textarea' or field.label == 'Notes' %}
{% if field.name in section.json and field.value %}
<pre style="width: 80%; text-align: left; display:inline; border: 1px solid #ccc; padding: 22px;">{{ field.value.strip | json_pretty | safe }}</pre>
{% elif field.name in section.markdown or field.label == 'Notes' %}
{% if field.label == 'Notes' %}
<div>
<label style="font-weight: bold; width: 100%; border-bottom: 1px solid #ccc; display: block; text-align: inherit;">
{{ field.label }}
</label>
<div style="display: inline-block; text-align: left;">
{% if field.value %}
{{ field.value | markdown | safe }}
{% else %}
&nbsp;
{% endif %}
</div>
</div>
{% else %}
{% if field.value %}
{{ field.value | markdown | safe }}
{% else %}
&nbsp;
{% endif %}
{% endif %}
{% elif not field.value %}
&nbsp;
{% endif %}
{% else %}
<div class="detail-view-field">
<label>{{ field.label }}</label>
<span>
{% if field.field.choices %} {# Display the selected choice text value #}
{% for id, value in field.field.choices %}
{% if field.value == id %}
{{ value }}
{% endif %}
{%endfor%}
{% else %}
{{ field.value }}
{% endif %}
</span>
</div>
{% endif %}

View File

@ -0,0 +1,93 @@
{% load json %}
{% load markdown %}
{% for section in tab.sections %}
{% if forloop.first %}
<h3>{{ tab.name }}</h3>
{% else %}
<hr />
<h3>{{ section.name }}</h3>
{% endif %}
<div style="align-items:flex-start; align-content: center; display: flexbox; width: 100%">
{% if section.layout == 'single' %}
{% for section_field in section.fields %}
{% for field in form %}
{% if field.name in section_field %}
{% include 'content/field.html.j2' %}
{% endif %}
{% endfor %}
{% endfor %}
{% elif section.layout == 'double' %}
{% if section.left %}
<div style="display: inline; width: 40%; margin: 30px;">
{% for section_field in section.left %}
{% for field in form %}
{% if field.name in section_field %}
{% include 'content/field.html.j2' %}
{% endif %}
{% endfor %}
{% endfor %}
</div>
{% endif %}
{% if section.right %}
<div style="display: inline; width: 40%; margin: 30px; text-align: left;">
{% for section_field in section.right %}
{% for field in form %}
{% if field.name in section_field %}
{% include 'content/field.html.j2' %}
{% endif %}
{% endfor %}
{% endfor %}
</div>
{% endif %}
{% endif %}
{% if forloop.first %}
{% if tab.edit_url %}
<div style="display:block;">
<input type="button" value="Edit" onclick="window.location='{{ tab.edit_url }}';">
</div>
{% endif %}
{% endif %}
</div>
{% endfor %}

View File

@ -0,0 +1,47 @@
{% extends 'base.html.j2' %}
{% block content %}
<div class="content-navigation-tabs">
<button onclick="window.location='{% url 'ITIM:Services' %}';" style="vertical-align: middle; padding: auto; margin: 0px">
<svg xmlns="http://www.w3.org/2000/svg" height="25px" viewBox="0 -960 960 960" width="25px"
style="vertical-align: middle; margin: 0px; padding: 0px border: none; " fill="#6a6e73">
<path d="m313-480 155 156q11 11 11.5 27.5T468-268q-11 11-28 11t-28-11L228-452q-6-6-8.5-13t-2.5-15q0-8 2.5-15t8.5-13l184-184q11-11 27.5-11.5T468-692q11 11 11 28t-11 28L313-480Zm264 0 155 156q11 11 11.5 27.5T732-268q-11 11-28 11t-28-11L492-452q-6-6-8.5-13t-2.5-15q0-8 2.5-15t8.5-13l184-184q11-11 27.5-11.5T732-692q11 11 11 28t-11 28L577-480Z" />
</svg>
Back to Services
</button>
{% for key, tab in form.tabs.items %}
{% if forloop.first %}
<button id="defaultOpen" class="content-navigation-tabs-link" onclick="openContentNavigationTab(event, '{{ tab.slug }}')">{{ tab.name }}</button>
{% else %}
<button id="tab-{{ tab.slug }}" class="content-navigation-tabs-link" onclick="openContentNavigationTab(event, '{{ tab.slug }}')">{{ tab.name }}</button>
{% endif %}
{% endfor %}
</div>
{% block tabs %}{% endblock %}
{% if open_tab %}
<script>
document.getElementById("tab-{{ open_tab }}").click();
</script>
{% else %}
<script>
document.getElementById("defaultOpen").click();
</script>
{% endif %}
{% endblock %}

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M21.7 18.6V17.6L22.8 16.8C22.9 16.7 23 16.6 22.9 16.5L21.9 14.8C21.9 14.7 21.7 14.7 21.6 14.7L20.4 15.2C20.1 15 19.8 14.8 19.5 14.7L19.3 13.4C19.3 13.3 19.2 13.2 19.1 13.2H17.1C16.9 13.2 16.8 13.3 16.8 13.4L16.6 14.7C16.3 14.9 16.1 15 15.8 15.2L14.6 14.7C14.5 14.7 14.4 14.7 14.3 14.8L13.3 16.5C13.3 16.6 13.3 16.7 13.4 16.8L14.5 17.6V18.6L13.4 19.4C13.3 19.5 13.2 19.6 13.3 19.7L14.3 21.4C14.4 21.5 14.5 21.5 14.6 21.5L15.8 21C16 21.2 16.3 21.4 16.6 21.5L16.8 22.8C16.9 22.9 17 23 17.1 23H19.1C19.2 23 19.3 22.9 19.3 22.8L19.5 21.5C19.8 21.3 20 21.2 20.3 21L21.5 21.4C21.6 21.4 21.7 21.4 21.8 21.3L22.8 19.6C22.9 19.5 22.9 19.4 22.8 19.4L21.7 18.6M18 19.5C17.2 19.5 16.5 18.8 16.5 18S17.2 16.5 18 16.5 19.5 17.2 19.5 18 18.8 19.5 18 19.5M12.3 22H3C1.9 22 1 21.1 1 20V4C1 2.9 1.9 2 3 2H21C22.1 2 23 2.9 23 4V13.1C22.4 12.5 21.7 12 21 11.7V6H3V20H11.3C11.5 20.7 11.8 21.4 12.3 22Z" /></svg>

After

Width:  |  Height:  |  Size: 958 B

View File

@ -49,6 +49,8 @@ span.navigation_icon {
{% include 'icons/devices.svg' %}
{% elif group_urls.name == 'Knowledge Base' %}
{% include 'icons/information.svg' %}
{% elif group_urls.name == 'Services' %}
{% include 'icons/service.svg' %}
{% elif group_urls.name == 'Software' %}
{% include 'icons/software.svg' %}
{% elif group_urls.name == 'Groups' %}

View File

@ -51,6 +51,18 @@ All forms must meet the following requirements:
```
## Details Form
A details form is for the display of a models data. This form should inherit from a base form and contain any additional fields as is required for the display of the models data. Additional requirements are as follows:
- `tab` is defined as a `dict` within the class. _See [Template](./templates.md#detail)._
- There is an `__init__` class defined that sets up the additional fields.
!!! danger "Requirement"
Ensure that there is a call to the super-class `__init__` method so that the form is correctly initialised. i.e. `super().__init__(*args, **kwargs)`
## Abstract Classes

View File

@ -19,6 +19,8 @@ Centurion ERP is a Django Application. We have added a lot of little tid bits th
- [Models](./models.md)
- [Templates](./templates.md)
- [Testing](./testing.md)
- [Views](./views.md)

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -0,0 +1,46 @@
<mxfile host="app.diagrams.net" modified="2024-08-13T04:59:07.625Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" etag="UnD9NsC2nlQotuYi3Jgi" version="24.4.13" type="device">
<diagram name="Page-1" id="43mCgrILUMj9ztUM_gXM">
<mxGraphModel dx="1434" dy="766" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="S1pZBpeQw_pKLN31ISmP-2" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="110" y="80" width="1000" height="520" as="geometry" />
</mxCell>
<mxCell id="S1pZBpeQw_pKLN31ISmP-3" value="Page Header" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
<mxGeometry x="110" y="80" width="1000" height="60" as="geometry" />
</mxCell>
<mxCell id="S1pZBpeQw_pKLN31ISmP-4" value="Navigation&lt;div&gt;&lt;br&gt;&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
<mxGeometry x="110" y="140" width="200" height="460" as="geometry" />
</mxCell>
<mxCell id="S1pZBpeQw_pKLN31ISmP-5" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" vertex="1" parent="1">
<mxGeometry x="310" y="140" width="800" height="460" as="geometry" />
</mxCell>
<mxCell id="S1pZBpeQw_pKLN31ISmP-6" value="Page Title" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#D1FCFF;strokeColor=#d6b656;" vertex="1" parent="1">
<mxGeometry x="310" y="140" width="800" height="60" as="geometry" />
</mxCell>
<mxCell id="S1pZBpeQw_pKLN31ISmP-7" value="Section Content, Single Column" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="320" y="210" width="780" height="150" as="geometry" />
</mxCell>
<mxCell id="S1pZBpeQw_pKLN31ISmP-8" value="Page Footer" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f8cecc;strokeColor=#b85450;" vertex="1" parent="1">
<mxGeometry x="310" y="560" width="800" height="40" as="geometry" />
</mxCell>
<mxCell id="S1pZBpeQw_pKLN31ISmP-9" value="Section Navigation Tabs" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1">
<mxGeometry x="320" y="210" width="780" height="30" as="geometry" />
</mxCell>
<mxCell id="S1pZBpeQw_pKLN31ISmP-10" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="320" y="370" width="780" height="150" as="geometry" />
</mxCell>
<mxCell id="S1pZBpeQw_pKLN31ISmP-11" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="390" y="370" width="320" height="150" as="geometry" />
</mxCell>
<mxCell id="S1pZBpeQw_pKLN31ISmP-15" value="" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="710" y="370" width="320" height="150" as="geometry" />
</mxCell>
<mxCell id="S1pZBpeQw_pKLN31ISmP-12" value="&lt;span style=&quot;color: rgb(0, 0, 0); font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: center; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(251, 251, 251); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;&quot;&gt;Section Content, Double Column&lt;/span&gt;" style="text;whiteSpace=wrap;html=1;" vertex="1" parent="1">
<mxGeometry x="620" y="425" width="210" height="40" as="geometry" />
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -0,0 +1,188 @@
---
title: Templates
description: Development documentation for template usage and layout for Centurion ERP by No Fuss Computing
date: 2024-08-13
template: project.html
about: https://gitlab.com/nofusscomputing/infrastructure/configuration-management/centurion_erp
---
This section of the documentation contains the details related to the templates used within Centurion ERP for rendering data for the end user to view.
## Templates
- Base
- Detail
## Base
The base template is common to all templates and is responsible for the rendering of the common layout. Each subsequent template includes this template. This enables **ALL** pages within the site to share the same layout.
![Base template layout](./media/layout-template-view-base.png)
Point of note is that the orange area of the template is what each template is "filling out."
This view contains the following areas:
- Page Header
_Site header._
- Navigation
_Site navigation._
- Page Title
_represents the "what" to the contents of the page. i.e. for a device this would be the device name._
- Content Area
_The views content_
- Page footer
_Site footer_
!!! note
This template should not be included directly as it is incomplete and requires subsequent templates to populate the contents of the orange area.
## Detail
This template is intended to be used to render the details of a single model. The layout of the detail view is as follows:
![detail layout](./media/layout-template-view-detail.png)
This view contains the following areas:
- Section navigation tabs
- Section Content
A detail page contains navigation tabs to aid in displaying multiple facets of an item, with each "tabbed" page containing one or more sections. Point of note is that the tabs are only rendered within the top section of each "tabbed" page.
Base definition for defining a detail page is as follows:
``` jinja
{% extends 'detail.html.j2' %}
{% load json %}
{% load markdown %}
{% block tabs %}
your tabs content here
{% endblock %}
```
!!! tip
Need to navigate directly to a tab, add `tab=<slug>` to the url query string
### Providing data for the view
For the view to render the page, you must define the data as part of the form class.
The variable name to use is `tabs` The layout/schema is as follows:
#### Full Example
This example is a full example with two tabs: `details` and `rendered_config`
``` python
tabs: dict = {
"details": {
"name": "Details",
"slug": "details",
"sections": [
{
"layout": "double",
"left": [
'name',
'config_key_variable',
'template',
'organization',
'c_created',
'c_modified'
],
"right": [
'model_notes',
]
}
]
},
"rendered_config": {
"name": "Rendered Config",
"slug": "rendered_config",
"sections": [
{
"layout": "single",
"fields": [
'config_variables',
],
"json": [
'config_variables'
]
}
]
}
}
```
additional fields can be defined as part of the form `__init__` method.
``` python
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['config_variables'] = forms.fields.JSONField(
widget = forms.Textarea(
attrs = {
"cols": "80",
"rows": "100"
}
),
label = 'Rendered Configuration',
initial = self.instance.config_variables,
)
self.fields['c_created'] = forms.DateTimeField(
label = 'Created',
input_formats=settings.DATETIME_FORMAT,
disabled = True,
initial = self.instance.created,
)
self.fields['c_modified'] = forms.DateTimeField(
label = 'Modified',
input_formats=settings.DATETIME_FORMAT,
disabled = True,
initial = self.instance.modified,
)
```
You can add an edit button to any tab by defining the following as part of the `__init__` method:
``` py
self.tabs['details'].update({
"edit_url": reverse('ITIM:_service_change', args=(self.instance.pk,))
})
```
in this example, the details tab will display an `Edit` button. The `Edit` button will only display at the end of the first section of any tab it has been defined for.

View File

@ -34,6 +34,8 @@ Centurion ERP contains the following modules:
- [IT Asset Management (ITAM)](./user/itam/index.md)
- [Knowledge Base](./user/assistance/knowledge_base.md)
- **Core Features:**
@ -75,75 +77,71 @@ Below is a list of modules/features we intend to add to Centurion. To find out w
- **Planned Modules:**
- Accounting _[see #88](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/88)_
- Accounting _[see #88](https://github.com/nofusscomputing/centurion_erp/issues/88)_
General Ledger - _[see #116](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/116)_
General Ledger - _[see #116](https://github.com/nofusscomputing/centurion_erp/issues/116)_
- Asset Management _[see #89](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/88)_
- Asset Management _[see #89](https://github.com/nofusscomputing/centurion_erp/issues/88)_
- Change Management _[see #90](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/90)_
- Change Management _[see #90](https://github.com/nofusscomputing/centurion_erp/issues/90)_
- Config Management
- Host Config _[see #44](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/44)_
- Core
- Location Management (Regions, Sites and Locations) _[see #62](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/62)_
- Location Management (Regions, Sites and Locations) _[see #62](https://github.com/nofusscomputing/centurion_erp/issues/62)_
- Customer Relationship Management (CRM) _[see #91](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/91)_
- Customer Relationship Management (CRM) _[see #91](https://github.com/nofusscomputing/centurion_erp/issues/91)_
- Database Management _[see #72](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/72)_
- Database Management _[see #72](https://github.com/nofusscomputing/centurion_erp/issues/72)_
- Development Operations (DevOPS) _[see #68](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/58)_
- Development Operations (DevOPS) _[see #68](https://github.com/nofusscomputing/centurion_erp/issues/58)_
- Repository Management _[see #115](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/115)_
- Repository Management _[see #115](https://github.com/nofusscomputing/centurion_erp/issues/115)_
- Human Resource Management _[see #92](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/92)_
- Human Resource Management _[see #92](https://github.com/nofusscomputing/centurion_erp/issues/92)_
- Incident Management _[see #93](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/93)_
- Information Management _[see #10](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/10)_
- Incident Management _[see #93](https://github.com/nofusscomputing/centurion_erp/issues/93)_
- IT Asset Management (ITAM)
- Licence Management _[see #4](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/4)_
- Licence Management _[see #4](https://github.com/nofusscomputing/centurion_erp/issues/4)_
- IT Infrastructure Management (ITIM) _[see #61](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/61)_
- IT Infrastructure Management (ITIM) _[see #61](https://github.com/nofusscomputing/centurion_erp/issues/61)_
- Cluster Management _[see #71](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/71)_
- Cluster Management _[see #71](https://github.com/nofusscomputing/centurion_erp/issues/71)_
- Database Management _[see #72](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/72)_
- Database Management _[see #72](https://github.com/nofusscomputing/centurion_erp/issues/72)_
- Service Management _[see #19](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/19)_
- Service Management _[see #19](https://github.com/nofusscomputing/centurion_erp/issues/19)_
- Software Package Management _[see #96](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/96)_
- Software Package Management _[see #96](https://github.com/nofusscomputing/centurion_erp/issues/96)_
- Role Management _[see #70](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/70)_
- Role Management _[see #70](https://github.com/nofusscomputing/centurion_erp/issues/70)_
- Virtual Machine Management _[see #73](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/73)_
- Virtual Machine Management _[see #73](https://github.com/nofusscomputing/centurion_erp/issues/73)_
- Vulnerability Management
- Software _[see #3](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/3)_
- Software _[see #3](https://github.com/nofusscomputing/centurion_erp/issues/3)_
- Order Management _[see #94](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/94)_
- Order Management _[see #94](https://github.com/nofusscomputing/centurion_erp/issues/94)_
- Supplier Management _[see #123](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/123)_
- Supplier Management _[see #123](https://github.com/nofusscomputing/centurion_erp/issues/123)_
- Project Management _[see #14](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/14)_
- Project Management _[see #14](https://github.com/nofusscomputing/centurion_erp/issues/14)_
- Problem Management _[see #95](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/95)_
- Problem Management _[see #95](https://github.com/nofusscomputing/centurion_erp/issues/95)_
- Request Management _[see #96](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/96)_
- Request Management _[see #96](https://github.com/nofusscomputing/centurion_erp/issues/96)_
- **Planned Integrations:**
- ArgoCD _[see #77](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/77)_
- ArgoCD _[see #77](https://github.com/nofusscomputing/centurion_erp/issues/77)_
[ArgoCD](https://github.com/argoproj-labs) is a Continuous Deployment system for ensuring objects deployed to kubernetes remain in the desired state.
- AWX _[see #113](https://gitlab.com/nofusscomputing/projects/django_template/-/issues/113)_
- AWX _[see #113](https://github.com/nofusscomputing/centurion_erp/issues/113)_
[AWX](https://github.com/ansible/awx) is an Automation Orchestration system that uses Ansible for its configuration.

View File

@ -10,3 +10,9 @@ This component within ITIM is an extension of Service Management, in particular
## Ports
- number _A Valid port number [1-65535]_
- description _Short description of the port_
- protocol _OSI Layer 4 protocol [TCP or UDP]_

View File

@ -6,7 +6,36 @@ template: project.html
about: https://gitlab.com/nofusscomputing/infrastructure/configuration-management/centurion_erp
---
This component within ITIM is intended to enable the management of services deployed throughout your IT infrastructure. A service is defined as anything that is deployed that end users would access via a client application.
This component within ITIM is intended to enable the management of services deployed throughout your IT infrastructure. A service is defined as anything that is deployed that end users would access via a client application. The design of our services is intended to work with either ansible collections or roles. Either way as long as the resulting ansible tasks look for the variables under a single key for the service; any method you choose to deploy a service is up to you.
## Services
Within the services the following fields are available:
- is_template _Defines the service as a template_
- template _name if the template that this service inherits from_
- name _name of the service_
- device _Device service deployed to_
- cluster _Cluster the service is deployed to_
- config _Ansible configuration variables_
- config_key_variable _Ansible dictionary key name for config_
- [port](./port.md) _Ports assosiated with the service_
- dependent_service _A List of services this service depends upon_
## Service Template
A service can be setup as a template `is_template=True` for which then can be used as the base template for further service creations. Both config and Ports are inherited from the template with any conflict taking the current services values.
## Deployed to
A service can be deployed to a Cluster or a Device. When assosiated with an item, within it's details page the services deployed to it are available.

View File

@ -152,6 +152,8 @@ nav:
- projects/centurion_erp/development/models.md
- projects/centurion_erp/development/templates.md
- projects/centurion_erp/development/testing.md
- projects/centurion_erp/development/views.md