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.
- illumio_pylo/API/APIConnector.py +82 -97
- illumio_pylo/API/CredentialsManager.py +38 -0
- illumio_pylo/Helpers/exports.py +1 -1
- illumio_pylo/__init__.py +1 -1
- illumio_pylo/cli/commands/credential_manager.py +379 -4
- illumio_pylo/cli/commands/label_delete_unused.py +0 -3
- illumio_pylo/cli/commands/ui/credential_manager_ui/app.js +449 -0
- illumio_pylo/cli/commands/ui/credential_manager_ui/index.html +168 -0
- illumio_pylo/cli/commands/ui/credential_manager_ui/styles.css +430 -0
- illumio_pylo/cli/commands/ven_duplicate_remover.py +79 -59
- illumio_pylo/utilities/cli.py +4 -1
- illumio_pylo/utilities/health_monitoring.py +5 -1
- {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.12.dist-info}/METADATA +2 -1
- {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.12.dist-info}/RECORD +17 -14
- {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.12.dist-info}/WHEEL +1 -1
- {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.12.dist-info}/licenses/LICENSE +0 -0
- {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.12.dist-info}/top_level.txt +0 -0
|
@@ -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">×</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">×</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">×</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>
|