measmonysuon commited on
Commit
2a09186
·
verified ·
1 Parent(s): 80d9601

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +268 -103
index.html CHANGED
@@ -303,8 +303,127 @@
303
  </div>
304
 
305
  <script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
  // Server management
307
- let servers = JSON.parse(localStorage.getItem('sshProxyServers')) || [];
308
  let currentServerId = null;
309
  let term;
310
  let socket;
@@ -384,9 +503,16 @@
384
  }
385
 
386
  // Initialize the app
387
- document.addEventListener('DOMContentLoaded', () => {
388
- renderServers();
389
- setupEventListeners();
 
 
 
 
 
 
 
390
  });
391
 
392
  // Tab switching
@@ -739,34 +865,35 @@
739
  }
740
 
741
  // Delete server
742
- function deleteServer() {
743
  if (!currentServerId) return;
744
 
745
  const server = servers.find(s => s.id === currentServerId);
746
  if (!server) return;
747
 
748
- // Show confirmation dialog
749
  if (confirm(`Are you sure you want to delete the server "${server.name}"?`)) {
750
- // Check if server is connected
751
- if (server.status === 'connected') {
752
- disconnectFromServer(server.id);
 
 
 
 
 
 
 
 
 
 
 
 
 
753
  }
754
-
755
- // Remove server from array
756
- servers = servers.filter(s => s.id !== currentServerId);
757
- saveServers();
758
- renderServers();
759
-
760
- // Close modal
761
- closeModal();
762
-
763
- // Show notification
764
- showNotification('Server configuration deleted', 'green');
765
  }
766
  }
767
 
768
  // Save server
769
- function saveServer() {
770
  const mode = this.dataset.mode;
771
  const serverId = this.dataset.id;
772
 
@@ -787,42 +914,47 @@
787
 
788
  // Validate required fields
789
  if (!server.name || !server.sshHost || !server.sshUsername || !server.proxyHost) {
790
- alert('Please fill in all required fields');
791
  return;
792
  }
793
-
794
- // Encrypt sensitive data before storage
795
- const sensitiveData = {
796
- sshPassword: server.sshPassword,
797
- proxyPassword: server.proxyPassword
798
- };
799
- server.encryptedCredentials = encryptCredentials(sensitiveData);
800
- delete server.sshPassword;
801
- delete server.proxyPassword;
802
-
803
- if (mode === 'edit') {
804
- // Update existing server
805
- const index = servers.findIndex(s => s.id === serverId);
806
- if (index !== -1) {
807
  server.id = serverId;
808
- server.status = servers[index].status; // Preserve status
809
- servers[index] = server;
810
  }
811
- } else {
812
- // Add new server
813
- server.id = Date.now().toString();
814
- servers.push(server);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
815
  }
816
-
817
- saveServers();
818
- renderServers();
819
- document.getElementById('servers-tab').click();
820
-
821
- // Show notification
822
- showNotification(`Server ${mode === 'edit' ? 'updated' : 'added'} successfully`, 'green');
823
-
824
- // Reset form
825
- resetForm();
826
  }
827
 
828
  // Reset form
@@ -883,69 +1015,70 @@
883
  }
884
 
885
  // Update server status
886
- function updateConnectionState(serverId, state, error = null) {
887
- const server = servers.find(s => s.id === serverId);
888
- if (server) {
889
- server.status = state;
890
- server.lastError = error;
891
- server.lastStateChange = new Date().toISOString();
892
- saveServers();
893
  renderServers();
894
  updateUIForConnectionState(state, error);
 
 
 
895
  }
896
  }
897
 
898
  // Export servers
899
- function exportServers() {
900
- const dataStr = JSON.stringify(servers, null, 2);
901
- const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
902
-
903
- const exportName = 'ssh-proxy-servers-' + new Date().toISOString().slice(0, 10) + '.json';
904
-
905
- const linkElement = document.createElement('a');
906
- linkElement.setAttribute('href', dataUri);
907
- linkElement.setAttribute('download', exportName);
908
- linkElement.click();
909
-
910
- // Show notification
911
- showNotification('Server configurations exported successfully', 'green');
 
 
 
 
 
912
  }
913
 
914
  // Import servers
