diff --git a/netbox/ipam/tables.py b/netbox/ipam/tables.py index acfde4403..2c3d95c88 100644 --- a/netbox/ipam/tables.py +++ b/netbox/ipam/tables.py @@ -33,7 +33,7 @@ IPADDRESS_LINK = """ {% if record.pk %} {{ record.address }} {% elif perms.ipam.add_ipaddress %} - {% if record.0 <= 65536 %}{{ record.0 }}{% else %}Many{% endif %} IP{{ record.0|pluralize }} available + {% if record.0 <= 65536 %}{{ record.0 }}{% else %}Many{% endif %} IP{{ record.0|pluralize }} available {% else %} {% if record.0 <= 65536 %}{{ record.0 }}{% else %}Many{% endif %} IP{{ record.0|pluralize }} available {% endif %} @@ -65,7 +65,7 @@ VLAN_LINK = """ {% if record.pk %} {{ record.vid }} {% elif perms.ipam.add_vlan %} - {{ record.available }} VLAN{{ record.available|pluralize }} available + {{ record.available }} VLAN{{ record.available|pluralize }} available {% else %} {{ record.available }} VLAN{{ record.available|pluralize }} available {% endif %} @@ -90,7 +90,7 @@ VLAN_ROLE_LINK = """ VLANGROUP_ADD_VLAN = """ {% with next_vid=record.get_next_available_vid %} {% if next_vid and perms.ipam.add_vlan %} - + {% endif %} diff --git a/netbox/project-static/netbox.scss b/netbox/project-static/netbox.scss index 3737d5b24..1a8ddba7d 100644 --- a/netbox/project-static/netbox.scss +++ b/netbox/project-static/netbox.scss @@ -245,7 +245,8 @@ span.color-label { } textarea#id_local_context_data, -textarea.markdown { +textarea.markdown, +textarea#id_public_key { font-family: $font-family-monospace; } @@ -355,3 +356,7 @@ span.bi-plus:before { table tbody tr.success { background-color: rgba($success, 0.15); } +table td, +table th { + font-size: $font-size-sm; +} diff --git a/netbox/project-static/src/clipboard.ts b/netbox/project-static/src/clipboard.ts new file mode 100644 index 000000000..f83936346 --- /dev/null +++ b/netbox/project-static/src/clipboard.ts @@ -0,0 +1,8 @@ +import Clipboard from 'clipboard'; +import { getElements } from './util'; + +export function initClipboard() { + for (const element of getElements('a.copy-token', 'button.copy-secret')) { + new Clipboard(element); + } +} diff --git a/netbox/project-static/src/global.d.ts b/netbox/project-static/src/global.d.ts index 76075f813..ad56feba8 100644 --- a/netbox/project-static/src/global.d.ts +++ b/netbox/project-static/src/global.d.ts @@ -26,6 +26,11 @@ type APIObjectBase = { [k: string]: JSONAble; }; +type APIKeyPair = { + public_key: string; + private_key: string; +}; + type APIReference = { id: number; name: string; diff --git a/netbox/project-static/src/index.ts b/netbox/project-static/src/index.ts index 3f44693bd..7c6a20b6b 100644 --- a/netbox/project-static/src/index.ts +++ b/netbox/project-static/src/index.ts @@ -1,5 +1,4 @@ import 'babel-polyfill'; import '@popperjs/core'; import 'bootstrap'; -import 'clipboard'; import './netbox'; diff --git a/netbox/project-static/src/netbox.ts b/netbox/project-static/src/netbox.ts index 383cef002..a8d9023fa 100644 --- a/netbox/project-static/src/netbox.ts +++ b/netbox/project-static/src/netbox.ts @@ -1,11 +1,13 @@ -import { Tooltip } from 'bootstrap'; +import { Modal, Tooltip } from 'bootstrap'; import Masonry from 'masonry-layout'; import { initApiSelect, initStaticSelect, initColorSelect } from './select'; import { initDateSelector } from './dateSelector'; import { initMessageToasts } from './toast'; import { initSpeedSelector, initForms } from './forms'; import { initRackElevation } from './buttons'; +import { initClipboard } from './clipboard'; import { initSearchBar } from './search'; +// import { initGenerateKeyPair } from './secrets'; import { getElements } from './util'; const INITIALIZERS = [ @@ -18,6 +20,8 @@ const INITIALIZERS = [ initSpeedSelector, initColorSelect, initRackElevation, + initClipboard, + // initGenerateKeyPair, ] as (() => void)[]; /** @@ -30,6 +34,10 @@ function initBootstrap(): void { for (const tooltip of getElements('[data-bs-toggle="tooltip"]')) { new Tooltip(tooltip, { container: 'body', boundary: 'window' }); } + for (const modal of getElements('[data-bs-toggle="modal"]')) { + // for (const modal of getElements('div.modal')) { + new Modal(modal); + } initMessageToasts(); initForms(); } diff --git a/netbox/project-static/src/secrets.ts b/netbox/project-static/src/secrets.ts new file mode 100644 index 000000000..88c255fc7 --- /dev/null +++ b/netbox/project-static/src/secrets.ts @@ -0,0 +1,62 @@ +import { apiGetBase, getElements, isApiError } from './util'; +/** + * + * $('#generate_keypair').click(function() { + $('#new_keypair_modal').modal('show'); + $.ajax({ + url: netbox_api_path + 'secrets/generate-rsa-key-pair/', + type: 'GET', + dataType: 'json', + success: function (response, status) { + var public_key = response.public_key; + var private_key = response.private_key; + $('#new_pubkey').val(public_key); + $('#new_privkey').val(private_key); + }, + error: function (xhr, ajaxOptions, thrownError) { + alert("There was an error generating a new key pair."); + } + }); + }); + */ +export function initGenerateKeyPair() { + const element = document.getElementById('new_keypair_modal') as HTMLDivElement; + const accept = document.getElementById('use_new_pubkey') as HTMLButtonElement; + const publicElem = element.querySelector('textarea#new_pubkey'); + const privateElem = element.querySelector('textarea#new_privkey'); + + function handleOpen() { + for (const elem of [publicElem, privateElem]) { + if (elem !== null) { + elem.setAttribute('readonly', ''); + } + } + + apiGetBase('/api/secrets/generate-rsa-key-pair').then(data => { + if (!isApiError(data)) { + const { private_key: priv, public_key: pub } = data; + if (publicElem !== null && privateElem !== null) { + publicElem.value = pub; + privateElem.value = priv; + } + } + }); + } + function handleAccept() { + const publicKeyField = document.getElementById('id_public_key') as HTMLTextAreaElement; + if (publicElem !== null) { + publicKeyField.value = publicElem.value; + publicKeyField.innerText = publicElem.value; + } + } + element.addEventListener('shown.bs.modal', handleOpen); + accept.addEventListener('click', handleAccept); +} + +export function initLockUnlock() { + for (const element of getElements('button.unlock-secret')) { + function handleClick() { + const { secretId } = element.dataset; + } + } +} diff --git a/netbox/project-static/src/util.ts b/netbox/project-static/src/util.ts index 0194b1039..b8cf37fdd 100644 --- a/netbox/project-static/src/util.ts +++ b/netbox/project-static/src/util.ts @@ -32,6 +32,18 @@ export function getCsrfToken(): string { return csrfToken; } +export async function apiGetBase>( + url: string, +): Promise { + const token = getCsrfToken(); + const res = await fetch(url, { + method: 'GET', + headers: { 'X-CSRFToken': token }, + }); + const json = (await res.json()) as T | APIError; + return json; +} + /** * Fetch data from the NetBox API (authenticated). * @param url API endpoint @@ -39,13 +51,7 @@ export function getCsrfToken(): string { export async function getApiData( url: string, ): Promise | APIError> { - const token = getCsrfToken(); - const res = await fetch(url, { - method: 'GET', - headers: { 'X-CSRFToken': token }, - }); - const json = (await res.json()) as APIAnswer | APIError; - return json; + return await apiGetBase>(url); } export function getElements( diff --git a/netbox/secrets/views.py b/netbox/secrets/views.py index 0ae2f6231..1bc54a3b6 100644 --- a/netbox/secrets/views.py +++ b/netbox/secrets/views.py @@ -73,7 +73,7 @@ class SecretListView(generic.ObjectListView): filterset = filters.SecretFilterSet filterset_form = forms.SecretFilterForm table = tables.SecretTable - action_buttons = ('import', 'export') + action_buttons = ('add', 'import', 'export') class SecretView(generic.ObjectView): diff --git a/netbox/templates/dcim/device/base.html b/netbox/templates/dcim/device/base.html index 496d33bbe..6e0e89503 100644 --- a/netbox/templates/dcim/device/base.html +++ b/netbox/templates/dcim/device/base.html @@ -215,16 +215,4 @@ {% endif %} -{% if perms.extras.view_objectchange %} - -{% endif %} {% endblock %} diff --git a/netbox/templates/dcim/inc/cable_toggle_buttons.html b/netbox/templates/dcim/inc/cable_toggle_buttons.html index e3eb318ac..586e60713 100644 --- a/netbox/templates/dcim/inc/cable_toggle_buttons.html +++ b/netbox/templates/dcim/inc/cable_toggle_buttons.html @@ -1,10 +1,10 @@ {% if perms.dcim.change_cable %} {% if cable.status == 'connected' %} - + {% else %} - + {% endif %} diff --git a/netbox/templates/dcim/powerfeed.html b/netbox/templates/dcim/powerfeed.html index db6155597..dc114040e 100644 --- a/netbox/templates/dcim/powerfeed.html +++ b/netbox/templates/dcim/powerfeed.html @@ -6,13 +6,13 @@ {% load plugins %} {% block breadcrumbs %} -
  • Power Feeds
  • -
  • {{ object.power_panel.site }}
  • -
  • {{ object.power_panel }}
  • + + + {% if object.rack %} -
  • {{ object.rack }}
  • + {% endif %} -
  • {{ object }}
  • + {% endblock %} {% block content %} @@ -25,13 +25,13 @@
    - + - + - + - + - + - + {% with utilization=object.connected_endpoint.get_power_draw %} {% if utilization %}
    Power PanelPower Panel {{ object.power_panel }}
    RackRack {% if object.rack %} {{ object.rack }} @@ -41,19 +41,19 @@
    TypeType {{ object.get_type_display }}
    StatusStatus {{ object.get_status_display }}
    Connected DeviceConnected Device {% if object.connected_endpoint %} {{ object.connected_endpoint.device }} ({{ object.connected_endpoint }}) @@ -63,7 +63,7 @@
    Utilization (Allocated)Utilization (Allocated) @@ -87,23 +87,23 @@
    - + - + - + - + - +
    SupplySupply {{ object.get_supply_display }}
    VoltageVoltage {{ object.voltage }}V
    AmperageAmperage {{ object.amperage }}A
    PhasePhase {{ object.get_phase_display }}
    Max UtilizationMax Utilization {{ object.max_utilization }}%
    @@ -126,7 +126,7 @@ {% elif object.cable %} - + {% if object.connected_endpoint %} - + - + - + - + - +
    CableCable {{ object.cable }} @@ -136,27 +136,27 @@
    DeviceDevice {{ object.connected_endpoint.device }}
    NameName {{ object.connected_endpoint.name }}
    TypeType {{ object.connected_endpoint.get_type_display|placeholder }}
    DescriptionDescription {{ object.connected_endpoint.description|placeholder }}
    Path StatusPath Status {% if object.path.is_active %} Reachable @@ -169,15 +169,20 @@
    {% else %}
    - {% if perms.dcim.add_cable %} - - Connect - - {% endif %} Not connected
    {% endif %}
    + {% if not object.mark_connected and not object.cable %} + + {% endif %}
    diff --git a/netbox/templates/dcim/powerpanel.html b/netbox/templates/dcim/powerpanel.html index e906aec90..febab7e0c 100644 --- a/netbox/templates/dcim/powerpanel.html +++ b/netbox/templates/dcim/powerpanel.html @@ -3,39 +3,41 @@ {% load plugins %} {% block breadcrumbs %} -
  • Power Panels
  • -
  • {{ object.site }}
  • + + {% if object.location %} -
  • {{ object.location }}
  • + {% endif %} -
  • {{ object }}
  • + {% endblock %} {% block content %}
    -
    -
    - Power Panel +
    +
    + Power Panel +
    +
    + + + + + + + + + +
    Site + {{ object.site }} +
    Location + {% if object.location %} + {{ object.location }} + {% else %} + None + {% endif %} +
    - - - - - - - - - -
    Site - {{ object.site }} -
    Location - {% if object.location %} - {{ object.location }} - {% else %} - None - {% endif %} -
    {% plugin_left_page object %}
    @@ -45,7 +47,7 @@ {% plugin_right_page object %}
    -
    +
    {% include 'panel_table.html' with table=powerfeed_table heading='Connected Feeds' %} {% plugin_full_width_page object %} diff --git a/netbox/templates/extras/object_changelog.html b/netbox/templates/extras/object_changelog.html index 64f76fe60..48df5161f 100644 --- a/netbox/templates/extras/object_changelog.html +++ b/netbox/templates/extras/object_changelog.html @@ -1,6 +1,6 @@ {% extends base_template %} -{% block title %}{{ block.super }} - Change Log{% endblock %} +{% block title %}{{ object }} - Change Log{% endblock %} {% block content %} {% include 'panel_table.html' %} diff --git a/netbox/templates/generic/object_edit.html b/netbox/templates/generic/object_edit.html index ab18c13a4..cfe334b2a 100644 --- a/netbox/templates/generic/object_edit.html +++ b/netbox/templates/generic/object_edit.html @@ -7,8 +7,8 @@ + + + +
    +
    {% render_field form.region %} {% render_field form.site_group %} @@ -33,17 +53,15 @@ {% render_field form.cluster_group %} {% render_field form.cluster %}
    + The VLAN group will be limited in scope to the most-specific object selected above.
    - The VLAN group will be limited in scope to the most-specific object selected above. - {% endwith %} -
    +
    + {% endwith %}
    {% if form.custom_fields %} -
    -
    Custom Fields
    -
    - {% render_custom_fields form %} -
    +
    +

    Custom Fields

    + {% render_custom_fields form %}
    {% endif %} {% endblock %} diff --git a/netbox/templates/ipam/vlangroup_vlans.html b/netbox/templates/ipam/vlangroup_vlans.html index ffb75d3b8..334fddc2d 100644 --- a/netbox/templates/ipam/vlangroup_vlans.html +++ b/netbox/templates/ipam/vlangroup_vlans.html @@ -1,24 +1,41 @@ -{% extends 'base.html' %} +{% extends 'generic/object.html' %} {% block title %}{{ object }} - VLANs{% endblock %} -{% block content %} -
    -
    - -
    -
    - {% include 'ipam/inc/vlangroup_header.html' %} -
    -
    - {% include 'utilities/obj_table.html' with table=vlan_table table_template='panel_table.html' heading='VLANs' bulk_edit_url='ipam:vlan_bulk_edit' bulk_delete_url='ipam:vlan_bulk_delete' %} -
    -
    +{% block controls %} +{% if perms.ipam.add_vlan and first_available_vlan %} + + Add a VLAN + +{% endif %} +{% if perms.ipam.change_vlangroup %} + + Edit this VLAN Group + +{% endif %} +{% endblock %} + +{% block breadcrumbs %} + +{% if object.site %} + +{% endif %} + +{% endblock %} + +{% block content %} +
    +
    + {% include 'utilities/obj_table.html' with table=vlan_table table_template='panel_table.html' heading='VLANs' bulk_edit_url='ipam:vlan_bulk_edit' bulk_delete_url='ipam:vlan_bulk_delete' %} +
    +
    {% endblock %} diff --git a/netbox/templates/ipam/vrf.html b/netbox/templates/ipam/vrf.html index 627d6f88a..63c4046e9 100644 --- a/netbox/templates/ipam/vrf.html +++ b/netbox/templates/ipam/vrf.html @@ -7,8 +7,8 @@ {% block title %}VRF {{ object }}{% endblock %} {% block breadcrumbs %} -
  • VRFs
  • -
  • {{ object }}
  • + + {% endblock %} {% block content %} diff --git a/netbox/templates/profile_button.html b/netbox/templates/profile_button.html index 70b30c653..8e150ed78 100644 --- a/netbox/templates/profile_button.html +++ b/netbox/templates/profile_button.html @@ -15,11 +15,12 @@ Admin - {% else %} + {% endif %} + +
  • Secrets
  • -
  • {{ object.role }}
  • -
  • {{ object.assigned_object }}
  • -
  • {{ object }}
  • -{% endblock %} - -{% block buttons %} - {% if perms.secrets.change_secret %} - {% edit_button object %} - {% endif %} - {% if perms.secrets.delete_secret %} - {% delete_button object %} - {% endif %} + + + + {% endblock %} {% block content %}
    -
    -
    - Secret Attributes +
    +
    + Secret Attributes +
    +
    + + + + + + + + + + + + + +
    Assigned object + {{ object.assigned_object }} +
    Role{{ object.role }}
    Name{{ object.name|placeholder }}
    - - - - - - - - - - - - - -
    Assigned object - {{ object.assigned_object }} -
    Role{{ object.role }}
    Name{{ object.name|placeholder }}
    {% include 'inc/custom_fields_panel.html' %} {% plugin_left_page object %}
    -
    -
    - Secret Data -
    -
    +
    +
    + Secret Data +
    +
    {% csrf_token %}
    @@ -60,13 +53,13 @@
    Secret
    ********
    - - -
    @@ -85,7 +78,3 @@ {% include 'secrets/inc/private_key_modal.html' %} {% endblock %} - -{% block javascript %} - -{% endblock %} diff --git a/netbox/templates/secrets/secret_edit.html b/netbox/templates/secrets/secret_edit.html index b7e7ab785..b46555757 100644 --- a/netbox/templates/secrets/secret_edit.html +++ b/netbox/templates/secrets/secret_edit.html @@ -1,99 +1,88 @@ -{% extends 'base.html' %} +{% extends 'generic/object_edit.html' %} {% load static %} {% load form_helpers %} -{% block content %} -
    - {% csrf_token %} - {{ form.private_key }} -
    -
    -

    {% block title %}{% if obj.pk %}Editing {{ obj }}{% else %}Add a Secret{% endif %}{% endblock %}

    - {% if form.non_field_errors %} -
    -
    Errors
    -
    - {{ form.non_field_errors }} -
    -
    - {% endif %} -
    -
    - Secret Assignment -
    -
    - {% with vm_tab_active=form.initial.virtual_machine %} - -
    -
    - {% render_field form.device %} -
    -
    - {% render_field form.virtual_machine %} -
    -
    - {% endwith %} - {% render_field form.role %} - {% render_field form.name %} - {% render_field form.userkeys %} - {% render_field form.tags %} -
    +{% block title %}{% if obj.pk %}Editing {{ obj }}{% else %}Add a Secret{% endif %}{% endblock %} + +{% block form %} +{% render_errors form %} + + +{{ form.private_key }} +
    +

    Secret Assignment

    + + {% with vm_tab_active=form.initial.virtual_machine %} +
    +
    + {% render_field form.device %}
    -
    -
    Secret Data
    -
    - {% if obj.pk %} -
    - -
    -

    ********

    -
    -
    - - -
    -
    - {% endif %} - {% render_field form.plaintext %} - {% render_field form.plaintext2 %} -
    +
    + {% render_field form.virtual_machine %}
    - {% if form.custom_fields %} -
    -
    Custom Fields
    -
    - {% render_custom_fields form %} -
    -
    - {% endif %} +
    + {% endwith %} + {% render_field form.role %} + {% render_field form.name %} + {% render_field form.userkeys %} + {% render_field form.tags %} +
    +
    +

    Secret Data

    + {% if obj.pk %} +
    + + +
    +
    + + +
    + {% endif %} + {% render_field form.plaintext %} + {% render_field form.plaintext2 %} + +
    +{% if form.custom_fields %} +
    +
    Custom Fields
    +
    + {% render_custom_fields form %}
    -
    -
    -
    - {% if obj.pk %} - - Cancel - {% else %} - - - Cancel - {% endif %} -
    -
    -
    - +{% endif %} {% include 'secrets/inc/private_key_modal.html' %} {% endblock %} -{% block javascript %} - -{% endblock %} diff --git a/netbox/templates/users/api_tokens.html b/netbox/templates/users/api_tokens.html index 04e7cb23d..f82e517fc 100644 --- a/netbox/templates/users/api_tokens.html +++ b/netbox/templates/users/api_tokens.html @@ -7,15 +7,15 @@
    {% for token in tokens %} -
    -
    -
    - Copy +
    +
    +
    + Copy {% if perms.users.change_token %} - Edit + Edit {% endif %} {% if perms.users.delete_token %} - Delete + Delete {% endif %}
    @@ -24,7 +24,7 @@ Expired {% endif %}
    -
    +
    Created
    @@ -55,22 +55,20 @@ {% empty %}

    You do not have any API tokens.

    {% endfor %} - {% if perms.users.add_token %} - - - Add a token - - {% else %} -
    {% endblock %} - -{% block javascript %} - -{% endblock %} diff --git a/netbox/templates/users/base.html b/netbox/templates/users/base.html index e9b4532e1..503b081ad 100644 --- a/netbox/templates/users/base.html +++ b/netbox/templates/users/base.html @@ -1,34 +1,21 @@ -{% extends 'base.html' %} +{% extends 'layout.html' %} + +{% block title %}{% endblock %} {% block content %}
    -
    -

    {% block title %}{% endblock %}

    -
    -
    -
    -
    - + API Tokens + User Key +
    -
    +
    {% block usercontent %}{% endblock %}
    diff --git a/netbox/templates/users/change_password.html b/netbox/templates/users/change_password.html index 20c6d048b..d5ef8780d 100644 --- a/netbox/templates/users/change_password.html +++ b/netbox/templates/users/change_password.html @@ -3,28 +3,20 @@ {% block title %}Change Password{% endblock %} +{% render_errors form %} + {% block usercontent %}
    {% csrf_token %} - {% if form.non_field_errors %} -
    -
    Errors
    -
    - {{ form.non_field_errors }} -
    -
    - {% endif %} -
    -
    Password
    -
    - {% render_field form.old_password %} - {% render_field form.new_password1 %} - {% render_field form.new_password2 %} -
    +
    +

    Password

    + {% render_field form.old_password %} + {% render_field form.new_password1 %} + {% render_field form.new_password2 %}
    -
    +
    + Cancel - Cancel
    {% endblock %} diff --git a/netbox/templates/users/profile.html b/netbox/templates/users/profile.html index 35a94ac6f..0855798d4 100644 --- a/netbox/templates/users/profile.html +++ b/netbox/templates/users/profile.html @@ -4,16 +4,34 @@ {% block title %}User Profile{% endblock %} {% block usercontent %} - User login + User Login
    {{ request.user.username }}
    - Full name -
    {{ request.user.first_name }} {{ request.user.last_name }}
    + Full Name +
    + {% if request.user.first_name and request.user.last_name %} + {{ request.user.first_name }} {{ request.user.last_name }} + {% elif request.user.first_name and not request.user.last_name %} + {{ request.user.first_name }} + {% elif request.user.last_name and not request.user.first_name %} + {{ request.user.last_name }} + {% else %} + None + {% endif %} +
    Email
    {{ request.user.email }}
    Registered
    {{ request.user.date_joined }}
    Groups -
    {{ request.user.groups.all|join:', ' }}
    - Admin access +
    + {% if request.user.groups.all %} + {% for group in request.user.groups.all %} + {{ group }} + {% endfor %} + {% else %} + None + {% endif %} +
    + Admin Access
    {{ request.user.is_staff|yesno|capfirst }}
    {% endblock %} diff --git a/netbox/templates/users/userkey.html b/netbox/templates/users/userkey.html index 09b5bde5b..1ac3e8629 100644 --- a/netbox/templates/users/userkey.html +++ b/netbox/templates/users/userkey.html @@ -4,18 +4,18 @@ {% block usercontent %} {% if object %} -
    +

    - Your user key is: + Your user key is {% if object.is_active %} - Active + Active {% else %} - Inactive + Inactive {% endif %}

    {% include 'inc/created_updated.html' %} @@ -25,7 +25,7 @@ Your user key is inactive. Ask an administrator to enable it for you.
    {% endif %} -
    {{ object.public_key }}
    +
    {{ object.public_key }}

    {% if object.session_key %}
    diff --git a/netbox/templates/users/userkey_edit.html b/netbox/templates/users/userkey_edit.html index a7aaa720c..76d79d398 100644 --- a/netbox/templates/users/userkey_edit.html +++ b/netbox/templates/users/userkey_edit.html @@ -13,48 +13,43 @@ {% endif %}
    {% csrf_token %} -
    +
    {% render_field form.public_key %}
    -
    -
    -
    - -
    -
    - - Cancel -
    +
    +
    + +
    +
    + Cancel +