Update Example Plugin

This commit is contained in:
Raven Scott
2026-05-31 01:14:07 -04:00
parent 8261b83b06
commit 5d28a307f2
2 changed files with 676 additions and 447 deletions
File diff suppressed because it is too large Load Diff
+134 -100
View File
@@ -10,146 +10,193 @@
<link rel="icon" href="/branding/p2ns-favicon-192.png" type="image/png" sizes="192x192">
<link rel="apple-touch-icon" href="/branding/p2ns-favicon-192.png">
<link rel="manifest" href="/manifest.json">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/branding.css">
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<div class="container">
<header class="brand-lockup brand-lockup--center">
<img src="/branding/p2ns-icon.png" alt="" class="brand-lockup-logo" width="40" height="40">
<div class="brand-lockup-text">
<header class="header">
<div class="header-content header-brand">
<img src="/branding/p2ns-icon.png" alt="" class="brand-lockup-logo" width="32" height="32">
<h1>Example Plugin</h1>
<p class="subtitle">A comprehensive template plugin demonstrating admin settings and actions</p>
<p class="subtitle">Template plugin demonstrating admin settings and actions</p>
</div>
<div class="header-actions">
<a href="https://p2ns.admin" target="_blank" rel="noopener noreferrer" class="btn btn-secondary btn-sm">Open Admin</a>
<div class="status" role="status" aria-live="polite">
<div class="status-indicator" id="statusIndicator" aria-label="Connection status"></div>
<span id="statusText">Loading...</span>
</div>
</div>
</header>
<main>
<section class="info">
<h2>Plugin Information</h2>
<p>This plugin demonstrates how to use admin settings and actions in a P2NS plugin. Configure settings in the admin panel to see them affect the plugin behavior.</p>
<main class="main-content">
<section class="section-card" id="info">
<header class="section-header">
<h2 class="section-title">Plugin Information</h2>
<p class="section-description">Live status from the plugin backend. Configure settings in the admin panel to see them affect behavior.</p>
</header>
<div id="plugin-info" class="loading">Loading plugin info...</div>
</section>
<section class="settings">
<h2>Current Settings</h2>
<p>These settings are configured in the admin panel and affect plugin behavior:</p>
<section class="section-card" id="settings">
<header class="section-header">
<h2 class="section-title">Current Settings</h2>
<p class="section-description">Values configured at <code>p2ns.admin</code> → Plugins → example.plugin → Settings</p>
</header>
<div id="settings-display" class="loading">Loading settings...</div>
<p class="settings-note"><strong>Note:</strong> Change these settings in the admin panel at <code>p2ns.admin</code> → Plugins → example.plugin → Settings</p>
<p class="settings-note"><strong>Note:</strong> Change these settings in the admin panel to see live updates below.</p>
</section>
<section class="identity">
<h2>Your Public Key</h2>
<p>Your persistent Hyperswarm public key that identifies you in the P2NS Network:</p>
<section class="section-card" id="identity">
<header class="section-header">
<h2 class="section-title">Your Public Key</h2>
<p class="section-description">Your persistent Hyperswarm public key that identifies you on the P2NS network.</p>
</header>
<div id="public-key-container" class="loading">Loading public key...</div>
</section>
<section class="api-demo">
<h2>API Demo (Using Settings)</h2>
<p>These API endpoints demonstrate how settings affect responses:</p>
<ul>
<li><code>GET /api/hello?name=World</code> - Uses "Greeting Prefix" setting</li>
<li><code>GET /api/calculate?value=10</code> - Uses "Multiplier" setting</li>
<li><code>GET /api/mode</code> - Uses "Operation Mode" setting</li>
<li><code>GET /api/info</code> - Shows all current settings</li>
<section class="section-card" id="api-demo">
<header class="section-header">
<h2 class="section-title">API Demo</h2>
<p class="section-description">These endpoints demonstrate how admin settings affect responses.</p>
</header>
<ul class="api-list">
<li><code>GET /api/hello?name=World</code> — uses Greeting Prefix setting</li>
<li><code>GET /api/calculate?value=10</code> — uses Multiplier setting</li>
<li><code>GET /api/mode</code> — uses Operation Mode setting</li>
<li><code>GET /api/info</code> — returns all current settings</li>
</ul>
<div class="button-group">
<button onclick="testHello()">Test Hello API</button>
<button onclick="testCalculate()">Test Calculate API</button>
<button onclick="testMode()">Test Mode API</button>
<button type="button" class="btn btn-primary" onclick="testHello()">Test Hello API</button>
<button type="button" class="btn btn-primary" onclick="testCalculate()">Test Calculate API</button>
<button type="button" class="btn btn-secondary" onclick="testMode()">Test Mode API</button>
</div>
<div id="api-result" class="result"></div>
</section>
<section class="actions">
<h2>Admin Actions</h2>
<p>Actions can be executed from the admin panel. Results are shown below:</p>
<section class="section-card" id="actions">
<header class="section-header">
<h2 class="section-title">Admin Actions</h2>
<p class="section-description">Execute actions at <code>p2ns.admin</code> → Plugins → example.plugin → Actions</p>
</header>
<div id="actions-display" class="loading">Loading action results...</div>
<p class="settings-note"><strong>Note:</strong> Execute actions in the admin panel at <code>p2ns.admin</code> → Plugins → example.plugin → Actions</p>
</section>
<section class="structure">
<h2>Plugin Structure</h2>
<section class="section-card" id="structure">
<header class="section-header">
<h2 class="section-title">Plugin Structure</h2>
<p class="section-description">Use this layout as a starting point for your own plugins.</p>
</header>
<pre><code>plugin-sites/example.plugin/
├── config.json # Plugin metadata
├── index.js # Plugin handler (uses settings & actions)
├── index.js # Plugin handler (uses settings &amp; actions)
└── www/ # Document root for static files
├── index.html # This file (displays settings)
├── index.html # This file
└── css/
── style.css # Stylesheet</code></pre>
── branding.css # Shared P2NS brand lockup
└── style.css # Plugin styles</code></pre>
</section>
</main>
<footer>
<p>Use this plugin as a starting point for your own P2NS plugins! Configure settings in the admin panel to see them in action.</p>
<footer class="site-footer">
<p>Use this plugin as a starting point for your own P2NS plugins.</p>
</footer>
</div>
<script>
// Load plugin info on page load
function updateStatus(data) {
const indicator = document.getElementById('statusIndicator');
const text = document.getElementById('statusText');
if (!indicator || !text) return;
indicator.classList.toggle('disconnected', !data.dnsReady);
text.textContent = data.dnsReady
? `${data.connectedPeers} peer${data.connectedPeers === 1 ? '' : 's'} connected`
: 'DNS not ready';
}
async function loadPluginInfo() {
try {
const response = await fetch('/api/info');
const data = await response.json();
updateStatus(data);
document.getElementById('plugin-info').innerHTML = `
<ul>
<li><strong>Plugin:</strong> ${data.plugin}</li>
<li><strong>Version:</strong> ${data.version}</li>
<li><strong>Description:</strong> ${data.description}</li>
<li><strong>DNS Ready:</strong> ${data.dnsReady ? 'Yes' : 'No'}</li>
<li><strong>Connected Peers:</strong> ${data.connectedPeers}</li>
</ul>
<dl class="info-grid">
<div class="info-item">
<dt>Plugin</dt>
<dd>${data.plugin}</dd>
</div>
<div class="info-item">
<dt>Version</dt>
<dd>${data.version}</dd>
</div>
<div class="info-item info-item--wide">
<dt>Description</dt>
<dd>${data.description}</dd>
</div>
<div class="info-item">
<dt>DNS Ready</dt>
<dd><span class="badge ${data.dnsReady ? 'badge--success' : 'badge--warning'}">${data.dnsReady ? 'Yes' : 'No'}</span></dd>
</div>
<div class="info-item">
<dt>Connected Peers</dt>
<dd>${data.connectedPeers}</dd>
</div>
</dl>
`;
} catch (err) {
document.getElementById('plugin-info').innerHTML =
document.getElementById('plugin-info').innerHTML =
'<p class="error">Failed to load plugin info: ' + err.message + '</p>';
}
}
// Load and display current settings
async function loadSettings() {
try {
const response = await fetch('/api/settings');
const data = await response.json();
const settings = data.settings || {};
document.getElementById('settings-display').innerHTML = `
<div class="settings-grid">
<div class="setting-item">
<strong>Greeting Prefix:</strong>
<strong>Greeting Prefix</strong>
<code>${settings.exampleString || 'Hello, World!'}</code>
<span class="setting-desc">Used in /api/hello endpoint</span>
</div>
<div class="setting-item">
<strong>Multiplier:</strong>
<strong>Multiplier</strong>
<code>${settings.exampleNumber || 42}</code>
<span class="setting-desc">Used in /api/calculate endpoint</span>
</div>
<div class="setting-item">
<strong>Include Timestamps:</strong>
<strong>Include Timestamps</strong>
<code>${settings.exampleBoolean ? 'Enabled' : 'Disabled'}</code>
<span class="setting-desc">Controls timestamp in API responses</span>
</div>
<div class="setting-item">
<strong>Operation Mode:</strong>
<strong>Operation Mode</strong>
<code>${settings.exampleSelect || 'option1'}</code>
<span class="setting-desc">Used in /api/mode endpoint</span>
</div>
</div>
`;
} catch (err) {
document.getElementById('settings-display').innerHTML =
document.getElementById('settings-display').innerHTML =
'<p class="error">Failed to load settings: ' + err.message + '</p>';
}
}
// Load action results
async function loadActions() {
try {
const response = await fetch('/api/actions');
const data = await response.json();
let html = '';
if (data.lastResult) {
html += `
<div class="action-result">
@@ -159,9 +206,9 @@
<p><strong>Message:</strong> ${data.lastResult.message}</p>
<p><strong>Timestamp:</strong> ${data.lastResult.timestamp}</p>
${data.lastResult.settingsUsed ? `
<details style="margin-top: 10px;">
<details class="action-details">
<summary>Settings Used</summary>
<pre style="margin-top: 5px;">${JSON.stringify(data.lastResult.settingsUsed, null, 2)}</pre>
<pre>${JSON.stringify(data.lastResult.settingsUsed, null, 2)}</pre>
</details>
` : ''}
${data.lastResult.calculation ? `
@@ -171,7 +218,7 @@
</div>
`;
}
if (data.history && data.history.length > 0) {
html += `
<div class="action-history">
@@ -188,64 +235,62 @@
</div>
</div>
`;
} else {
html += '<p class="loading">No action history yet. Execute an action from the admin panel to see results here.</p>';
} else if (!data.lastResult) {
html += '<p class="empty-state">No action history yet. Execute an action from the admin panel to see results here.</p>';
}
document.getElementById('actions-display').innerHTML = html;
} catch (err) {
document.getElementById('actions-display').innerHTML =
document.getElementById('actions-display').innerHTML =
'<p class="error">Failed to load actions: ' + err.message + '</p>';
}
}
// Test the hello API endpoint (uses greeting prefix setting)
async function testHello() {
const resultDiv = document.getElementById('api-result');
resultDiv.innerHTML = '<p class="loading">Calling API...</p>';
try {
const name = prompt('Enter your name (or leave blank for default):', 'World') || 'World';
const response = await fetch(`/api/hello?name=${encodeURIComponent(name)}`);
const data = await response.json();
let html = `
<div class="success">
<p><strong>Response:</strong> ${data.message}</p>
`;
if (data.timestamp) {
html += `<p><strong>Timestamp:</strong> ${data.timestamp}</p>`;
}
html += `
<p class="api-note"><em>Note: The greeting prefix comes from the "Greeting Prefix" setting in the admin panel.</em></p>
<p class="api-note"><em>The greeting prefix comes from the "Greeting Prefix" setting in the admin panel.</em></p>
</div>
`;
resultDiv.innerHTML = html;
} catch (err) {
resultDiv.innerHTML = '<p class="error">API call failed: ' + err.message + '</p>';
}
}
// Test the calculate API endpoint (uses multiplier setting)
async function testCalculate() {
const resultDiv = document.getElementById('api-result');
resultDiv.innerHTML = '<p class="loading">Calling API...</p>';
try {
const value = prompt('Enter a number to multiply:', '10') || '10';
const response = await fetch(`/api/calculate?value=${encodeURIComponent(value)}`);
const data = await response.json();
resultDiv.innerHTML = `
<div class="success">
<p><strong>Calculation:</strong> ${data.calculation}</p>
<p><strong>Input:</strong> ${data.input}</p>
<p><strong>Multiplier (from settings):</strong> ${data.multiplier}</p>
<p><strong>Result:</strong> ${data.result}</p>
<p class="api-note"><em>Note: The multiplier comes from the "Multiplier" setting in the admin panel.</em></p>
<p class="api-note"><em>The multiplier comes from the "Multiplier" setting in the admin panel.</em></p>
</div>
`;
} catch (err) {
@@ -253,21 +298,20 @@
}
}
// Test the mode API endpoint (uses select setting)
async function testMode() {
const resultDiv = document.getElementById('api-result');
resultDiv.innerHTML = '<p class="loading">Calling API...</p>';
try {
const response = await fetch('/api/mode');
const data = await response.json();
resultDiv.innerHTML = `
<div class="success">
<p><strong>Current Mode:</strong> ${data.mode}</p>
<p><strong>Description:</strong> ${data.description}</p>
<p><strong>Available Modes:</strong> ${data.availableModes.join(', ')}</p>
<p class="api-note"><em>Note: The mode comes from the "Operation Mode" setting in the admin panel.</em></p>
<p class="api-note"><em>The mode comes from the "Operation Mode" setting in the admin panel.</em></p>
</div>
`;
} catch (err) {
@@ -275,20 +319,19 @@
}
}
// Load public key
async function loadPublicKey() {
try {
const response = await fetch('/api/public-key');
const data = await response.json();
const container = document.getElementById('public-key-container');
if (data.publicKey) {
container.innerHTML = `
<div class="public-key-display">
<div class="public-key-header">
<span>Public Key</span>
<button onclick="copyPublicKey()" class="copy-button" title="Copy to clipboard">
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<button type="button" onclick="copyPublicKey(event)" class="btn btn-secondary btn-sm copy-button" title="Copy to clipboard">
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
</svg>
Copy
@@ -303,27 +346,25 @@
container.innerHTML = '<p class="error">No public key found. Your identity will be generated when you start the P2NS system.</p>';
}
} catch (err) {
document.getElementById('public-key-container').innerHTML =
document.getElementById('public-key-container').innerHTML =
'<p class="error">Failed to load public key: ' + err.message + '</p>';
}
}
// Copy public key to clipboard
async function copyPublicKey() {
async function copyPublicKey(event) {
const publicKeyText = document.getElementById('public-key-text');
if (!publicKeyText) return;
const publicKey = publicKeyText.textContent;
try {
await navigator.clipboard.writeText(publicKey);
// Visual feedback
const button = event?.target?.closest('.copy-button');
if (button) {
const originalHTML = button.innerHTML;
button.innerHTML = `
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
Copied!
@@ -335,15 +376,10 @@
}, 2000);
}
} catch (err) {
// Use ConfirmationModal if available, otherwise fallback to alert
if (window.ConfirmationModal) {
window.ConfirmationModal.alert(
'Failed to copy public key to clipboard: ' + err.message,
{
title: 'Error',
type: 'danger',
confirmText: 'OK'
}
{ title: 'Error', type: 'danger', confirmText: 'OK' }
);
} else {
alert('Failed to copy public key to clipboard: ' + err.message);
@@ -351,18 +387,16 @@
}
}
// Load all data when page loads
loadPluginInfo();
loadSettings();
loadPublicKey();
loadActions();
// Refresh settings and actions every 5 seconds to show updates
setInterval(() => {
loadPluginInfo();
loadSettings();
loadActions();
}, 5000);
</script>
</body>
</html>