915
- function importServers(event) {
916
  const file = event.target.files[0];
917
  if (!file) return;
918
 
919
  const reader = new FileReader();
920
- reader.onload = (e) => {
921
  try {
922
  const importedServers = JSON.parse(e.target.result);
923
  if (Array.isArray(importedServers) && importedServers.length > 0) {
924
- // Merge with existing servers, avoiding duplicates
925
- const existingIds = servers.map(s => s.id);
926
- const newServers = importedServers.filter(s => !existingIds.includes(s.id));
927
-
928
- if (newServers.length > 0) {
929
- servers = [...servers, ...newServers];
930
- saveServers();
931
- renderServers();
932
-
933
- // Show notification
934
- showNotification(`Imported ${newServers.length} server configuration(s)`, 'green');
935
- } else {
936
- // Show notification
937
- showNotification('No new server configurations to import', 'yellow');
938
  }
 
 
 
 
 
 
939
  } else {
940
- // Show notification
941
- showNotification('Invalid server configurations file', 'red');
942
  }
943
  } catch (error) {
944
- // Show notification
945
- showNotification('Error parsing server configurations file', 'red');
946
  }
947
 
948
- // Reset file input
949
  event.target.value = '';
950
  };
951
  reader.readAsText(file);
@@ -1051,10 +1184,35 @@
1051
  term.setOption('theme', themeConfig);
1052
  }
1053
 
1054
- // Add password encryption for stored credentials
1055
- function encryptCredentials(data) {
1056
- // Use a proper encryption library like CryptoJS
1057
- return btoa(JSON.stringify(data)); // Basic encoding for example
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1058
  }
1059
 
1060
  // Enhanced error handling
@@ -1096,12 +1254,19 @@
1096
  };
1097
  }
1098
 
1099
- const debouncedSearch = debounce((searchTerm) => {
1100
- const serverCards = document.querySelectorAll('.server-card');
1101
- serverCards.forEach(card => {
1102
- const name = card.querySelector('.server-name').textContent.toLowerCase();
1103
- card.classList.toggle('hidden', !name.includes(searchTerm.toLowerCase()));
1104
- });
 
 
 
 
 
 
 
1105
  }, 300);
1106
 
1107
  // Add accessibility
 
303
  </div>
304
 
305
  <script>
306
+ // IndexedDB Database Manager
307
+ class DatabaseManager {
308
+ constructor() {
309
+ this.dbName = 'SSHProxyManager';
310
+ this.dbVersion = 1;
311
+ this.storeName = 'servers';
312
+ this.db = null;
313
+ }
314
+
315
+ async init() {
316
+ return new Promise((resolve, reject) => {
317
+ const request = indexedDB.open(this.dbName, this.dbVersion);
318
+
319
+ request.onerror = (event) => {
320
+ console.error('Database error:', event.target.error);
321
+ reject(event.target.error);
322
+ };
323
+
324
+ request.onsuccess = (event) => {
325
+ this.db = event.target.result;
326
+ resolve();
327
+ };
328
+
329
+ request.onupgradeneeded = (event) => {
330
+ const db = event.target.result;
331
+ if (!db.objectStoreNames.contains(this.storeName)) {
332
+ const store = db.createObjectStore(this.storeName, { keyPath: 'id' });
333
+ // Create indexes for searching
334
+ store.createIndex('name', 'name', { unique: false });
335
+ store.createIndex('sshHost', 'sshHost', { unique: false });
336
+ store.createIndex('status', 'status', { unique: false });
337
+ }
338
+ };
339
+ });
340
+ }
341
+
342
+ async getAllServers() {
343
+ return new Promise((resolve, reject) => {
344
+ if (!this.db) {
345
+ reject(new Error('Database not initialized'));
346
+ return;
347
+ }
348
+
349
+ const transaction = this.db.transaction([this.storeName], 'readonly');
350
+ const store = transaction.objectStore(this.storeName);
351
+ const request = store.getAll();
352
+
353
+ request.onsuccess = () => resolve(request.result);
354
+ request.onerror = () => reject(request.error);
355
+ });
356
+ }
357
+
358
+ async saveServer(server) {
359
+ return new Promise((resolve, reject) => {
360
+ const transaction = this.db.transaction([this.storeName], 'readwrite');
361
+ const store = transaction.objectStore(this.storeName);
362
+ const request = store.put(server);
363
+
364
+ request.onsuccess = () => resolve(request.result);
365
+ request.onerror = () => reject(request.error);
366
+ });
367
+ }
368
+
369
+ async deleteServer(serverId) {
370
+ return new Promise((resolve, reject) => {
371
+ const transaction = this.db.transaction([this.storeName], 'readwrite');
372
+ const store = transaction.objectStore(this.storeName);
373
+ const request = store.delete(serverId);
374
+
375
+ request.onsuccess = () => resolve();
376
+ request.onerror = () => reject(request.error);
377
+ });
378
+ }
379
+
380
+ async searchServers(query) {
381
+ return new Promise((resolve, reject) => {
382
+ const transaction = this.db.transaction([this.storeName], 'readonly');
383
+ const store = transaction.objectStore(this.storeName);
384
+ const nameIndex = store.index('name');
385
+ const request = nameIndex.getAll();
386
+
387
+ request.onsuccess = () => {
388
+ const servers = request.result;
389
+ const results = servers.filter(server =>
390
+ server.name.toLowerCase().includes(query.toLowerCase()) ||
391
+ server.sshHost.toLowerCase().includes(query.toLowerCase())
392
+ );
393
+ resolve(results);
394
+ };
395
+ request.onerror = () => reject(request.error);
396
+ });
397
+ }
398
+
399
+ async updateServerStatus(serverId, status) {
400
+ return new Promise((resolve, reject) => {
401
+ const transaction = this.db.transaction([this.storeName], 'readwrite');
402
+ const store = transaction.objectStore(this.storeName);
403
+ const request = store.get(serverId);
404
+
405
+ request.onsuccess = () => {
406
+ const server = request.result;
407
+ if (server) {
408
+ server.status = status;
409
+ server.lastStateChange = new Date().toISOString();
410
+ const updateRequest = store.put(server);
411
+ updateRequest.onsuccess = () => resolve(server);
412
+ updateRequest.onerror = () => reject(updateRequest.error);
413
+ } else {
414
+ reject(new Error('Server not found'));
415
+ }
416
+ };
417
+ request.onerror = () => reject(request.error);
418
+ });
419
+ }
420
+ }
421
+
422
+ // Initialize database manager
423
+ const dbManager = new DatabaseManager();
424
+ let servers = []; // Keep this for compatibility with existing code
425
+
426
  // Server management
 
