illumio-pylo 0.3.11__py3-none-any.whl → 0.3.13__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 +138 -106
- illumio_pylo/API/CredentialsManager.py +168 -3
- illumio_pylo/API/Explorer.py +619 -14
- illumio_pylo/API/JsonPayloadTypes.py +64 -4
- illumio_pylo/FilterQuery.py +892 -0
- illumio_pylo/Helpers/exports.py +1 -1
- illumio_pylo/LabelCommon.py +13 -3
- illumio_pylo/LabelDimension.py +109 -0
- illumio_pylo/LabelStore.py +97 -38
- illumio_pylo/WorkloadStore.py +58 -0
- illumio_pylo/__init__.py +9 -3
- illumio_pylo/cli/__init__.py +5 -2
- illumio_pylo/cli/commands/__init__.py +1 -0
- illumio_pylo/cli/commands/credential_manager.py +555 -4
- illumio_pylo/cli/commands/label_delete_unused.py +0 -3
- illumio_pylo/cli/commands/traffic_export.py +358 -0
- illumio_pylo/cli/commands/ui/credential_manager_ui/app.js +638 -0
- illumio_pylo/cli/commands/ui/credential_manager_ui/index.html +217 -0
- illumio_pylo/cli/commands/ui/credential_manager_ui/styles.css +581 -0
- illumio_pylo/cli/commands/update_pce_objects_cache.py +1 -2
- illumio_pylo/cli/commands/ven_duplicate_remover.py +79 -59
- illumio_pylo/cli/commands/workload_export.py +29 -0
- 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.13.dist-info}/METADATA +2 -1
- {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.13.dist-info}/RECORD +29 -24
- {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.13.dist-info}/WHEEL +1 -1
- illumio_pylo/Query.py +0 -331
- {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.13.dist-info}/licenses/LICENSE +0 -0
- {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.13.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,638 @@
|
|
|
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
|
+
themeToggle: document.getElementById('theme-toggle'),
|
|
25
|
+
|
|
26
|
+
// Modal
|
|
27
|
+
credentialModal: document.getElementById('credential-modal'),
|
|
28
|
+
modalTitle: document.getElementById('modal-title'),
|
|
29
|
+
credentialForm: document.getElementById('credential-form'),
|
|
30
|
+
|
|
31
|
+
// Form fields
|
|
32
|
+
formMode: document.getElementById('form-mode'),
|
|
33
|
+
formOriginalName: document.getElementById('form-original-name'),
|
|
34
|
+
formName: document.getElementById('form-name'),
|
|
35
|
+
formFqdn: document.getElementById('form-fqdn'),
|
|
36
|
+
formPort: document.getElementById('form-port'),
|
|
37
|
+
formOrgId: document.getElementById('form-org-id'),
|
|
38
|
+
formApiUser: document.getElementById('form-api-user'),
|
|
39
|
+
formApiKey: document.getElementById('form-api-key'),
|
|
40
|
+
formVerifySsl: document.getElementById('form-verify-ssl'),
|
|
41
|
+
formEncrypt: document.getElementById('form-encrypt'),
|
|
42
|
+
formSshKey: document.getElementById('form-ssh-key'),
|
|
43
|
+
formUseWorkdir: document.getElementById('form-use-workdir'),
|
|
44
|
+
|
|
45
|
+
// Form sections
|
|
46
|
+
encryptionSection: document.getElementById('encryption-section'),
|
|
47
|
+
sshKeysSection: document.getElementById('ssh-keys-section'),
|
|
48
|
+
storageSection: document.getElementById('storage-section'),
|
|
49
|
+
apiKeyRequired: document.getElementById('api-key-required'),
|
|
50
|
+
apiKeyHint: document.getElementById('api-key-hint'),
|
|
51
|
+
|
|
52
|
+
// Test modal
|
|
53
|
+
testModal: document.getElementById('test-modal'),
|
|
54
|
+
testResult: document.getElementById('test-result'),
|
|
55
|
+
|
|
56
|
+
// Delete modal
|
|
57
|
+
deleteModal: document.getElementById('delete-modal'),
|
|
58
|
+
deleteCredentialName: document.getElementById('delete-credential-name'),
|
|
59
|
+
btnCloseDeleteModal: document.getElementById('btn-close-delete-modal'),
|
|
60
|
+
btnCancelDelete: document.getElementById('btn-cancel-delete'),
|
|
61
|
+
btnConfirmDelete: document.getElementById('btn-confirm-delete'),
|
|
62
|
+
|
|
63
|
+
// Encrypt modal
|
|
64
|
+
encryptModal: document.getElementById('encrypt-modal'),
|
|
65
|
+
encryptCredentialName: document.getElementById('encrypt-credential-name'),
|
|
66
|
+
encryptSshKey: document.getElementById('encrypt-ssh-key'),
|
|
67
|
+
btnCloseEncryptModal: document.getElementById('btn-close-encrypt-modal'),
|
|
68
|
+
btnCancelEncrypt: document.getElementById('btn-cancel-encrypt'),
|
|
69
|
+
btnConfirmEncrypt: document.getElementById('btn-confirm-encrypt'),
|
|
70
|
+
|
|
71
|
+
// Shutdown modal
|
|
72
|
+
shutdownModal: document.getElementById('shutdown-modal'),
|
|
73
|
+
btnShutdown: document.getElementById('btn-shutdown'),
|
|
74
|
+
btnCloseShutdownModal: document.getElementById('btn-close-shutdown-modal'),
|
|
75
|
+
btnCancelShutdown: document.getElementById('btn-cancel-shutdown'),
|
|
76
|
+
btnConfirmShutdown: document.getElementById('btn-confirm-shutdown'),
|
|
77
|
+
shutdownMessage: document.getElementById('shutdown-message'),
|
|
78
|
+
shutdownCountdown: document.getElementById('shutdown-countdown'),
|
|
79
|
+
shutdownActions: document.getElementById('shutdown-actions')
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// State
|
|
83
|
+
let sshKeys = [];
|
|
84
|
+
let encryptionAvailable = false;
|
|
85
|
+
let credentialToDelete = null;
|
|
86
|
+
let credentialToEncrypt = null;
|
|
87
|
+
let shutdownInProgress = false;
|
|
88
|
+
|
|
89
|
+
// Theme Management
|
|
90
|
+
function initTheme() {
|
|
91
|
+
// Check for saved theme preference or default to browser preference
|
|
92
|
+
const savedTheme = localStorage.getItem('theme');
|
|
93
|
+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
94
|
+
|
|
95
|
+
if (savedTheme) {
|
|
96
|
+
setTheme(savedTheme);
|
|
97
|
+
} else if (prefersDark) {
|
|
98
|
+
setTheme('dark');
|
|
99
|
+
} else {
|
|
100
|
+
setTheme('light');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Listen for browser theme changes
|
|
104
|
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
|
|
105
|
+
if (!localStorage.getItem('theme')) {
|
|
106
|
+
setTheme(e.matches ? 'dark' : 'light');
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function setTheme(theme) {
|
|
112
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
113
|
+
elements.themeToggle.textContent = theme === 'dark' ? '☀️' : '🌙';
|
|
114
|
+
elements.themeToggle.title = theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function toggleTheme() {
|
|
118
|
+
const currentTheme = document.documentElement.getAttribute('data-theme');
|
|
119
|
+
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
|
120
|
+
setTheme(newTheme);
|
|
121
|
+
localStorage.setItem('theme', newTheme);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Initialize the application
|
|
125
|
+
async function init() {
|
|
126
|
+
initTheme();
|
|
127
|
+
await checkEncryptionStatus();
|
|
128
|
+
await loadCredentials();
|
|
129
|
+
setupEventListeners();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Setup event listeners
|
|
133
|
+
function setupEventListeners() {
|
|
134
|
+
// Theme toggle button
|
|
135
|
+
elements.themeToggle.addEventListener('click', toggleTheme);
|
|
136
|
+
|
|
137
|
+
// New credential button
|
|
138
|
+
elements.btnNewCredential.addEventListener('click', () => openCreateModal());
|
|
139
|
+
|
|
140
|
+
// Modal close buttons
|
|
141
|
+
elements.btnCloseModal.addEventListener('click', closeModal);
|
|
142
|
+
elements.btnCancel.addEventListener('click', closeModal);
|
|
143
|
+
|
|
144
|
+
// Form submission
|
|
145
|
+
elements.credentialForm.addEventListener('submit', handleFormSubmit);
|
|
146
|
+
|
|
147
|
+
// Encryption checkbox
|
|
148
|
+
elements.formEncrypt.addEventListener('change', toggleSshKeySelection);
|
|
149
|
+
|
|
150
|
+
// Test modal close
|
|
151
|
+
elements.btnCloseTestModal.addEventListener('click', closeTestModal);
|
|
152
|
+
elements.btnCloseTest.addEventListener('click', closeTestModal);
|
|
153
|
+
|
|
154
|
+
// Delete modal buttons
|
|
155
|
+
elements.btnCloseDeleteModal.addEventListener('click', closeDeleteModal);
|
|
156
|
+
elements.btnCancelDelete.addEventListener('click', closeDeleteModal);
|
|
157
|
+
elements.btnConfirmDelete.addEventListener('click', confirmDelete);
|
|
158
|
+
|
|
159
|
+
// Encrypt modal buttons
|
|
160
|
+
elements.btnCloseEncryptModal.addEventListener('click', closeEncryptModal);
|
|
161
|
+
elements.btnCancelEncrypt.addEventListener('click', closeEncryptModal);
|
|
162
|
+
elements.btnConfirmEncrypt.addEventListener('click', confirmEncrypt);
|
|
163
|
+
|
|
164
|
+
// Shutdown modal buttons
|
|
165
|
+
elements.btnShutdown.addEventListener('click', openShutdownModal);
|
|
166
|
+
elements.btnConfirmShutdown.addEventListener('click', confirmShutdown);
|
|
167
|
+
|
|
168
|
+
// Close modals on background click
|
|
169
|
+
elements.credentialModal.addEventListener('click', (e) => {
|
|
170
|
+
if (e.target === elements.credentialModal) closeModal();
|
|
171
|
+
});
|
|
172
|
+
elements.testModal.addEventListener('click', (e) => {
|
|
173
|
+
if (e.target === elements.testModal) closeTestModal();
|
|
174
|
+
});
|
|
175
|
+
elements.deleteModal.addEventListener('click', (e) => {
|
|
176
|
+
if (e.target === elements.deleteModal) closeDeleteModal();
|
|
177
|
+
});
|
|
178
|
+
elements.encryptModal.addEventListener('click', (e) => {
|
|
179
|
+
if (e.target === elements.encryptModal) closeEncryptModal();
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// API Functions
|
|
184
|
+
async function apiCall(url, options = {}) {
|
|
185
|
+
try {
|
|
186
|
+
const response = await fetch(url, {
|
|
187
|
+
headers: {
|
|
188
|
+
'Content-Type': 'application/json',
|
|
189
|
+
...options.headers
|
|
190
|
+
},
|
|
191
|
+
...options
|
|
192
|
+
});
|
|
193
|
+
const data = await response.json();
|
|
194
|
+
if (!response.ok) {
|
|
195
|
+
throw new Error(data.error || 'An error occurred');
|
|
196
|
+
}
|
|
197
|
+
return data;
|
|
198
|
+
} catch (error) {
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Load all credentials
|
|
204
|
+
async function loadCredentials() {
|
|
205
|
+
elements.credentialsLoading.classList.remove('hidden');
|
|
206
|
+
elements.credentialsTable.classList.add('hidden');
|
|
207
|
+
elements.noCredentials.classList.add('hidden');
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
const credentials = await apiCall('/api/credentials');
|
|
211
|
+
renderCredentials(credentials);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
showNotification('Failed to load credentials: ' + error.message, 'error');
|
|
214
|
+
elements.credentialsLoading.classList.add('hidden');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Render credentials table
|
|
219
|
+
function renderCredentials(credentials) {
|
|
220
|
+
elements.credentialsLoading.classList.add('hidden');
|
|
221
|
+
|
|
222
|
+
if (credentials.length === 0) {
|
|
223
|
+
elements.noCredentials.classList.remove('hidden');
|
|
224
|
+
elements.credentialsTable.classList.add('hidden');
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
elements.credentialsTable.classList.remove('hidden');
|
|
229
|
+
elements.credentialsBody.innerHTML = '';
|
|
230
|
+
|
|
231
|
+
for (const cred of credentials) {
|
|
232
|
+
const row = document.createElement('tr');
|
|
233
|
+
// Show encrypt button only if encryption is available and key is not already encrypted
|
|
234
|
+
const showEncryptBtn = encryptionAvailable && sshKeys.length > 0 && !cred.api_key_encrypted;
|
|
235
|
+
console.log('Rendering credential:', cred.name, 'Show Encrypt Button:', showEncryptBtn, 'Encryption Available:', encryptionAvailable, 'SSH Keys:', sshKeys.length, 'API Key Encrypted:', cred.api_key_encrypted);
|
|
236
|
+
row.innerHTML = `
|
|
237
|
+
<td><strong>${escapeHtml(cred.name)}</strong></td>
|
|
238
|
+
<td>${escapeHtml(cred.fqdn)}</td>
|
|
239
|
+
<td>${cred.port}</td>
|
|
240
|
+
<td>${cred.org_id}</td>
|
|
241
|
+
<td>${escapeHtml(cred.api_user)}</td>
|
|
242
|
+
<td class="${cred.api_key_encrypted ? 'status-yes' : 'status-no'}">${cred.api_key_encrypted ? 'Yes' : 'No'}</td>
|
|
243
|
+
<td class="${cred.verify_ssl ? 'status-yes' : 'status-no'}">${cred.verify_ssl ? 'Yes' : 'No'}</td>
|
|
244
|
+
<td title="${escapeHtml(cred.originating_file)}">${truncatePath(cred.originating_file)}</td>
|
|
245
|
+
<td class="actions-cell">
|
|
246
|
+
<button class="btn btn-small btn-success btn-test" data-name="${escapeHtml(cred.name)}">Test</button>
|
|
247
|
+
<button class="btn btn-small btn-secondary btn-edit" data-name="${escapeHtml(cred.name)}">Edit</button>
|
|
248
|
+
${showEncryptBtn ? `<button class="btn btn-small btn-warning btn-encrypt" data-name="${escapeHtml(cred.name)}">Encrypt</button>` : ''}
|
|
249
|
+
<button class="btn btn-small btn-danger btn-delete" data-name="${escapeHtml(cred.name)}">Delete</button>
|
|
250
|
+
</td>
|
|
251
|
+
`;
|
|
252
|
+
elements.credentialsBody.appendChild(row);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Add event listeners to buttons
|
|
256
|
+
document.querySelectorAll('.btn-test').forEach(btn => {
|
|
257
|
+
btn.addEventListener('click', () => testCredential(btn.dataset.name));
|
|
258
|
+
});
|
|
259
|
+
document.querySelectorAll('.btn-edit').forEach(btn => {
|
|
260
|
+
btn.addEventListener('click', () => openEditModal(btn.dataset.name));
|
|
261
|
+
});
|
|
262
|
+
document.querySelectorAll('.btn-encrypt').forEach(btn => {
|
|
263
|
+
btn.addEventListener('click', () => openEncryptModal(btn.dataset.name));
|
|
264
|
+
});
|
|
265
|
+
document.querySelectorAll('.btn-delete').forEach(btn => {
|
|
266
|
+
btn.addEventListener('click', () => openDeleteModal(btn.dataset.name));
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Check encryption availability and load SSH keys
|
|
271
|
+
async function checkEncryptionStatus() {
|
|
272
|
+
try {
|
|
273
|
+
const status = await apiCall('/api/encryption-status');
|
|
274
|
+
encryptionAvailable = status.available;
|
|
275
|
+
|
|
276
|
+
if (encryptionAvailable) {
|
|
277
|
+
const keysData = await apiCall('/api/ssh-keys');
|
|
278
|
+
sshKeys = keysData.keys || [];
|
|
279
|
+
|
|
280
|
+
if (sshKeys.length > 0) {
|
|
281
|
+
elements.encryptionSection.classList.remove('hidden');
|
|
282
|
+
populateSshKeySelect();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
} catch (error) {
|
|
286
|
+
console.log('Encryption not available:', error.message);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Populate SSH key select dropdown
|
|
291
|
+
function populateSshKeySelect() {
|
|
292
|
+
elements.formSshKey.innerHTML = '';
|
|
293
|
+
for (const key of sshKeys) {
|
|
294
|
+
const option = document.createElement('option');
|
|
295
|
+
option.value = key.index;
|
|
296
|
+
option.textContent = `${key.type} | ${key.fingerprint.substring(0, 16)}... | ${key.comment || 'No comment'}`;
|
|
297
|
+
elements.formSshKey.appendChild(option);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Toggle SSH key selection visibility
|
|
302
|
+
function toggleSshKeySelection() {
|
|
303
|
+
if (elements.formEncrypt.checked && sshKeys.length > 0) {
|
|
304
|
+
elements.sshKeysSection.classList.remove('hidden');
|
|
305
|
+
} else {
|
|
306
|
+
elements.sshKeysSection.classList.add('hidden');
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Open modal for creating new credential
|
|
311
|
+
function openCreateModal() {
|
|
312
|
+
resetForm();
|
|
313
|
+
elements.formMode.value = 'create';
|
|
314
|
+
elements.modalTitle.textContent = 'New Credential';
|
|
315
|
+
elements.btnSubmit.textContent = 'Create';
|
|
316
|
+
elements.formName.removeAttribute('readonly');
|
|
317
|
+
elements.formApiKey.setAttribute('required', 'required');
|
|
318
|
+
elements.apiKeyRequired.classList.remove('hidden');
|
|
319
|
+
elements.apiKeyHint.classList.add('hidden');
|
|
320
|
+
elements.storageSection.classList.remove('hidden');
|
|
321
|
+
elements.credentialModal.classList.remove('hidden');
|
|
322
|
+
elements.formName.focus();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Open modal for editing existing credential
|
|
326
|
+
async function openEditModal(name) {
|
|
327
|
+
resetForm();
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
const credential = await apiCall(`/api/credentials/${encodeURIComponent(name)}`);
|
|
331
|
+
|
|
332
|
+
elements.formMode.value = 'edit';
|
|
333
|
+
elements.formOriginalName.value = credential.name;
|
|
334
|
+
elements.modalTitle.textContent = 'Edit Credential';
|
|
335
|
+
elements.btnSubmit.textContent = 'Update';
|
|
336
|
+
|
|
337
|
+
// Populate form
|
|
338
|
+
elements.formName.value = credential.name;
|
|
339
|
+
elements.formName.setAttribute('readonly', 'readonly');
|
|
340
|
+
elements.formFqdn.value = credential.fqdn;
|
|
341
|
+
elements.formPort.value = credential.port;
|
|
342
|
+
elements.formOrgId.value = credential.org_id;
|
|
343
|
+
elements.formApiUser.value = credential.api_user;
|
|
344
|
+
elements.formVerifySsl.checked = credential.verify_ssl;
|
|
345
|
+
|
|
346
|
+
// API key is optional for updates
|
|
347
|
+
elements.formApiKey.removeAttribute('required');
|
|
348
|
+
elements.apiKeyRequired.classList.add('hidden');
|
|
349
|
+
elements.apiKeyHint.classList.remove('hidden');
|
|
350
|
+
|
|
351
|
+
// Hide storage section for edits
|
|
352
|
+
elements.storageSection.classList.add('hidden');
|
|
353
|
+
|
|
354
|
+
elements.credentialModal.classList.remove('hidden');
|
|
355
|
+
elements.formFqdn.focus();
|
|
356
|
+
} catch (error) {
|
|
357
|
+
showNotification('Failed to load credential: ' + error.message, 'error');
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Open modal for deleting credential
|
|
362
|
+
function openDeleteModal(name) {
|
|
363
|
+
credentialToDelete = name;
|
|
364
|
+
elements.deleteCredentialName.textContent = name;
|
|
365
|
+
elements.deleteModal.classList.remove('hidden');
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Close modal
|
|
369
|
+
function closeModal() {
|
|
370
|
+
elements.credentialModal.classList.add('hidden');
|
|
371
|
+
resetForm();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Close delete modal
|
|
375
|
+
function closeDeleteModal() {
|
|
376
|
+
elements.deleteModal.classList.add('hidden');
|
|
377
|
+
credentialToDelete = null;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Open modal for encrypting credential
|
|
381
|
+
function openEncryptModal(name) {
|
|
382
|
+
credentialToEncrypt = name;
|
|
383
|
+
elements.encryptCredentialName.textContent = name;
|
|
384
|
+
// Populate SSH key dropdown
|
|
385
|
+
elements.encryptSshKey.innerHTML = '';
|
|
386
|
+
for (const key of sshKeys) {
|
|
387
|
+
const option = document.createElement('option');
|
|
388
|
+
option.value = key.index;
|
|
389
|
+
option.textContent = `${key.type} | ${key.fingerprint.substring(0, 16)}... | ${key.comment || 'No comment'}`;
|
|
390
|
+
elements.encryptSshKey.appendChild(option);
|
|
391
|
+
}
|
|
392
|
+
elements.encryptModal.classList.remove('hidden');
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Close encrypt modal
|
|
396
|
+
function closeEncryptModal() {
|
|
397
|
+
elements.encryptModal.classList.add('hidden');
|
|
398
|
+
credentialToEncrypt = null;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Open modal for shutting down the server
|
|
402
|
+
function openShutdownModal() {
|
|
403
|
+
// Reset modal to initial state
|
|
404
|
+
shutdownInProgress = false;
|
|
405
|
+
elements.shutdownMessage.textContent = 'This will stop the Credential Manager server program.';
|
|
406
|
+
elements.shutdownCountdown.classList.add('hidden');
|
|
407
|
+
elements.shutdownCountdown.textContent = '';
|
|
408
|
+
elements.shutdownActions.classList.remove('hidden');
|
|
409
|
+
elements.btnConfirmShutdown.disabled = false;
|
|
410
|
+
elements.btnConfirmShutdown.textContent = 'Stop Server';
|
|
411
|
+
elements.btnCancelShutdown.disabled = true;
|
|
412
|
+
elements.btnCloseShutdownModal.disabled = true;
|
|
413
|
+
elements.shutdownModal.classList.remove('hidden');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Shutdown modal intentionally stays visible once opened.
|
|
417
|
+
|
|
418
|
+
// Confirm shutdown
|
|
419
|
+
async function confirmShutdown() {
|
|
420
|
+
if (shutdownInProgress) return;
|
|
421
|
+
|
|
422
|
+
shutdownInProgress = true;
|
|
423
|
+
elements.btnConfirmShutdown.disabled = true;
|
|
424
|
+
elements.btnConfirmShutdown.textContent = 'Stopping...';
|
|
425
|
+
elements.btnCancelShutdown.disabled = true;
|
|
426
|
+
elements.btnCloseShutdownModal.disabled = true;
|
|
427
|
+
|
|
428
|
+
try {
|
|
429
|
+
await apiCall('/api/shutdown', { method: 'POST' });
|
|
430
|
+
|
|
431
|
+
// Update modal to show success and countdown
|
|
432
|
+
elements.shutdownMessage.textContent = 'Server shutdown acknowledged. Closing window...';
|
|
433
|
+
elements.shutdownActions.classList.add('hidden');
|
|
434
|
+
elements.shutdownCountdown.classList.remove('hidden');
|
|
435
|
+
|
|
436
|
+
// Start countdown
|
|
437
|
+
let countdown = 10;
|
|
438
|
+
elements.shutdownCountdown.textContent = `Window will close in ${countdown} seconds...`;
|
|
439
|
+
elements.shutdownCountdown.className = 'countdown-text';
|
|
440
|
+
|
|
441
|
+
const countdownInterval = setInterval(() => {
|
|
442
|
+
countdown--;
|
|
443
|
+
if (countdown > 0) {
|
|
444
|
+
elements.shutdownCountdown.textContent = `Window will close in ${countdown} seconds...`;
|
|
445
|
+
} else {
|
|
446
|
+
clearInterval(countdownInterval);
|
|
447
|
+
elements.shutdownCountdown.textContent = 'Closing window...';
|
|
448
|
+
// Attempt to close the window/tab
|
|
449
|
+
window.close();
|
|
450
|
+
// If window.close() doesn't work (common in modern browsers), show a message
|
|
451
|
+
setTimeout(() => {
|
|
452
|
+
elements.shutdownCountdown.textContent = 'Please close this browser tab/window manually.';
|
|
453
|
+
}, 500);
|
|
454
|
+
}
|
|
455
|
+
}, 1000);
|
|
456
|
+
} catch (error) {
|
|
457
|
+
shutdownInProgress = false;
|
|
458
|
+
elements.btnConfirmShutdown.disabled = false;
|
|
459
|
+
elements.btnConfirmShutdown.textContent = 'Stop Server';
|
|
460
|
+
showNotification('Failed to stop server: ' + error.message, 'error');
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Confirm credential encryption
|
|
465
|
+
async function confirmEncrypt() {
|
|
466
|
+
if (!credentialToEncrypt) return;
|
|
467
|
+
|
|
468
|
+
elements.btnConfirmEncrypt.disabled = true;
|
|
469
|
+
elements.btnConfirmEncrypt.textContent = 'Encrypting...';
|
|
470
|
+
|
|
471
|
+
try {
|
|
472
|
+
await apiCall(`/api/credentials/${encodeURIComponent(credentialToEncrypt)}/encrypt`, {
|
|
473
|
+
method: 'POST',
|
|
474
|
+
body: JSON.stringify({
|
|
475
|
+
ssh_key_index: parseInt(elements.encryptSshKey.value)
|
|
476
|
+
})
|
|
477
|
+
});
|
|
478
|
+
showNotification('API key encrypted successfully!', 'success');
|
|
479
|
+
closeEncryptModal();
|
|
480
|
+
await loadCredentials();
|
|
481
|
+
} catch (error) {
|
|
482
|
+
showNotification('Failed to encrypt API key: ' + error.message, 'error');
|
|
483
|
+
} finally {
|
|
484
|
+
elements.btnConfirmEncrypt.disabled = false;
|
|
485
|
+
elements.btnConfirmEncrypt.textContent = 'Encrypt';
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Reset form
|
|
490
|
+
function resetForm() {
|
|
491
|
+
elements.credentialForm.reset();
|
|
492
|
+
elements.formMode.value = 'create';
|
|
493
|
+
elements.formOriginalName.value = '';
|
|
494
|
+
elements.formPort.value = '8443';
|
|
495
|
+
elements.formOrgId.value = '1';
|
|
496
|
+
elements.formVerifySsl.checked = true;
|
|
497
|
+
elements.formEncrypt.checked = false;
|
|
498
|
+
elements.sshKeysSection.classList.add('hidden');
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Handle form submission
|
|
502
|
+
async function handleFormSubmit(e) {
|
|
503
|
+
e.preventDefault();
|
|
504
|
+
|
|
505
|
+
const mode = elements.formMode.value;
|
|
506
|
+
const data = {
|
|
507
|
+
name: elements.formName.value.trim(),
|
|
508
|
+
fqdn: elements.formFqdn.value.trim(),
|
|
509
|
+
port: parseInt(elements.formPort.value),
|
|
510
|
+
org_id: parseInt(elements.formOrgId.value),
|
|
511
|
+
api_user: elements.formApiUser.value.trim(),
|
|
512
|
+
verify_ssl: elements.formVerifySsl.checked
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
// Add API key if provided
|
|
516
|
+
const apiKey = elements.formApiKey.value;
|
|
517
|
+
if (apiKey) {
|
|
518
|
+
data.api_key = apiKey;
|
|
519
|
+
} else if (mode === 'create') {
|
|
520
|
+
showNotification('API key is required', 'error');
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Add encryption settings
|
|
525
|
+
if (elements.formEncrypt.checked && sshKeys.length > 0) {
|
|
526
|
+
data.encrypt = true;
|
|
527
|
+
data.ssh_key_index = parseInt(elements.formSshKey.value);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Add storage location for create
|
|
531
|
+
if (mode === 'create') {
|
|
532
|
+
data.use_current_workdir = elements.formUseWorkdir.checked;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
elements.btnSubmit.disabled = true;
|
|
536
|
+
elements.btnSubmit.textContent = mode === 'create' ? 'Creating...' : 'Updating...';
|
|
537
|
+
|
|
538
|
+
try {
|
|
539
|
+
if (mode === 'create') {
|
|
540
|
+
await apiCall('/api/credentials', {
|
|
541
|
+
method: 'POST',
|
|
542
|
+
body: JSON.stringify(data)
|
|
543
|
+
});
|
|
544
|
+
showNotification('Credential created successfully!', 'success');
|
|
545
|
+
} else {
|
|
546
|
+
const originalName = elements.formOriginalName.value;
|
|
547
|
+
await apiCall(`/api/credentials/${encodeURIComponent(originalName)}`, {
|
|
548
|
+
method: 'PUT',
|
|
549
|
+
body: JSON.stringify(data)
|
|
550
|
+
});
|
|
551
|
+
showNotification('Credential updated successfully!', 'success');
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
closeModal();
|
|
555
|
+
await loadCredentials();
|
|
556
|
+
} catch (error) {
|
|
557
|
+
showNotification('Failed to save credential: ' + error.message, 'error');
|
|
558
|
+
} finally {
|
|
559
|
+
elements.btnSubmit.disabled = false;
|
|
560
|
+
elements.btnSubmit.textContent = mode === 'create' ? 'Create' : 'Update';
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Confirm credential deletion
|
|
565
|
+
async function confirmDelete() {
|
|
566
|
+
if (!credentialToDelete) return;
|
|
567
|
+
|
|
568
|
+
elements.btnConfirmDelete.disabled = true;
|
|
569
|
+
elements.btnConfirmDelete.textContent = 'Deleting...';
|
|
570
|
+
|
|
571
|
+
try {
|
|
572
|
+
await apiCall(`/api/credentials/${encodeURIComponent(credentialToDelete)}`, {
|
|
573
|
+
method: 'DELETE'
|
|
574
|
+
});
|
|
575
|
+
showNotification('Credential deleted successfully!', 'success');
|
|
576
|
+
closeDeleteModal();
|
|
577
|
+
await loadCredentials();
|
|
578
|
+
} catch (error) {
|
|
579
|
+
showNotification('Failed to delete credential: ' + error.message, 'error');
|
|
580
|
+
} finally {
|
|
581
|
+
elements.btnConfirmDelete.disabled = false;
|
|
582
|
+
elements.btnConfirmDelete.textContent = 'Delete';
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Test credential connection
|
|
587
|
+
async function testCredential(name) {
|
|
588
|
+
elements.testResult.className = 'test-result loading';
|
|
589
|
+
elements.testResult.textContent = 'Testing connection...';
|
|
590
|
+
elements.testModal.classList.remove('hidden');
|
|
591
|
+
|
|
592
|
+
try {
|
|
593
|
+
const result = await apiCall(`/api/credentials/${encodeURIComponent(name)}/test`, {
|
|
594
|
+
method: 'POST'
|
|
595
|
+
});
|
|
596
|
+
elements.testResult.className = 'test-result success';
|
|
597
|
+
elements.testResult.textContent = `Connection to "${name}" successful!`;
|
|
598
|
+
} catch (error) {
|
|
599
|
+
elements.testResult.className = 'test-result error';
|
|
600
|
+
elements.testResult.textContent = `Connection failed: ${error.message}`;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Close test modal
|
|
605
|
+
function closeTestModal() {
|
|
606
|
+
elements.testModal.classList.add('hidden');
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Show notification
|
|
610
|
+
function showNotification(message, type = 'info') {
|
|
611
|
+
elements.notification.textContent = message;
|
|
612
|
+
elements.notification.className = `notification ${type}`;
|
|
613
|
+
elements.notification.classList.remove('hidden');
|
|
614
|
+
|
|
615
|
+
// Auto-hide after 5 seconds
|
|
616
|
+
setTimeout(() => {
|
|
617
|
+
elements.notification.classList.add('hidden');
|
|
618
|
+
}, 5000);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Utility: Escape HTML
|
|
622
|
+
function escapeHtml(text) {
|
|
623
|
+
const div = document.createElement('div');
|
|
624
|
+
div.textContent = text;
|
|
625
|
+
return div.innerHTML;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Utility: Truncate file path for display
|
|
629
|
+
function truncatePath(path) {
|
|
630
|
+
if (path.length > 30) {
|
|
631
|
+
return '...' + path.slice(-27);
|
|
632
|
+
}
|
|
633
|
+
return path;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Start the application
|
|
637
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
638
|
+
})();
|