Просмотр исходного кода

migrate napalm/lldp to typescript

checktheroads 4 лет назад
Родитель
Сommit
f3eb0df081

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
netbox/project-static/dist/jobs.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
netbox/project-static/dist/jobs.js.map


Разница между файлами не показана из-за своего большого размера
+ 43 - 0
netbox/project-static/dist/lldp.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
netbox/project-static/dist/lldp.js.map


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
netbox/project-static/dist/netbox.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
netbox/project-static/dist/netbox.css.map


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
netbox/project-static/dist/netbox.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
netbox/project-static/dist/netbox.js.map


+ 16 - 0
netbox/project-static/netbox.scss

@@ -436,3 +436,19 @@ pre.change-diff {
     background-color: rgba($green, 0.4);
   }
 }
+
+div.card-overlay {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  background-color: rgba($white, 0.75);
+  border-radius: $border-radius;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  & > div.spinner-border {
+    width: 6rem;
+    height: 6rem;
+    color: $secondary;
+  }
+}

+ 5 - 2
netbox/project-static/package.json

@@ -5,7 +5,7 @@
   "license": "Apache2",
   "scripts": {
     "bundle:css": "parcel build --public-url /static -o netbox.css main.scss && parcel build --public-url /static -o rack_elevation.css rack_elevation.scss",
-    "bundle:js": "parcel build -o netbox.js src/index.ts && parcel build -o jobs.js src/jobs.ts",
+    "bundle:js": "parcel build --public-url /static -o netbox.js src/index.ts && parcel build --public-url /static -o jobs.js src/jobs.ts && parcel build --public-url /static -o lldp.js src/lldp.ts",
     "bundle": "yarn bundle:css && yarn bundle:js"
   },
   "dependencies": {
@@ -62,5 +62,8 @@
       "@babel/proposal-object-rest-spread",
       "@babel/plugin-proposal-nullish-coalescing-operator"
     ]
-  }
+  },
+  "browserslist": [
+    "defaults"
+  ]
 }

+ 15 - 0
netbox/project-static/src/global.d.ts

@@ -98,6 +98,21 @@ type APIUserConfig = {
   [k: string]: unknown;
 };
 
+type LLDPInterface = {
+  parent_interface: string | null;
+  remote_chassis_id: string | null;
+  remote_port: string | null;
+  remote_port_description: string | null;
+  remote_system_capab: string[];
+  remote_system_description: string | null;
+  remote_system_enable_capab: string[];
+  remote_system_name: string | null;
+};
+
+type LLDPNeighborDetail = {
+  get_lldp_neighbors_detail: { [interface: string]: LLDPInterface[] };
+};
+
 interface ObjectWithGroup extends APIObjectBase {
   group: Nullable<APIReference>;
 }

+ 7 - 1
netbox/project-static/src/jobs.ts

@@ -89,10 +89,16 @@ async function checkJobStatus(id: string) {
   }
 }
 