427
  let currentServerId = null;
428
  let term;
429
  let socket;
 
503
  }
504
 
505
  // Initialize the app
506
+ document.addEventListener('DOMContentLoaded', async () => {
507
+ try {
508
+ await dbManager.init();
509
+ servers = await dbManager.getAllServers();
510
+ renderServers();
511
+ setupEventListeners();
512
+ } catch (error) {
513
+ console.error('Failed to initialize database:', error);
514
+ showNotification('Failed to load server configurations', 'red');
515
+ }
516
  });
517
 
518
  // Tab switching
 
865
  }
866
 
867
  // Delete server
868
+ async function deleteServer() {
869
  if (!currentServerId) return;
870
 
871
  const server = servers.find(s => s.id === currentServerId);
872
  if (!server) return;
873
 
 
874
  if (confirm(`Are you sure you want to delete the server "${server.name}"?`)) {
875
+ try {
876
+ if (server.status === 'connected') {
877
+ disconnectFromServer(server.id);
878
+ }
879
+
880
+ // Delete from IndexedDB
881
+ await dbManager.deleteServer(currentServerId);
882
+
883
+ // Update local array
884
+ servers = servers.filter(s => s.id !== currentServerId);
885
+ renderServers();
886
+ closeModal();
887
+ showNotification('Server configuration deleted', 'green');
888
+ } catch (error) {
889
+ console.error('Failed to delete server:', error);
890
+ showNotification('Failed to delete server configuration', 'red');
891
  }
 
 
 
 
 
 
 
 
 
 
 
892
  }
893
  }
894
 
895
  // Save server
896
+ async function saveServer() {
897
  const mode = this.dataset.mode;
898
  const serverId = this.dataset.id;
899
 
 
914
 
915
  // Validate required fields
916
  if (!server.name || !server.sshHost || !server.sshUsername || !server.proxyHost) {
917
+ showNotification('Please fill in all required fields', 'red');
918
  return;
919
  }
920
+
921
+ try {
922
+ // Encrypt sensitive data
923
+ const sensitiveData = {
924
+ sshPassword: server.sshPassword,
925
+ proxyPassword: server.proxyPassword
926
+ };
927
+ server.encryptedCredentials = await encryptCredentials(sensitiveData);
928
+ delete server.sshPassword;
929
+ delete server.proxyPassword;
930
+
931
+ if (mode === 'edit') {
 
 
932
  server.id = serverId;
933
+ } else {
934
+ server.id = Date.now().toString();
935
  }
936
+
937
+ // Save to IndexedDB
938
+ await dbManager.saveServer(server);
939
+
940
+ // Update local array
941
+ if (mode === 'edit') {
942
+ const index = servers.findIndex(s => s.id === serverId);
943
+ if (index !== -1) {
944
+ servers[index] = server;
945
+ }
946
+ } else {
947
+ servers.push(server);
948
+ }
949
+
950
+ renderServers();
951
+ document.getElementById('servers-tab').click();
952
+ showNotification(`Server ${mode === 'edit' ? 'updated' : 'added'} successfully`, 'green');
953
+ resetForm();
954
+ } catch (error) {
955
+ console.error('Failed to save server:', error);
956
+ showNotification('Failed to save server configuration', 'red');
957
  }
 
 
 
 
 
 
 
 
 
 
958
  }
