illumio-pylo 0.3.10__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 +145 -103
- illumio_pylo/API/CredentialsManager.py +38 -0
- illumio_pylo/API/Explorer.py +44 -0
- illumio_pylo/API/JsonPayloadTypes.py +39 -0
- illumio_pylo/Helpers/exports.py +1 -1
- illumio_pylo/IPList.py +15 -8
- illumio_pylo/IPMap.py +9 -0
- illumio_pylo/__init__.py +1 -1
- illumio_pylo/cli/commands/__init__.py +1 -0
- illumio_pylo/cli/commands/credential_manager.py +379 -4
- illumio_pylo/cli/commands/label_delete_unused.py +79 -0
- 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 +145 -93
- illumio_pylo/utilities/cli.py +4 -1
- illumio_pylo/utilities/health_monitoring.py +5 -1
- {illumio_pylo-0.3.10.dist-info → illumio_pylo-0.3.12.dist-info}/METADATA +18 -11
- {illumio_pylo-0.3.10.dist-info → illumio_pylo-0.3.12.dist-info}/RECORD +22 -18
- {illumio_pylo-0.3.10.dist-info → illumio_pylo-0.3.12.dist-info}/WHEEL +1 -1
- {illumio_pylo-0.3.10.dist-info → illumio_pylo-0.3.12.dist-info/licenses}/LICENSE +0 -0
- {illumio_pylo-0.3.10.dist-info → illumio_pylo-0.3.12.dist-info}/top_level.txt +0 -0
|
@@ -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>
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
/* Base styles */
|
|
2
|
+
* {
|
|
3
|
+
box-sizing: border-box;
|
|
4
|
+
margin: 0;
|
|
5
|
+
padding: 0;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
body {
|
|
9
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
10
|
+
background-color: #f5f7fa;
|
|
11
|
+
color: #333;
|
|
12
|
+
line-height: 1.6;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.container {
|
|
16
|
+
max-width: 1200px;
|
|
17
|
+
margin: 0 auto;
|
|
18
|
+
padding: 20px;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Header */
|
|
22
|
+
header {
|
|
23
|
+
text-align: center;
|
|
24
|
+
margin-bottom: 30px;
|
|
25
|
+
padding: 20px;
|
|
26
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
27
|
+
border-radius: 10px;
|
|
28
|
+
color: white;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
header h1 {
|
|
32
|
+
font-size: 2rem;
|
|
33
|
+
margin-bottom: 5px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
header .subtitle {
|
|
37
|
+
opacity: 0.9;
|
|
38
|
+
font-size: 1rem;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Buttons */
|
|
42
|
+
.btn {
|
|
43
|
+
padding: 10px 20px;
|
|
44
|
+
border: none;
|
|
45
|
+
border-radius: 5px;
|
|
46
|
+
cursor: pointer;
|
|
47
|
+
font-size: 0.95rem;
|
|
48
|
+
font-weight: 500;
|
|
49
|
+
transition: all 0.2s ease;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.btn-primary {
|
|
53
|
+
background-color: #667eea;
|
|
54
|
+
color: white;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.btn-primary:hover {
|
|
58
|
+
background-color: #5a6fd6;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.btn-secondary {
|
|
62
|
+
background-color: #e2e8f0;
|
|
63
|
+
color: #4a5568;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.btn-secondary:hover {
|
|
67
|
+
background-color: #cbd5e0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.btn-danger {
|
|
71
|
+
background-color: #e53e3e;
|
|
72
|
+
color: white;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.btn-danger:hover {
|
|
76
|
+
background-color: #c53030;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.btn-success {
|
|
80
|
+
background-color: #38a169;
|
|
81
|
+
color: white;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.btn-success:hover {
|
|
85
|
+
background-color: #2f855a;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.btn-small {
|
|
89
|
+
padding: 5px 10px;
|
|
90
|
+
font-size: 0.85rem;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* Section styles */
|
|
94
|
+
section {
|
|
95
|
+
background: white;
|
|
96
|
+
border-radius: 10px;
|
|
97
|
+
padding: 25px;
|
|
98
|
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.section-header {
|
|
102
|
+
display: flex;
|
|
103
|
+
justify-content: space-between;
|
|
104
|
+
align-items: center;
|
|
105
|
+
margin-bottom: 20px;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.section-header h2 {
|
|
109
|
+
font-size: 1.3rem;
|
|
110
|
+
color: #2d3748;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* Table styles */
|
|
114
|
+
table {
|
|
115
|
+
width: 100%;
|
|
116
|
+
border-collapse: collapse;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
th, td {
|
|
120
|
+
padding: 12px 15px;
|
|
121
|
+
text-align: left;
|
|
122
|
+
border-bottom: 1px solid #e2e8f0;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
th {
|
|
126
|
+
background-color: #f7fafc;
|
|
127
|
+
font-weight: 600;
|
|
128
|
+
color: #4a5568;
|
|
129
|
+
font-size: 0.85rem;
|
|
130
|
+
text-transform: uppercase;
|
|
131
|
+
letter-spacing: 0.5px;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
tr:hover {
|
|
135
|
+
background-color: #f7fafc;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
td {
|
|
139
|
+
font-size: 0.95rem;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.actions-cell {
|
|
143
|
+
white-space: nowrap;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.actions-cell .btn {
|
|
147
|
+
margin-right: 5px;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.actions-cell .btn:last-child {
|
|
151
|
+
margin-right: 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* Status indicators */
|
|
155
|
+
.status-yes {
|
|
156
|
+
color: #38a169;
|
|
157
|
+
font-weight: 500;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.status-no {
|
|
161
|
+
color: #e53e3e;
|
|
162
|
+
font-weight: 500;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* Loading and empty states */
|
|
166
|
+
.loading, .empty-state {
|
|
167
|
+
text-align: center;
|
|
168
|
+
padding: 40px;
|
|
169
|
+
color: #718096;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.loading::before {
|
|
173
|
+
content: '⏳ ';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.empty-state {
|
|
177
|
+
background-color: #f7fafc;
|
|
178
|
+
border-radius: 8px;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* Notifications */
|
|
182
|
+
.notification {
|
|
183
|
+
padding: 15px 20px;
|
|
184
|
+
border-radius: 8px;
|
|
185
|
+
margin-bottom: 20px;
|
|
186
|
+
font-weight: 500;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.notification.success {
|
|
190
|
+
background-color: #c6f6d5;
|
|
191
|
+
color: #276749;
|
|
192
|
+
border: 1px solid #9ae6b4;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.notification.error {
|
|
196
|
+
background-color: #fed7d7;
|
|
197
|
+
color: #c53030;
|
|
198
|
+
border: 1px solid #feb2b2;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.notification.info {
|
|
202
|
+
background-color: #bee3f8;
|
|
203
|
+
color: #2b6cb0;
|
|
204
|
+
border: 1px solid #90cdf4;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/* Modal */
|
|
208
|
+
.modal {
|
|
209
|
+
position: fixed;
|
|
210
|
+
top: 0;
|
|
211
|
+
left: 0;
|
|
212
|
+
width: 100%;
|
|
213
|
+
height: 100%;
|
|
214
|
+
background-color: rgba(0, 0, 0, 0.5);
|
|
215
|
+
display: flex;
|
|
216
|
+
justify-content: center;
|
|
217
|
+
align-items: center;
|
|
218
|
+
z-index: 1000;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.modal-content {
|
|
222
|
+
background: white;
|
|
223
|
+
border-radius: 12px;
|
|
224
|
+
width: 90%;
|
|
225
|
+
max-width: 550px;
|
|
226
|
+
max-height: 90vh;
|
|
227
|
+
overflow-y: auto;
|
|
228
|
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.modal-small {
|
|
232
|
+
max-width: 400px;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.modal-header {
|
|
236
|
+
display: flex;
|
|
237
|
+
justify-content: space-between;
|
|
238
|
+
align-items: center;
|
|
239
|
+
padding: 20px 25px;
|
|
240
|
+
border-bottom: 1px solid #e2e8f0;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.modal-header h2 {
|
|
244
|
+
font-size: 1.2rem;
|
|
245
|
+
color: #2d3748;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.btn-close {
|
|
249
|
+
background: none;
|
|
250
|
+
border: none;
|
|
251
|
+
font-size: 1.5rem;
|
|
252
|
+
color: #a0aec0;
|
|
253
|
+
cursor: pointer;
|
|
254
|
+
line-height: 1;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.btn-close:hover {
|
|
258
|
+
color: #4a5568;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/* Form styles */
|
|
262
|
+
form {
|
|
263
|
+
padding: 25px;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.form-group {
|
|
267
|
+
margin-bottom: 20px;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.form-group label {
|
|
271
|
+
display: block;
|
|
272
|
+
margin-bottom: 6px;
|
|
273
|
+
font-weight: 500;
|
|
274
|
+
color: #4a5568;
|
|
275
|
+
font-size: 0.95rem;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.form-group input[type="text"],
|
|
279
|
+
.form-group input[type="number"],
|
|
280
|
+
.form-group input[type="password"],
|
|
281
|
+
.form-group select {
|
|
282
|
+
width: 100%;
|
|
283
|
+
padding: 10px 12px;
|
|
284
|
+
border: 1px solid #e2e8f0;
|
|
285
|
+
border-radius: 6px;
|
|
286
|
+
font-size: 1rem;
|
|
287
|
+
transition: border-color 0.2s;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.form-group input:focus,
|
|
291
|
+
.form-group select:focus {
|
|
292
|
+
outline: none;
|
|
293
|
+
border-color: #667eea;
|
|
294
|
+
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.form-group small {
|
|
298
|
+
display: block;
|
|
299
|
+
margin-top: 5px;
|
|
300
|
+
color: #718096;
|
|
301
|
+
font-size: 0.85rem;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.form-row {
|
|
305
|
+
display: flex;
|
|
306
|
+
gap: 15px;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.form-row .form-group {
|
|
310
|
+
flex: 1;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.checkbox-group label {
|
|
314
|
+
display: flex;
|
|
315
|
+
align-items: center;
|
|
316
|
+
cursor: pointer;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.checkbox-group input[type="checkbox"] {
|
|
320
|
+
margin-right: 10px;
|
|
321
|
+
width: 18px;
|
|
322
|
+
height: 18px;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.form-section {
|
|
326
|
+
background-color: #f7fafc;
|
|
327
|
+
padding: 15px;
|
|
328
|
+
border-radius: 8px;
|
|
329
|
+
margin-bottom: 20px;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.form-section h3 {
|
|
333
|
+
font-size: 1rem;
|
|
334
|
+
color: #4a5568;
|
|
335
|
+
margin-bottom: 15px;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.form-actions {
|
|
339
|
+
display: flex;
|
|
340
|
+
justify-content: flex-end;
|
|
341
|
+
gap: 10px;
|
|
342
|
+
padding-top: 15px;
|
|
343
|
+
border-top: 1px solid #e2e8f0;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/* Test result */
|
|
347
|
+
.test-result {
|
|
348
|
+
padding: 25px;
|
|
349
|
+
text-align: center;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.test-result.success {
|
|
353
|
+
color: #276749;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.test-result.success::before {
|
|
357
|
+
content: '✅ ';
|
|
358
|
+
font-size: 2rem;
|
|
359
|
+
display: block;
|
|
360
|
+
margin-bottom: 10px;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.test-result.error {
|
|
364
|
+
color: #c53030;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.test-result.error::before {
|
|
368
|
+
content: '❌ ';
|
|
369
|
+
font-size: 2rem;
|
|
370
|
+
display: block;
|
|
371
|
+
margin-bottom: 10px;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.test-result.loading::before {
|
|
375
|
+
content: '🔄 ';
|
|
376
|
+
font-size: 2rem;
|
|
377
|
+
display: block;
|
|
378
|
+
margin-bottom: 10px;
|
|
379
|
+
animation: spin 1s linear infinite;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
@keyframes spin {
|
|
383
|
+
from { transform: rotate(0deg); }
|
|
384
|
+
to { transform: rotate(360deg); }
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/* Utility classes */
|
|
388
|
+
.hidden {
|
|
389
|
+
display: none !important;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/* Responsive */
|
|
393
|
+
@media (max-width: 768px) {
|
|
394
|
+
.form-row {
|
|
395
|
+
flex-direction: column;
|
|
396
|
+
gap: 0;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
table {
|
|
400
|
+
font-size: 0.85rem;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
th, td {
|
|
404
|
+
padding: 8px 10px;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.section-header {
|
|
408
|
+
flex-direction: column;
|
|
409
|
+
gap: 15px;
|
|
410
|
+
align-items: flex-start;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/* Delete confirmation modal */
|
|
415
|
+
.delete-confirmation {
|
|
416
|
+
padding: 25px;
|
|
417
|
+
text-align: center;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.delete-confirmation p {
|
|
421
|
+
margin-bottom: 10px;
|
|
422
|
+
font-size: 1rem;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.delete-confirmation .warning-text {
|
|
426
|
+
color: #c53030;
|
|
427
|
+
font-size: 0.9rem;
|
|
428
|
+
font-style: italic;
|
|
429
|
+
}
|
|
430
|
+
|