illumio-pylo 0.3.11__py3-none-any.whl → 0.3.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,449 @@
1
+ // Credential Manager Web Editor - JavaScript Application
2
+
3
+ (function() {
4
+ 'use strict';
5
+
6
+ // DOM Elements
7
+ const elements = {
8
+ // Credentials table
9
+ credentialsTable: document.getElementById('credentials-table'),
10
+ credentialsBody: document.getElementById('credentials-body'),
11
+ credentialsLoading: document.getElementById('credentials-loading'),
12
+ noCredentials: document.getElementById('no-credentials'),
13
+
14
+ // Notification
15
+ notification: document.getElementById('notification'),
16
+
17
+ // Buttons
18
+ btnNewCredential: document.getElementById('btn-new-credential'),
19
+ btnCloseModal: document.getElementById('btn-close-modal'),
20
+ btnCancel: document.getElementById('btn-cancel'),
21
+ btnSubmit: document.getElementById('btn-submit'),
22
+ btnCloseTestModal: document.getElementById('btn-close-test-modal'),
23
+ btnCloseTest: document.getElementById('btn-close-test'),
24
+
25
+ // Modal
26
+ credentialModal: document.getElementById('credential-modal'),
27
+ modalTitle: document.getElementById('modal-title'),
28
+ credentialForm: document.getElementById('credential-form'),
29
+
30
+ // Form fields
31
+ formMode: document.getElementById('form-mode'),
32
+ formOriginalName: document.getElementById('form-original-name'),
33
+ formName: document.getElementById('form-name'),
34
+ formFqdn: document.getElementById('form-fqdn'),
35
+ formPort: document.getElementById('form-port'),
36
+ formOrgId: document.getElementById('form-org-id'),
37
+ formApiUser: document.getElementById('form-api-user'),
38
+ formApiKey: document.getElementById('form-api-key'),
39
+ formVerifySsl: document.getElementById('form-verify-ssl'),
40
+ formEncrypt: document.getElementById('form-encrypt'),
41
+ formSshKey: document.getElementById('form-ssh-key'),
42
+ formUseWorkdir: document.getElementById('form-use-workdir'),
43
+
44
+ // Form sections
45
+ encryptionSection: document.getElementById('encryption-section'),
46
+ sshKeysSection: document.getElementById('ssh-keys-section'),
47
+ storageSection: document.getElementById('storage-section'),
48
+ apiKeyRequired: document.getElementById('api-key-required'),
49
+ apiKeyHint: document.getElementById('api-key-hint'),
50
+
51
+ // Test modal
52
+ testModal: document.getElementById('test-modal'),
53
+ testResult: document.getElementById('test-result'),
54
+
55
+ // Delete modal
56
+ deleteModal: document.getElementById('delete-modal'),
57
+ deleteCredentialName: document.getElementById('delete-credential-name'),
58
+ btnCloseDeleteModal: document.getElementById('btn-close-delete-modal'),
59
+ btnCancelDelete: document.getElementById('btn-cancel-delete'),
60
+ btnConfirmDelete: document.getElementById('btn-confirm-delete')
61
+ };
62
+
63
+ // State
64
+ let sshKeys = [];
65
+ let encryptionAvailable = false;
66
+ let credentialToDelete = null;
67
+
68
+ // Initialize the application
69
+ async function init() {
70
+ await loadCredentials();
71
+ await checkEncryptionStatus();
72
+ setupEventListeners();
73
+ }
74
+
75
+ // Setup event listeners
76
+ function setupEventListeners() {
77
+ // New credential button
78
+ elements.btnNewCredential.addEventListener('click', () => openCreateModal());
79
+
80
+ // Modal close buttons
81
+ elements.btnCloseModal.addEventListener('click', closeModal);
82
+ elements.btnCancel.addEventListener('click', closeModal);
83
+
84
+ // Form submission
85
+ elements.credentialForm.addEventListener('submit', handleFormSubmit);
86
+
87
+ // Encryption checkbox
88
+ elements.formEncrypt.addEventListener('change', toggleSshKeySelection);
89
+
90
+ // Test modal close
91
+ elements.btnCloseTestModal.addEventListener('click', closeTestModal);
92
+ elements.btnCloseTest.addEventListener('click', closeTestModal);
93
+
94
+ // Delete modal buttons
95
+ elements.btnCloseDeleteModal.addEventListener('click', closeDeleteModal);
96
+ elements.btnCancelDelete.addEventListener('click', closeDeleteModal);
97
+ elements.btnConfirmDelete.addEventListener('click', confirmDelete);
98
+
99
+ // Close modals on background click
100
+ elements.credentialModal.addEventListener('click', (e) => {
101
+ if (e.target === elements.credentialModal) closeModal();
102
+ });
103
+ elements.testModal.addEventListener('click', (e) => {
104
+ if (e.target === elements.testModal) closeTestModal();
105
+ });
106
+ elements.deleteModal.addEventListener('click', (e) => {
107
+ if (e.target === elements.deleteModal) closeDeleteModal();
108
+ });
109
+ }
110
+
111
+ // API Functions
112
+ async function apiCall(url, options = {}) {
113
+ try {
114
+ const response = await fetch(url, {
115
+ headers: {
116
+ 'Content-Type': 'application/json',
117
+ ...options.headers
118
+ },
119
+ ...options
120
+ });
121
+ const data = await response.json();
122
+ if (!response.ok) {
123
+ throw new Error(data.error || 'An error occurred');
124
+ }
125
+ return data;
126
+ } catch (error) {
127
+ throw error;
128
+ }
129
+ }
130
+
131
+ // Load all credentials
132
+ async function loadCredentials() {
133
+ elements.credentialsLoading.classList.remove('hidden');
134
+ elements.credentialsTable.classList.add('hidden');
135
+ elements.noCredentials.classList.add('hidden');
136
+
137
+ try {
138
+ const credentials = await apiCall('/api/credentials');
139
+ renderCredentials(credentials);
140
+ } catch (error) {
141
+ showNotification('Failed to load credentials: ' + error.message, 'error');
142
+ elements.credentialsLoading.classList.add('hidden');
143
+ }
144
+ }
145
+
146
+ // Render credentials table
147
+ function renderCredentials(credentials) {
148
+ elements.credentialsLoading.classList.add('hidden');
149
+
150
+ if (credentials.length === 0) {
151
+ elements.noCredentials.classList.remove('hidden');
152
+ elements.credentialsTable.classList.add('hidden');
153
+ return;
154
+ }
155
+
156
+ elements.credentialsTable.classList.remove('hidden');
157
+ elements.credentialsBody.innerHTML = '';
158
+
159
+ for (const cred of credentials) {
160
+ const row = document.createElement('tr');
161
+ row.innerHTML = `
162
+ <td><strong>${escapeHtml(cred.name)}</strong></td>
163
+ <td>${escapeHtml(cred.fqdn)}</td>
164
+ <td>${cred.port}</td>
165
+ <td>${cred.org_id}</td>
166
+ <td>${escapeHtml(cred.api_user)}</td>
167
+ <td class="${cred.verify_ssl ? 'status-yes' : 'status-no'}">${cred.verify_ssl ? 'Yes' : 'No'}</td>
168
+ <td title="${escapeHtml(cred.originating_file)}">${truncatePath(cred.originating_file)}</td>
169
+ <td class="actions-cell">
170
+ <button class="btn btn-small btn-success btn-test" data-name="${escapeHtml(cred.name)}">Test</button>
171
+ <button class="btn btn-small btn-secondary btn-edit" data-name="${escapeHtml(cred.name)}">Edit</button>
172
+ <button class="btn btn-small btn-danger btn-delete" data-name="${escapeHtml(cred.name)}">Delete</button>
173
+ </td>
174
+ `;
175
+ elements.credentialsBody.appendChild(row);
176
+ }
177
+
178
+ // Add event listeners to buttons
179
+ document.querySelectorAll('.btn-test').forEach(btn => {
180
+ btn.addEventListener('click', () => testCredential(btn.dataset.name));
181
+ });
182
+ document.querySelectorAll('.btn-edit').forEach(btn => {
183
+ btn.addEventListener('click', () => openEditModal(btn.dataset.name));
184
+ });
185
+ document.querySelectorAll('.btn-delete').forEach(btn => {
186
+ btn.addEventListener('click', () => openDeleteModal(btn.dataset.name));
187
+ });
188
+ }
189
+
190
+ // Check encryption availability and load SSH keys
191
+ async function checkEncryptionStatus() {
192
+ try {
193
+ const status = await apiCall('/api/encryption-status');
194
+ encryptionAvailable = status.available;
195
+
196
+ if (encryptionAvailable) {
197
+ const keysData = await apiCall('/api/ssh-keys');
198
+ sshKeys = keysData.keys || [];
199
+
200
+ if (sshKeys.length > 0) {
201
+ elements.encryptionSection.classList.remove('hidden');
202
+ populateSshKeySelect();
203
+ }
204
+ }
205
+ } catch (error) {
206
+ console.log('Encryption not available:', error.message);
207
+ }
208
+ }
209
+
210
+ // Populate SSH key select dropdown
211
+ function populateSshKeySelect() {
212
+ elements.formSshKey.innerHTML = '';
213
+ for (const key of sshKeys) {
214
+ const option = document.createElement('option');
215
+ option.value = key.index;
216
+ option.textContent = `${key.type} | ${key.fingerprint.substring(0, 16)}... | ${key.comment || 'No comment'}`;
217
+ elements.formSshKey.appendChild(option);
218
+ }
219
+ }
220
+
221
+ // Toggle SSH key selection visibility
222
+ function toggleSshKeySelection() {
223
+ if (elements.formEncrypt.checked && sshKeys.length > 0) {
224
+ elements.sshKeysSection.classList.remove('hidden');
225
+ } else {
226
+ elements.sshKeysSection.classList.add('hidden');
227
+ }
228
+ }
229
+
230
+ // Open modal for creating new credential
231
+ function openCreateModal() {
232
+ resetForm();
233
+ elements.formMode.value = 'create';
234
+ elements.modalTitle.textContent = 'New Credential';
235
+ elements.btnSubmit.textContent = 'Create';
236
+ elements.formName.removeAttribute('readonly');
237
+ elements.formApiKey.setAttribute('required', 'required');
238
+ elements.apiKeyRequired.classList.remove('hidden');
239
+ elements.apiKeyHint.classList.add('hidden');
240
+ elements.storageSection.classList.remove('hidden');
241
+ elements.credentialModal.classList.remove('hidden');
242
+ elements.formName.focus();
243
+ }
244
+
245
+ // Open modal for editing existing credential
246
+ async function openEditModal(name) {
247
+ resetForm();
248
+
249
+ try {
250
+ const credential = await apiCall(`/api/credentials/${encodeURIComponent(name)}`);
251
+
252
+ elements.formMode.value = 'edit';
253
+ elements.formOriginalName.value = credential.name;
254
+ elements.modalTitle.textContent = 'Edit Credential';
255
+ elements.btnSubmit.textContent = 'Update';
256
+
257
+ // Populate form
258
+ elements.formName.value = credential.name;
259
+ elements.formName.setAttribute('readonly', 'readonly');
260
+ elements.formFqdn.value = credential.fqdn;
261
+ elements.formPort.value = credential.port;
262
+ elements.formOrgId.value = credential.org_id;
263
+ elements.formApiUser.value = credential.api_user;
264
+ elements.formVerifySsl.checked = credential.verify_ssl;
265
+
266
+ // API key is optional for updates
267
+ elements.formApiKey.removeAttribute('required');
268
+ elements.apiKeyRequired.classList.add('hidden');
269
+ elements.apiKeyHint.classList.remove('hidden');
270
+
271
+ // Hide storage section for edits
272
+ elements.storageSection.classList.add('hidden');
273
+
274
+ elements.credentialModal.classList.remove('hidden');
275
+ elements.formFqdn.focus();
276
+ } catch (error) {
277
+ showNotification('Failed to load credential: ' + error.message, 'error');
278
+ }
279
+ }
280
+
281
+ // Open modal for deleting credential
282
+ function openDeleteModal(name) {
283
+ credentialToDelete = name;
284
+ elements.deleteCredentialName.textContent = name;
285
+ elements.deleteModal.classList.remove('hidden');
286
+ }
287
+
288
+ // Close modal
289
+ function closeModal() {
290
+ elements.credentialModal.classList.add('hidden');
291
+ resetForm();
292
+ }
293
+
294
+ // Close delete modal
295
+ function closeDeleteModal() {
296
+ elements.deleteModal.classList.add('hidden');
297
+ credentialToDelete = null;
298
+ }
299
+
300
+ // Reset form
301
+ function resetForm() {
302
+ elements.credentialForm.reset();
303
+ elements.formMode.value = 'create';
304
+ elements.formOriginalName.value = '';
305
+ elements.formPort.value = '8443';
306
+ elements.formOrgId.value = '1';
307
+ elements.formVerifySsl.checked = true;
308
+ elements.formEncrypt.checked = false;
309
+ elements.sshKeysSection.classList.add('hidden');
310
+ }
311
+
312
+ // Handle form submission
313
+ async function handleFormSubmit(e) {
314
+ e.preventDefault();
315
+
316
+ const mode = elements.formMode.value;
317
+ const data = {
318
+ name: elements.formName.value.trim(),
319
+ fqdn: elements.formFqdn.value.trim(),
320
+ port: parseInt(elements.formPort.value),
321
+ org_id: parseInt(elements.formOrgId.value),
322
+ api_user: elements.formApiUser.value.trim(),
323
+ verify_ssl: elements.formVerifySsl.checked
324
+ };
325
+
326
+ // Add API key if provided
327
+ const apiKey = elements.formApiKey.value;
328
+ if (apiKey) {
329
+ data.api_key = apiKey;
330
+ } else if (mode === 'create') {
331
+ showNotification('API key is required', 'error');
332
+ return;
333
+ }
334
+
335
+ // Add encryption settings
336
+ if (elements.formEncrypt.checked && sshKeys.length > 0) {
337
+ data.encrypt = true;
338
+ data.ssh_key_index = parseInt(elements.formSshKey.value);
339
+ }
340
+
341
+ // Add storage location for create
342
+ if (mode === 'create') {
343
+ data.use_current_workdir = elements.formUseWorkdir.checked;
344
+ }
345
+
346
+ elements.btnSubmit.disabled = true;
347
+ elements.btnSubmit.textContent = mode === 'create' ? 'Creating...' : 'Updating...';
348
+
349
+ try {
350
+ if (mode === 'create') {
351
+ await apiCall('/api/credentials', {
352
+ method: 'POST',
353
+ body: JSON.stringify(data)
354
+ });
355
+ showNotification('Credential created successfully!', 'success');
356
+ } else {
357
+ const originalName = elements.formOriginalName.value;
358
+ await apiCall(`/api/credentials/${encodeURIComponent(originalName)}`, {
359
+ method: 'PUT',
360
+ body: JSON.stringify(data)
361
+ });
362
+ showNotification('Credential updated successfully!', 'success');
363
+ }
364
+
365
+ closeModal();
366
+ await loadCredentials();
367
+ } catch (error) {
368
+ showNotification('Failed to save credential: ' + error.message, 'error');
369
+ } finally {
370
+ elements.btnSubmit.disabled = false;
371
+ elements.btnSubmit.textContent = mode === 'create' ? 'Create' : 'Update';
372
+ }
373
+ }
374
+
375
+ // Confirm credential deletion
376
+ async function confirmDelete() {
377
+ if (!credentialToDelete) return;
378
+
379
+ elements.btnConfirmDelete.disabled = true;
380
+ elements.btnConfirmDelete.textContent = 'Deleting...';
381
+
382
+ try {
383
+ await apiCall(`/api/credentials/${encodeURIComponent(credentialToDelete)}`, {
384
+ method: 'DELETE'
385
+ });
386
+ showNotification('Credential deleted successfully!', 'success');
387
+ closeDeleteModal();
388
+ await loadCredentials();
389
+ } catch (error) {
390
+ showNotification('Failed to delete credential: ' + error.message, 'error');
391
+ } finally {
392
+ elements.btnConfirmDelete.disabled = false;
393
+ elements.btnConfirmDelete.textContent = 'Delete';
394
+ }
395
+ }
396
+
397
+ // Test credential connection
398
+ async function testCredential(name) {
399
+ elements.testResult.className = 'test-result loading';
400
+ elements.testResult.textContent = 'Testing connection...';
401
+ elements.testModal.classList.remove('hidden');
402
+
403
+ try {
404
+ const result = await apiCall(`/api/credentials/${encodeURIComponent(name)}/test`, {
405
+ method: 'POST'
406
+ });
407
+ elements.testResult.className = 'test-result success';
408
+ elements.testResult.textContent = `Connection to "${name}" successful!`;
409
+ } catch (error) {
410
+ elements.testResult.className = 'test-result error';
411
+ elements.testResult.textContent = `Connection failed: ${error.message}`;
412
+ }
413
+ }
414
+
415
+ // Close test modal
416
+ function closeTestModal() {
417
+ elements.testModal.classList.add('hidden');
418
+ }
419
+
420
+ // Show notification
421
+ function showNotification(message, type = 'info') {
422
+ elements.notification.textContent = message;
423
+ elements.notification.className = `notification ${type}`;
424
+ elements.notification.classList.remove('hidden');
425
+
426
+ // Auto-hide after 5 seconds
427
+ setTimeout(() => {
428
+ elements.notification.classList.add('hidden');
429
+ }, 5000);
430
+ }
431
+
432
+ // Utility: Escape HTML
433
+ function escapeHtml(text) {
434
+ const div = document.createElement('div');
435
+ div.textContent = text;
436
+ return div.innerHTML;
437
+ }
438
+
439
+ // Utility: Truncate file path for display
440
+ function truncatePath(path) {
441
+ if (path.length > 30) {
442
+ return '...' + path.slice(-27);
443
+ }
444
+ return path;
445
+ }
446
+
447
+ // Start the application
448
+ document.addEventListener('DOMContentLoaded', init);
449
+ })();
@@ -0,0 +1,168 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Credential Manager - Web Editor</title>
7
+ <link rel="stylesheet" href="/static/styles.css">
8
+ </head>
9
+ <body>
10
+ <div class="container">
11
+ <header>
12
+ <h1>🔐 PYLO Credential Manager</h1>
13
+ <p class="subtitle">Manage your PCE credentials</p>
14
+ </header>
15
+
16
+ <!-- Notification area -->
17
+ <div id="notification" class="notification hidden"></div>
18
+
19
+ <!-- Main content area -->
20
+ <main>
21
+ <!-- Credentials list section -->
22
+ <section id="credentials-section">
23
+ <div class="section-header">
24
+ <h2>Stored Credentials</h2>
25
+ <button id="btn-new-credential" class="btn btn-primary">+ New Credential</button>
26
+ </div>
27
+ <div id="credentials-loading" class="loading">Loading credentials...</div>
28
+ <table id="credentials-table" class="hidden">
29
+ <thead>
30
+ <tr>
31
+ <th>Name</th>
32
+ <th>FQDN</th>
33
+ <th>Port</th>
34
+ <th>Org ID</th>
35
+ <th>API User</th>
36
+ <th>SSL</th>
37
+ <th>File</th>
38
+ <th>Actions</th>
39
+ </tr>
40
+ </thead>
41
+ <tbody id="credentials-body">
42
+ </tbody>
43
+ </table>
44
+ <div id="no-credentials" class="empty-state hidden">
45
+ <p>No credentials found. Click "New Credential" to create one.</p>
46
+ </div>
47
+ </section>
48
+ </main>
49
+
50
+ <!-- Modal for Create/Edit credential -->
51
+ <div id="credential-modal" class="modal hidden">
52
+ <div class="modal-content">
53
+ <div class="modal-header">
54
+ <h2 id="modal-title">New Credential</h2>
55
+ <button class="btn-close" id="btn-close-modal">&times;</button>
56
+ </div>
57
+ <form id="credential-form">
58
+ <input type="hidden" id="form-mode" value="create">
59
+ <input type="hidden" id="form-original-name" value="">
60
+
61
+ <div class="form-group">
62
+ <label for="form-name">Profile Name *</label>
63
+ <input type="text" id="form-name" required placeholder="e.g., prod-pce">
64
+ </div>
65
+
66
+ <div class="form-group">
67
+ <label for="form-fqdn">PCE FQDN *</label>
68
+ <input type="text" id="form-fqdn" required placeholder="e.g., pce1.mycompany.com">
69
+ </div>
70
+
71
+ <div class="form-row">
72
+ <div class="form-group">
73
+ <label for="form-port">Port *</label>
74
+ <input type="number" id="form-port" required value="8443" min="1" max="65535">
75
+ </div>
76
+ <div class="form-group">
77
+ <label for="form-org-id">Organization ID *</label>
78
+ <input type="number" id="form-org-id" required value="1" min="1">
79
+ </div>
80
+ </div>
81
+
82
+ <div class="form-group">
83
+ <label for="form-api-user">API User *</label>
84
+ <input type="text" id="form-api-user" required placeholder="e.g., api_xxx">
85
+ </div>
86
+
87
+ <div class="form-group">
88
+ <label for="form-api-key">API Key <span id="api-key-required">*</span></label>
89
+ <input type="password" id="form-api-key" placeholder="Enter API key">
90
+ <small id="api-key-hint" class="hidden">Leave empty to keep current key</small>
91
+ </div>
92
+
93
+ <div class="form-group checkbox-group">
94
+ <label>
95
+ <input type="checkbox" id="form-verify-ssl" checked>
96
+ Verify SSL/TLS Certificate
97
+ </label>
98
+ </div>
99
+
100
+ <!-- Encryption section -->
101
+ <div id="encryption-section" class="form-section hidden">
102
+ <h3>API Key Encryption</h3>
103
+ <div class="form-group checkbox-group">
104
+ <label>
105
+ <input type="checkbox" id="form-encrypt">
106
+ Encrypt API key with SSH key
107
+ </label>
108
+ </div>
109
+ <div id="ssh-keys-section" class="hidden">
110
+ <label for="form-ssh-key">Select SSH Key:</label>
111
+ <select id="form-ssh-key">
112
+ </select>
113
+ </div>
114
+ </div>
115
+
116
+ <!-- Storage location (only for create) -->
117
+ <div id="storage-section" class="form-group checkbox-group">
118
+ <label>
119
+ <input type="checkbox" id="form-use-workdir">
120
+ Save in current working directory (otherwise user home)
121
+ </label>
122
+ </div>
123
+
124
+ <div class="form-actions">
125
+ <button type="button" class="btn btn-secondary" id="btn-cancel">Cancel</button>
126
+ <button type="submit" class="btn btn-primary" id="btn-submit">Create</button>
127
+ </div>
128
+ </form>
129
+ </div>
130
+ </div>
131
+
132
+ <!-- Test result modal -->
133
+ <div id="test-modal" class="modal hidden">
134
+ <div class="modal-content modal-small">
135
+ <div class="modal-header">
136
+ <h2>Connection Test</h2>
137
+ <button class="btn-close" id="btn-close-test-modal">&times;</button>
138
+ </div>
139
+ <div id="test-result" class="test-result">
140
+ </div>
141
+ <div class="form-actions">
142
+ <button type="button" class="btn btn-primary" id="btn-close-test">Close</button>
143
+ </div>
144
+ </div>
145
+ </div>
146
+
147
+ <!-- Delete confirmation modal -->
148
+ <div id="delete-modal" class="modal hidden">
149
+ <div class="modal-content modal-small">
150
+ <div class="modal-header">
151
+ <h2>Confirm Deletion</h2>
152
+ <button class="btn-close" id="btn-close-delete-modal">&times;</button>
153
+ </div>
154
+ <div class="delete-confirmation">
155
+ <p>Are you sure you want to delete the credential "<strong id="delete-credential-name"></strong>"?</p>
156
+ <p class="warning-text">This action cannot be undone.</p>
157
+ </div>
158
+ <div class="form-actions">
159
+ <button type="button" class="btn btn-secondary" id="btn-cancel-delete">Cancel</button>
160
+ <button type="button" class="btn btn-danger" id="btn-confirm-delete">Delete</button>
161
+ </div>
162
+ </div>
163
+ </div>
164
+ </div>
165
+
166
+ <script src="/static/app.js"></script>
167
+ </body>
168
+ </html>