959
 
960
  // Reset form
 
1015
  }
1016
 
1017
  // Update server status
1018
+ async function updateConnectionState(serverId, state, error = null) {
1019
+ try {
1020
+ const updatedServer = await dbManager.updateServerStatus(serverId, state);
1021
+ const index = servers.findIndex(s => s.id === serverId);
1022
+ if (index !== -1) {
1023
+ servers[index] = updatedServer;
1024
+ }
1025
  renderServers();
1026
  updateUIForConnectionState(state, error);
1027
+ } catch (error) {
1028
+ console.error('Failed to update server status:', error);
1029
+ showNotification('Failed to update server status', 'red');
1030
  }
1031
  }
1032
 
1033
  // Export servers
1034
+ async function exportServers() {
1035
+ try {
1036
+ const allServers = await dbManager.getAllServers();
1037
+ const dataStr = JSON.stringify(allServers, null, 2);
1038
+ const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
1039
+
1040
+ const exportName = 'ssh-proxy-servers-' + new Date().toISOString().slice(0, 10) + '.json';
1041
+
1042
+ const linkElement = document.createElement('a');
1043
+ linkElement.setAttribute('href', dataUri);
1044
+ linkElement.setAttribute('download', exportName);
1045
+ linkElement.click();
1046
+
1047
+ showNotification('Server configurations exported successfully', 'green');
1048
+ } catch (error) {
1049
+ console.error('Failed to export servers:', error);
1050
+ showNotification('Failed to export server configurations', 'red');
1051
+ }
1052
  }
1053
 
1054
  // Import servers
1055
+ async function importServers(event) {
1056
  const file = event.target.files[0];
1057
  if (!file) return;
1058
 
1059
  const reader = new FileReader();
1060
+ reader.onload = async (e) => {
1061
  try {
1062
  const importedServers = JSON.parse(e.target.result);
1063
  if (Array.isArray(importedServers) && importedServers.length > 0) {
1064
+ // Save each server to IndexedDB
1065
+ for (const server of importedServers) {
1066
+ await dbManager.saveServer(server);
 
 
 
 
 
 
 
 
 
 
 
1067
  }
1068
+
1069
+ // Update local array
1070
+ servers = await dbManager.getAllServers();
1071
+ renderServers();
1072
+
1073
+ showNotification(`Imported ${importedServers.length} server configuration(s)`, 'green');
1074
  } else {
1075
+ showNotification('No valid server configurations found', 'yellow');
 
1076
  }
1077
  } catch (error) {
1078
+ console.error('Failed to import servers:', error);
1079
+ showNotification('Failed to import server configurations', 'red');
1080
  }
1081
 
 
1082
  event.target.value = '';
1083
  };
1084
  reader.readAsText(file);
 
1184
  term.setOption('theme', themeConfig);
1185
  }
1186
 
1187
+ // Enhanced encryption using WebCrypto API
1188
+ async function encryptCredentials(data) {
1189
+ const encoder = new TextEncoder();
1190
+ const key = await window.crypto.subtle.generateKey(
1191
+ {
1192
+ name: "AES-GCM",
1193
+ length: 256
1194
+ },
1195
+ true,
1196
+ ["encrypt", "decrypt"]
1197
+ );
1198
+
1199
+ const iv = window.crypto.getRandomValues(new Uint8Array(12));
1200
+ const encoded = encoder.encode(JSON.stringify(data));
1201
+
1202
+ const encrypted = await window.crypto.subtle.encrypt(
1203
+ {
1204
+ name: "AES-GCM",
1205
+ iv: iv
1206
+ },
1207
+ key,
1208
+ encoded
1209
+ );
1210
+
1211
+ return {
1212
+ encrypted: Array.from(new Uint8Array(encrypted)),
1213
+ iv: Array.from(iv),
1214
+ key: await exportKey(key)
1215
+ };
1216
  }
1217
 
1218
  // Enhanced error handling
 
1254
  };
1255
  }
1256
 
1257
+ const debouncedSearch = debounce(async (searchTerm) => {
1258
+ try {
1259
+ const results = await dbManager.searchServers(searchTerm);
1260
+ const serverCards = document.querySelectorAll('.server-card');
1261
+ serverCards.forEach(card => {
1262
+ const serverId = card.dataset.id;
1263
+ const found = results.some(server => server.id === serverId);
1264
+ card.classList.toggle('hidden', !found);
1265
+ });
1266
+ } catch (error) {
1267
+ console.error('Failed to search servers:', error);
1268
+ showNotification('Failed to search servers', 'red');
1269
+ }
1270
  }, 300);
1271
 
1272
  // Add accessibility