security and apple icons
|
@ -8,7 +8,7 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
group = 'stirling.software'
|
group = 'stirling.software'
|
||||||
version = '0.12.2'
|
version = '0.12.3'
|
||||||
sourceCompatibility = '17'
|
sourceCompatibility = '17'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
@ -45,8 +45,9 @@ launch4j {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation 'org.yaml:snakeyaml:2.1'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-web:3.1.2'
|
implementation 'org.springframework.boot:spring-boot-starter-web:3.1.2'
|
||||||
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.1.1'
|
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.1.2'
|
||||||
testImplementation 'org.springframework.boot:spring-boot-starter-test:3.1.2'
|
testImplementation 'org.springframework.boot:spring-boot-starter-test:3.1.2'
|
||||||
// https://mvnrepository.com/artifact/org.apache.pdfbox/jbig2-imageio
|
// https://mvnrepository.com/artifact/org.apache.pdfbox/jbig2-imageio
|
||||||
implementation group: 'org.apache.pdfbox', name: 'jbig2-imageio', version: '3.0.4'
|
implementation group: 'org.apache.pdfbox', name: 'jbig2-imageio', version: '3.0.4'
|
||||||
|
|
|
@ -30,7 +30,7 @@ public class MetricsFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
//System.out.println("uri="+uri + ", method=" + request.getMethod() );
|
//System.out.println("uri="+uri + ", method=" + request.getMethod() );
|
||||||
// Ignore static resources
|
// Ignore static resources
|
||||||
if (!(uri.startsWith("/js") || uri.startsWith("/images") || uri.endsWith(".ico") || uri.endsWith(".css") || uri.endsWith(".svg")|| uri.endsWith(".js") || uri.contains("swagger") || uri.startsWith("/api"))) {
|
if (!(uri.startsWith("/js") || uri.startsWith("api-docs") || uri.startsWith("/images") || uri.endsWith(".png") || uri.endsWith(".ico") || uri.endsWith(".css") || uri.endsWith(".svg")|| uri.endsWith(".js") || uri.contains("swagger") || uri.startsWith("/api"))) {
|
||||||
Counter counter = Counter.builder("http.requests")
|
Counter counter = Counter.builder("http.requests")
|
||||||
.tag("uri", uri)
|
.tag("uri", uri)
|
||||||
.tag("method", request.getMethod())
|
.tag("method", request.getMethod())
|
||||||
|
|
BIN
src/main/resources/static/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
src/main/resources/static/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
src/main/resources/static/apple-touch-icon-114x114.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
src/main/resources/static/apple-touch-icon-120x120.png
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
src/main/resources/static/apple-touch-icon-144x144.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
src/main/resources/static/apple-touch-icon-152x152.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
src/main/resources/static/apple-touch-icon-180x180.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
src/main/resources/static/apple-touch-icon-57x57.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
src/main/resources/static/apple-touch-icon-60x60.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
src/main/resources/static/apple-touch-icon-72x72.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
src/main/resources/static/apple-touch-icon-76x76.png
Normal file
After Width: | Height: | Size: 5 KiB |
BIN
src/main/resources/static/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 14 KiB |
9
src/main/resources/static/browserconfig.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<browserconfig>
|
||||||
|
<msapplication>
|
||||||
|
<tile>
|
||||||
|
<square150x150logo src="/mstile-150x150.png"/>
|
||||||
|
<TileColor>#2d89ef</TileColor>
|
||||||
|
</tile>
|
||||||
|
</msapplication>
|
||||||
|
</browserconfig>
|
BIN
src/main/resources/static/favicon-16x16.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
src/main/resources/static/favicon-32x32.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 15 KiB |
20
src/main/resources/static/manifest.json
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"name": "Stirling-PDF",
|
||||||
|
"short_name": "Stirling-PDF",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/android-icon-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/android-icon-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"theme_color": "#000000"
|
||||||
|
}
|
BIN
src/main/resources/static/mstile-150x150.png
Normal file
After Width: | Height: | Size: 13 KiB |
41
src/main/resources/static/safari-pinned-tab.svg
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="752.000000pt" height="752.000000pt" viewBox="0 0 752.000000 752.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
<metadata>
|
||||||
|
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||||
|
</metadata>
|
||||||
|
<g transform="translate(0.000000,752.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M5025 7049 c-87 -21 -1141 -257 -2770 -620 -566 -126 -1057 -239
|
||||||
|
-1090 -250 -153 -56 -253 -125 -297 -206 -62 -113 -58 67 -58 -2629 l0 -2462
|
||||||
|
22 -5 c73 -19 1640 -347 1655 -347 9 0 58 39 107 88 596 575 1106 1069 1611
|
||||||
|
1556 336 324 725 700 865 836 140 135 383 369 540 520 157 151 390 377 518
|
||||||
|
502 l233 228 -23 33 c-12 17 -89 122 -170 231 l-148 200 0 944 c0 894 -1 943
|
||||||
|
-17 938 -40 -11 -853 -186 -868 -186 -13 0 -15 38 -15 325 0 179 -3 325 -7
|
||||||
|
324 -5 -1 -44 -10 -88 -20z m-2205 -1513 c167 -23 343 -73 504 -145 l66 -30 0
|
||||||
|
-536 0 -535 -40 0 c-40 0 -46 6 -263 224 -211 212 -226 226 -307 265 -142 68
|
||||||
|
-274 75 -427 21 -141 -49 -213 -161 -213 -329 0 -89 16 -124 82 -172 91 -66
|
||||||
|
206 -115 499 -212 228 -76 340 -126 458 -204 157 -105 282 -253 366 -436 59
|
||||||
|
-128 70 -203 69 -452 -1 -213 -2 -224 -31 -337 -33 -123 -89 -255 -144 -335
|
||||||
|
-43 -63 -169 -194 -240 -248 -172 -134 -388 -199 -705 -212 -260 -11 -473 34
|
||||||
|
-858 179 l-246 93 3 540 2 540 43 3 c34 3 43 0 48 -15 48 -159 263 -392 514
|
||||||
|
-560 159 -107 273 -152 401 -160 154 -10 269 42 353 160 74 104 102 196 90
|
||||||
|
295 -18 145 -192 275 -511 381 -375 125 -541 216 -706 386 -198 203 -273 411
|
||||||
|
-271 760 l0 170 38 110 c77 223 147 338 285 468 158 148 283 219 478 268 252
|
||||||
|
64 465 81 663 55z"/>
|
||||||
|
<path d="M5633 2297 c-267 -688 -370 -952 -393 -1002 l-27 -60 -114 -44 c-63
|
||||||
|
-24 -127 -50 -144 -57 -16 -7 -104 -43 -195 -79 -91 -36 -174 -70 -185 -75
|
||||||
|
-11 -5 -46 -19 -77 -30 -32 -12 -58 -26 -58 -31 0 -6 -4 -8 -10 -4 -5 3 -17 1
|
||||||
|
-27 -4 -25 -14 -169 -44 -1248 -256 -324 -64 -599 -119 -610 -122 -17 -5 -15
|
||||||
|
-8 12 -15 63 -17 633 -117 803 -142 293 -42 276 -42 471 -3 96 20 226 45 289
|
||||||
|
57 63 12 282 55 485 95 204 40 422 83 485 95 63 12 237 46 385 75 231 46 390
|
||||||
|
77 765 149 l65 13 -50 11 c-27 6 -192 34 -365 62 -173 28 -331 54 -350 59 -41
|
||||||
|
9 -41 -12 1 226 30 175 61 355 124 720 20 116 42 235 49 266 12 53 11 61 -12
|
||||||
|
118 -13 33 -27 61 -31 61 -3 0 -20 -37 -38 -83z"/>
|
||||||
|
<path d="M6404 928 c4 -23 8 -44 10 -45 2 -2 44 4 94 14 51 9 89 20 84 24 -8
|
||||||
|
9 -158 49 -180 49 -11 0 -13 -9 -8 -42z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
19
src/main/resources/static/site.webmanifest
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "Stirling PDF",
|
||||||
|
"short_name": "Stirling PDF",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "#ffffff",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
|
@ -5,6 +5,24 @@
|
||||||
|
|
||||||
<title th:text="${@appName} + (${title} != null and ${title} != '' ? ' - ' + ${title} : '')"></title>
|
<title th:text="${@appName} + (${title} != null and ${title} != '' ? ' - ' + ${title} : '')"></title>
|
||||||
<link rel="shortcut icon" href="favicon.svg">
|
<link rel="shortcut icon" href="favicon.svg">
|
||||||
|
|
||||||
|
<link rel="apple-touch-icon" sizes="57x57" href="apple-touch-icon-57x57.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="60x60" href="apple-touch-icon-60x60.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="72x72" href="apple-touch-icon-72x72.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="76x76" href="apple-touch-icon-76x76.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="114x114" href="apple-touch-icon-114x114.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="120x120" href="apple-touch-icon-120x120.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="144x144" href="apple-touch-icon-144x144.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="152x152" href="apple-touch-icon-152x152.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon-180x180.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png">
|
||||||
|
<link rel="manifest" href="site.webmanifest">
|
||||||
|
<link rel="mask-icon" href="safari-pinned-tab.svg" color="#ca2b2a">
|
||||||
|
<meta name="msapplication-TileColor" content="#2d89ef">
|
||||||
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
<!-- jQuery -->
|
<!-- jQuery -->
|
||||||
|
|
|
@ -54,11 +54,22 @@
|
||||||
body: formData
|
body: formData
|
||||||
}).then(response => response.text())
|
}).then(response => response.text())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
// Escape < and > characters
|
// Escape < and > characters
|
||||||
let escapedData = data.replace(/</g, '<').replace(/>/g, '>');
|
let escapedData = data.replace(/</g, '<').replace(/>/g, '>');
|
||||||
|
|
||||||
// Wrap the JavaScript content in a pre and code tag and add it to the div
|
// Create the elements manually
|
||||||
document.querySelector('#script-content').innerHTML = '<pre><code class="language-javascript">' + escapedData + '</code></pre>';
|
let preElement = document.createElement('pre');
|
||||||
|
let codeElement = document.createElement('code');
|
||||||
|
codeElement.classList.add('language-javascript');
|
||||||
|
codeElement.textContent = escapedData; // Use textContent instead of innerHTML
|
||||||
|
preElement.appendChild(codeElement);
|
||||||
|
|
||||||
|
let scriptContent = document.querySelector('#script-content');
|
||||||
|
// Clear existing content, if any
|
||||||
|
while (scriptContent.firstChild) {
|
||||||
|
scriptContent.removeChild(scriptContent.firstChild);
|
||||||
|
}
|
||||||
|
scriptContent.appendChild(preElement);
|
||||||
|
|
||||||
// Highlight the code using Prism.js
|
// Highlight the code using Prism.js
|
||||||
Prism.highlightAll();
|
Prism.highlightAll();
|
||||||
|
|
|
@ -35,110 +35,120 @@
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
document.getElementById("pdfInfoForm").addEventListener("submit", function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
// Prevent the form from submitting the traditional way
|
const formData = new FormData(event.target);
|
||||||
document.getElementById("pdfInfoForm").addEventListener("submit", function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
// Fetch the formData
|
fetch('get-info-on-pdf', {
|
||||||
const formData = new FormData(event.target);
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
displayJsonData(data);
|
||||||
|
setDownloadLink(data);
|
||||||
|
document.getElementById("downloadJson").style.display = "block";
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error:', error));
|
||||||
|
});
|
||||||
|
|
||||||
fetch('get-info-on-pdf', {
|
function displayJsonData(jsonData) {
|
||||||
method: 'POST',
|
const jsonContent = document.getElementById('json-content');
|
||||||
body: formData
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
displayJsonData(data); // Display the data
|
|
||||||
setDownloadLink(data); // Set download link
|
|
||||||
document.getElementById("downloadJson").style.display = "block";
|
|
||||||
})
|
|
||||||
.catch(error => console.error('Error:', error));
|
|
||||||
});
|
|
||||||
|
|
||||||
function displayJsonData(jsonData) {
|
while (jsonContent.firstChild) {
|
||||||
let content = '';
|
jsonContent.removeChild(jsonContent.firstChild);
|
||||||
for (const key in jsonData) {
|
}
|
||||||
content += renderJsonSection(key, jsonData[key]);
|
|
||||||
}
|
|
||||||
document.getElementById('json-content').innerHTML = content;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setDownloadLink(jsonData) {
|
for (const key in jsonData) {
|
||||||
const downloadLink = document.getElementById('downloadJson');
|
const sectionElem = createJsonSection(key, jsonData[key]);
|
||||||
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(jsonData, null, 4));
|
jsonContent.appendChild(sectionElem);
|
||||||
downloadLink.setAttribute("href", dataStr);
|
}
|
||||||
downloadLink.setAttribute("download", "data.json");
|
}
|
||||||
}
|
|
||||||
|
function setDownloadLink(jsonData) {
|
||||||
|
const downloadLink = document.getElementById('downloadJson');
|
||||||
|
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(jsonData, null, 4));
|
||||||
|
downloadLink.setAttribute("href", dataStr);
|
||||||
|
downloadLink.setAttribute("download", "data.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
function createJsonSection(key, value, depth = 0) {
|
||||||
|
let safeKey = (typeof key === "string") ? key.replace(/[^a-zA-Z0-9]/g, '_') : key;
|
||||||
|
const card = document.createElement('div');
|
||||||
|
card.className = 'card mb-3';
|
||||||
|
|
||||||
|
const header = document.createElement('div');
|
||||||
|
header.className = 'card-header';
|
||||||
|
header.id = `${safeKey}-heading-${depth}`;
|
||||||
|
const h5Elem = document.createElement('h5');
|
||||||
|
h5Elem.className = 'mb-0';
|
||||||
|
|
||||||
|
if (key === 'XMPMetadata' && typeof value === "string") {
|
||||||
|
const buttonElem = createButtonElement(key, safeKey, depth);
|
||||||
|
h5Elem.appendChild(buttonElem);
|
||||||
|
} else if (value && typeof value === 'object') {
|
||||||
|
if (Array.isArray(value) && value.length === 0) {
|
||||||
|
h5Elem.textContent = `${key}: Empty array`;
|
||||||
|
} else if (!Array.isArray(value) && Object.keys(value).length === 0) {
|
||||||
|
h5Elem.textContent = `${key}: Empty object`;
|
||||||
|
} else {
|
||||||
|
const buttonElem = createButtonElement(key, safeKey, depth);
|
||||||
|
h5Elem.appendChild(buttonElem);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
h5Elem.textContent = `${key}: ${String(value)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
header.appendChild(h5Elem);
|
||||||
|
card.appendChild(header);
|
||||||
|
|
||||||
|
const content = document.createElement('div');
|
||||||
|
content.id = `${safeKey}-content-${depth}`;
|
||||||
|
content.className = 'collapse';
|
||||||
|
content.setAttribute('aria-labelledby', `${safeKey}-heading-${depth}`);
|
||||||
|
|
||||||
|
if (key === 'XMPMetadata' && typeof value === "string") {
|
||||||
|
const body = document.createElement('div');
|
||||||
|
body.className = 'card-body';
|
||||||
|
const preElem = document.createElement('pre');
|
||||||
|
preElem.textContent = value; // Not escaping since we're using textContent
|
||||||
|
body.appendChild(preElem);
|
||||||
|
content.appendChild(body);
|
||||||
|
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
|
||||||
|
const body = document.createElement('div');
|
||||||
|
body.className = 'card-body';
|
||||||
|
for (const subKey in value) {
|
||||||
|
const subElem = createJsonSection(subKey, value[subKey], depth + 1);
|
||||||
|
body.appendChild(subElem);
|
||||||
|
}
|
||||||
|
content.appendChild(body);
|
||||||
|
} else if (value && typeof value === 'object' && Array.isArray(value)) {
|
||||||
|
const body = document.createElement('div');
|
||||||
|
body.className = 'card-body';
|
||||||
|
value.forEach((val, index) => {
|
||||||
|
const subElem = createJsonSection(`${key}[${index}]`, val, depth + 1);
|
||||||
|
body.appendChild(subElem);
|
||||||
|
});
|
||||||
|
content.appendChild(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
card.appendChild(content);
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function renderJsonSection(key, value, depth = 0) {
|
function createButtonElement(key, safeKey, depth) {
|
||||||
let safeKey = (typeof key === "string") ? key.replace(/[^a-zA-Z0-9]/g, '_') : key;
|
const buttonElem = document.createElement('button');
|
||||||
|
buttonElem.className = 'btn btn-link';
|
||||||
let output = `<div class="card mb-3">
|
buttonElem.type = 'button';
|
||||||
<div class="card-header" id="${safeKey}-heading-${depth}">
|
buttonElem.dataset.toggle = "collapse";
|
||||||
<h5 class="mb-0">`;
|
buttonElem.dataset.target = `#${safeKey}-content-${depth}`;
|
||||||
|
buttonElem.setAttribute('aria-expanded', 'true');
|
||||||
if (key === 'XMPMetadata' && typeof value === "string") {
|
buttonElem.setAttribute('aria-controls', `${safeKey}-content-${depth}`);
|
||||||
output += `<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#${safeKey}-content-${depth}" aria-expanded="true" aria-controls="${safeKey}-content-${depth}">
|
buttonElem.textContent = key;
|
||||||
${key}
|
return buttonElem;
|
||||||
</button>`;
|
}
|
||||||
} else if (value && typeof value === 'object') {
|
|
||||||
if (Array.isArray(value) && value.length === 0) {
|
|
||||||
output += `${key}: Empty array`;
|
|
||||||
} else if (!Array.isArray(value) && Object.keys(value).length === 0) {
|
|
||||||
output += `${key}: Empty object`;
|
|
||||||
} else {
|
|
||||||
output += `<button class="btn btn-link" type="button" data-toggle="collapse" data-target="#${safeKey}-content-${depth}" aria-expanded="true" aria-controls="${safeKey}-content-${depth}">
|
|
||||||
${key}
|
|
||||||
</button>`;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
output += `${key}: ${value}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
output += `
|
|
||||||
</h5>
|
|
||||||
</div>
|
|
||||||
<div id="${safeKey}-content-${depth}" class="collapse" aria-labelledby="${safeKey}-heading-${depth}">`;
|
|
||||||
|
|
||||||
if (key === 'XMPMetadata' && typeof value === "string") {
|
|
||||||
output += `<div class="card-body"><pre>${escapeHTML(value)}</pre></div>`;
|
|
||||||
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
||||||
output += '<div class="card-body">';
|
|
||||||
if (Object.keys(value).length) {
|
|
||||||
for (const subKey in value) {
|
|
||||||
output += renderJsonSection(subKey, value[subKey], depth + 1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
output += '<p class="text-muted">Empty</p>';
|
|
||||||
}
|
|
||||||
output += '</div>';
|
|
||||||
} else if (value && typeof value === 'object' && Array.isArray(value)) {
|
|
||||||
output += '<div class="card-body">';
|
|
||||||
if (value.length) {
|
|
||||||
value.forEach((val, index) => {
|
|
||||||
const arrayKey = `${key}[${index}]`;
|
|
||||||
output += renderJsonSection(arrayKey, val, depth + 1);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
output += '<p class="text-muted">Empty</p>';
|
|
||||||
}
|
|
||||||
output += '</div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
output += '</div></div>';
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function escapeHTML(s) {
|
|
||||||
if(s)
|
|
||||||
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|