Update Example Plugin
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -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 & 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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user