-if (document !== null) {
+function initJobs() {
   const { id, complete } = getJobInfo();
   if (id !== null && !complete) {
     // If there is a job ID and it is not completed, check for the job's status.
     Promise.resolve(checkJobStatus(id));
   }
 }
+
+if (document.readyState !== 'loading') {
+  initJobs();
+} else {
+  document.addEventListener('DOMContentLoaded', initJobs);
+}

+ 109 - 0
netbox/project-static/src/lldp.ts

@@ -0,0 +1,109 @@
+import { createToast } from './toast';
+import { getNetboxData, apiGetBase, hasError, isTruthy } from './util';
+
+/**
+ * Toggle visibility of card loader.
+ */
+function toggleLoader(action: 'show' | 'hide') {
+  const spinnerContainer = document.querySelector('div.card-overlay');
+  if (spinnerContainer !== null) {
+    if (action === 'show') {
+      spinnerContainer.classList.remove('d-none');
+    } else {
+      spinnerContainer.classList.add('d-none');
+    }
+  }
+}
+
+/**
+ * Get an attribute from a row's cell.
+ *
+ * @param row Interface row
+ * @param query CSS media query
+ * @param attr Cell attribute
+ */
+function getData(row: HTMLTableRowElement, query: string, attr: string): string | null {
+  return row.querySelector(query)?.getAttribute(attr) ?? null;
+}
+
+/**
+ * Update row styles based on LLDP neighbor data.
+ */
+function updateRowStyle(data: LLDPNeighborDetail) {
+  for (const [fullIface, neighbors] of Object.entries(data.get_lldp_neighbors_detail)) {
+    const [iface] = fullIface.split('.');
+
+    const row = document.getElementById(iface) as Nullable<HTMLTableRowElement>;
+
+    if (row !== null) {
+      for (const neighbor of neighbors) {
+        const cellDevice = row.querySelector<HTMLTableCellElement>('td.device');
+        const cellInterface = row.querySelector<HTMLTableCellElement>('td.interface');
+        const cDevice = getData(row, 'td.configured_device', 'data');
+        const cChassis = getData(row, 'td.configured_chassis', 'data-chassis');
+        const cInterface = getData(row, 'td.configured_interface', 'data');
+
+        let cInterfaceShort = null;
+        if (isTruthy(cInterface)) {
+          cInterfaceShort = cInterface.replace(/^([A-Z][a-z])[^0-9]*([0-9\/]+)$/, '$1$2');
+        }
+
+        const nHost = neighbor.remote_system_name ?? '';
+        const nPort = neighbor.remote_port ?? '';
+        const [nDevice] = nHost.split('.');
+        const [nInterface] = nPort.split('.');
+
+        if (cellDevice !== null) {
+          cellDevice.innerText = nDevice;
+        }
+
+        if (cellInterface !== null) {
+          cellInterface.innerText = nInterface;
+        }
+
+        if (!isTruthy(cDevice) && isTruthy(nDevice)) {
+          row.classList.add('info');
+        } else if (
+          (cDevice === nDevice || cChassis === nDevice) &&
+          cInterfaceShort === nInterface
+        ) {
+          row.classList.add('success');
+        } else if (cDevice === nDevice || cChassis === nDevice) {
+          row.classList.add('success');
+        } else {
+          row.classList.add('danger');
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Initialize LLDP Neighbor fetching.
+ */
+function initLldpNeighbors() {
+  toggleLoader('show');
+  const url = getNetboxData('object-url');
+  if (url !== null) {
+    apiGetBase<LLDPNeighborDetail>(url)
+      .then(data => {
+        if (hasError(data)) {
+          createToast('danger', 'Error Retrieving LLDP Neighbor Information', data.error).show();
+          toggleLoader('hide');
+          return;
+        } else {
+          updateRowStyle(data);
+        }
+        return;
+      })
+      .finally(() => {
+        toggleLoader('hide');
+      });
+  }
+}
+
+if (document.readyState !== 'loading') {
+  initLldpNeighbors();
+} else {
+  document.addEventListener('DOMContentLoaded', initLldpNeighbors);
+}

+ 13 - 0
netbox/project-static/src/util.ts

@@ -159,3 +159,16 @@ export function getSelectedOptions<E extends HTMLElement>(base: E): SelectedOpti
   }
   return selected;
 }
+
+export function getNetboxData(key: string): string | null {
+  if (!key.startsWith('data-')) {
+    key = `data-${key}`;
+  }
+  for (const element of getElements('body > div#netbox-data > *')) {
+    const value = element.getAttribute(key);
+    if (isTruthy(value)) {
+      return value;
+    }
+  }
+  return null;
+}

+ 8 - 8
netbox/templates/base.html

@@ -15,19 +15,19 @@
       name="viewport"
       content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width"
     />
-  </head>
-  <body>
-    {% block layout %}{% endblock %}
     <script
+      type="text/javascript"
       src="{% static 'netbox.js' %}"
       onerror="window.location='{% url 'media_failure' %}?filename=netbox.js'">
     </script>
-
-    <script type="text/javascript">
-      var netbox_api_path = "/{{ settings.BASE_PATH }}api/";
-      var netbox_csrf_token = "{{ csrf_token }}";
-    </script>
+    {% block head %}{% endblock %}
+  </head>
+  <body>
+    {% block layout %}{% endblock %}
     {% block javascript %}{% endblock %}
     {% include './messages.html' %}
+    <div id="netbox-data" style="display: none!important; visibility: hidden!important">
+    {% block data %}{% endblock %}
+    </div>
   </body>
 </html>

+ 18 - 53
netbox/templates/dcim/device/lldp_neighbors.html

@@ -1,11 +1,23 @@
 {% extends 'dcim/device/base.html' %}
+{% load static %}
 
 {% block title %}{{ object }} - LLDP Neighbors{% endblock %}
 
+{% block head %}
+<script type="text/javascript" src="{% static 'lldp.js' %}" onerror="window.location='{% url 'media_failure' %}?filename=lldp.js'"></script>
+{% endblock %}
+
 {% block content %}
-    {% include 'inc/ajax_loader.html' %}
     <div class="card">
-        <h5 class="card-header">LLDP Neighbors</h5>
+        <div class="card-overlay">
+            <div class="spinner-border" role="status">
+                <span class="visually-hidden">Loading...</span>
+            </div>
+        </div>
+        <div class="card-header">
+            <h5 class="d-inline">LLDP Neighbors</h5>
+            {% comment %} <div id="spinner-container" class="float-end"></div> {% endcomment %}
+        </div>
         <div class="card-body">
             <table class="table table-hover">
                 <thead>
@@ -20,7 +32,7 @@
                 <tbody>
                     {% for iface in interfaces %}
                         <tr id="{{ iface.name }}">
-                            <td>{{ iface }}</td>
+                            <td class="font-monospace">{{ iface }}</td>
                             {% if iface.connected_endpoint.device %}
                                 <td class="configured_device" data="{{ iface.connected_endpoint.device }}" data-chassis="{{ iface.connected_endpoint.device.virtual_chassis.name }}">
                                     <a href="{% url 'dcim:device' pk=iface.connected_endpoint.device.pk %}">{{ iface.connected_endpoint.device }}</a>
@@ -36,7 +48,7 @@
                                     </td>
                                 {% endwith %}
                             {% else %}
-                                <td colspan="2">None</td>
+                                <td class="text-muted" colspan="2">None</td>
                             {% endif %}
                             <td class="device"></td>
                             <td class="interface"></td>
@@ -48,53 +60,6 @@
     </div>
 {% endblock %}
 
-{% block javascript %}
-<script type="text/javascript">
-$(document).ready(function() {
-    $.ajax({
-        url: "{% url 'dcim-api:device-napalm' pk=object.pk %}?method=get_lldp_neighbors_detail",
-        dataType: 'json',
-        success: function(json) {
-            $.each(json['get_lldp_neighbors_detail'], function(iface, neighbors) {
-                var neighbor = neighbors[0];
-                var row = $('#' + iface.split(".")[0].replace(/([\/:])/g, "\\$1"));
-
-                // Glean configured hostnames/interfaces from the DOM
-                var configured_device = row.children('td.configured_device').attr('data');
-                var configured_chassis = row.children('td.configured_device').attr('data-chassis');
-                var configured_interface = row.children('td.configured_interface').attr('data');
-                var configured_interface_short = null;
-                if (configured_interface) {
-                    // Match long-form IOS names against short ones (e.g. Gi0/1 == GigabitEthernet0/1).
-                    configured_interface_short = configured_interface.replace(/^([A-Z][a-z])[^0-9]*([0-9\/]+)$/, "$1$2");
-                }
-
-                // Clean up hostnames/interfaces learned via LLDP
-                var neighbor_host = neighbor['remote_system_name'] || ""; // sanitize hostname if it's null to avoid breaking the split func
-                var neighbor_port = neighbor['remote_port'] || ""; // sanitize port if it's null to avoid breaking the split func
-                var lldp_device = neighbor_host.split(".")[0];  // Strip off any trailing domain name
-                var lldp_interface = neighbor_port.split(".")[0];   // Strip off any trailing subinterface ID
-
-                // Add LLDP neighbors to table
-                row.children('td.device').html(lldp_device);
-                row.children('td.interface').html(lldp_interface);
-
-                // Apply colors to rows
-                if (!configured_device && lldp_device) {
-                    row.addClass('info');
-                } else if ((configured_device == lldp_device || configured_chassis == lldp_device) && configured_interface == lldp_interface) {
-                    row.addClass('success');
-                } else if ((configured_device == lldp_device || configured_chassis == lldp_device) && configured_interface_short == lldp_interface) {
-                    row.addClass('success');
-                } else {
-                    row.addClass('danger');
-                }
-            });
-        },
-        error: function(xhr) {
-            alert(xhr.responseText);
-        }
-    });
-});
-</script>
+{% block data %}
+<span data-object-url="{% url 'dcim-api:device-napalm' pk=object.pk %}?method=get_lldp_neighbors_detail"></span>
 {% endblock %}

+ 7 - 2
netbox/templates/layout.html

@@ -1,5 +1,8 @@
-{% extends 'base.html' %} {% load search_options %} {% block layout %}
+{% extends 'base.html' %}
+{% load search_options %}
 
+{% block head %}{% endblock %}
+{% block layout %}
 <div class="container-fluid">
   <div class="row">
     <nav
@@ -56,4 +59,6 @@
     </main>
   </div>
 </div>
-{% endblock %} {%block javascript %} {%endblock%}
+{% endblock %}
+{% block javascript %}{% endblock %}
+{% block data %}{% endblock %}

Некоторые файлы не были показаны из-за большого количества измененных файлов