atlas-browser 0.2.0
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.
- package/.github/FUNDING.yml +1 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +26 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +16 -0
- package/CONTRIBUTING.md +63 -0
- package/LICENSE +21 -0
- package/PRIVACY.md +37 -0
- package/README.md +163 -0
- package/SECURITY.md +29 -0
- package/assets/logo.png +0 -0
- package/bin/cli.js +142 -0
- package/dist-electron/main/blocker.js +109 -0
- package/dist-electron/main/blocker.js.map +1 -0
- package/dist-electron/main/bookmark-manager.js +121 -0
- package/dist-electron/main/bookmark-manager.js.map +1 -0
- package/dist-electron/main/download-manager.js +118 -0
- package/dist-electron/main/download-manager.js.map +1 -0
- package/dist-electron/main/index.js +116 -0
- package/dist-electron/main/index.js.map +1 -0
- package/dist-electron/main/ipc-handlers.js +303 -0
- package/dist-electron/main/ipc-handlers.js.map +1 -0
- package/dist-electron/main/menu.js +229 -0
- package/dist-electron/main/menu.js.map +1 -0
- package/dist-electron/main/security-analyzer.js +71 -0
- package/dist-electron/main/security-analyzer.js.map +1 -0
- package/dist-electron/main/settings-manager.js +105 -0
- package/dist-electron/main/settings-manager.js.map +1 -0
- package/dist-electron/main/tab-manager.js +205 -0
- package/dist-electron/main/tab-manager.js.map +1 -0
- package/dist-electron/main/tor-manager.js +59 -0
- package/dist-electron/main/tor-manager.js.map +1 -0
- package/dist-electron/preload/preload.js +73 -0
- package/dist-electron/preload/preload.js.map +1 -0
- package/install.sh +120 -0
- package/package.json +67 -0
- package/src/main/blocker.ts +121 -0
- package/src/main/bookmark-manager.ts +99 -0
- package/src/main/download-manager.ts +103 -0
- package/src/main/index.ts +93 -0
- package/src/main/ipc-handlers.ts +283 -0
- package/src/main/menu.ts +192 -0
- package/src/main/security-analyzer.ts +97 -0
- package/src/main/settings-manager.ts +84 -0
- package/src/main/tab-manager.ts +249 -0
- package/src/main/tor-manager.ts +59 -0
- package/src/preload/preload.ts +85 -0
- package/src/renderer/bookmarks.html +84 -0
- package/src/renderer/browser-ui.js +427 -0
- package/src/renderer/downloads.html +94 -0
- package/src/renderer/history.html +111 -0
- package/src/renderer/index.html +152 -0
- package/src/renderer/internet-map.html +313 -0
- package/src/renderer/network-map.js +131 -0
- package/src/renderer/security-panel.js +13 -0
- package/src/renderer/settings.html +138 -0
- package/src/renderer/styles.css +688 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Downloads - Atlas Browser</title>
|
|
6
|
+
<style>
|
|
7
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
8
|
+
body { background: #0a0a0f; color: #e2e2e8; font-family: -apple-system, sans-serif; padding: 40px; }
|
|
9
|
+
h1 { font-size: 24px; margin-bottom: 8px; }
|
|
10
|
+
.subtitle { color: #8888a0; font-size: 14px; margin-bottom: 24px; }
|
|
11
|
+
.clear-btn { background: none; border: 1px solid #1e1e2e; color: #8888a0; padding: 6px 16px; border-radius: 8px; cursor: pointer; font-size: 12px; }
|
|
12
|
+
.clear-btn:hover { border-color: #ef4444; color: #ef4444; }
|
|
13
|
+
.download-list { margin-top: 16px; }
|
|
14
|
+
.download-item { display: flex; align-items: center; gap: 12px; padding: 12px 16px; border: 1px solid #1e1e2e; border-radius: 10px; margin-bottom: 8px; background: #12121a; }
|
|
15
|
+
.download-item:hover { border-color: #00d4ff33; }
|
|
16
|
+
.dl-icon { font-size: 24px; }
|
|
17
|
+
.dl-info { flex: 1; }
|
|
18
|
+
.dl-name { font-size: 14px; font-weight: 500; }
|
|
19
|
+
.dl-meta { font-size: 11px; color: #8888a0; margin-top: 2px; font-family: 'SF Mono', monospace; }
|
|
20
|
+
.dl-status { font-size: 11px; font-family: 'SF Mono', monospace; }
|
|
21
|
+
.dl-status.completed { color: #22c55e; }
|
|
22
|
+
.dl-status.progressing { color: #00d4ff; }
|
|
23
|
+
.dl-status.cancelled { color: #ef4444; }
|
|
24
|
+
.dl-actions button { background: none; border: none; color: #8888a0; cursor: pointer; font-size: 12px; padding: 4px 8px; border-radius: 4px; }
|
|
25
|
+
.dl-actions button:hover { background: #1a1a28; color: #e2e2e8; }
|
|
26
|
+
.empty { text-align: center; padding: 60px; color: #8888a0; }
|
|
27
|
+
.empty-icon { font-size: 48px; margin-bottom: 12px; }
|
|
28
|
+
.progress-bar { height: 3px; background: #1e1e2e; border-radius: 2px; margin-top: 6px; }
|
|
29
|
+
.progress-fill { height: 100%; background: #00d4ff; border-radius: 2px; transition: width 0.3s; }
|
|
30
|
+
</style>
|
|
31
|
+
</head>
|
|
32
|
+
<body>
|
|
33
|
+
<h1>⬇ Downloads</h1>
|
|
34
|
+
<div class="subtitle">
|
|
35
|
+
<button class="clear-btn" id="clear-btn">Clear History</button>
|
|
36
|
+
</div>
|
|
37
|
+
<div id="download-list" class="download-list">
|
|
38
|
+
<div class="empty">
|
|
39
|
+
<div class="empty-icon">📥</div>
|
|
40
|
+
<p>No downloads yet</p>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
<script>
|
|
44
|
+
const api = window.browserAPI;
|
|
45
|
+
const list = document.getElementById('download-list');
|
|
46
|
+
const clearBtn = document.getElementById('clear-btn');
|
|
47
|
+
|
|
48
|
+
function formatBytes(b) {
|
|
49
|
+
if (b === 0) return '0 B';
|
|
50
|
+
const k = 1024;
|
|
51
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
52
|
+
const i = Math.floor(Math.log(b) / Math.log(k));
|
|
53
|
+
return parseFloat((b / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function load() {
|
|
57
|
+
if (!api) return;
|
|
58
|
+
const downloads = await api.getDownloads();
|
|
59
|
+
if (!downloads || downloads.length === 0) {
|
|
60
|
+
list.innerHTML = '<div class="empty"><div class="empty-icon">📥</div><p>No downloads yet</p></div>';
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
list.innerHTML = downloads.map(d => {
|
|
64
|
+
const pct = d.totalBytes > 0 ? Math.round(d.receivedBytes / d.totalBytes * 100) : 0;
|
|
65
|
+
return '<div class="download-item">' +
|
|
66
|
+
'<span class="dl-icon">' + (d.state === 'completed' ? '✅' : d.state === 'progressing' ? '⏳' : '❌') + '</span>' +
|
|
67
|
+
'<div class="dl-info">' +
|
|
68
|
+
'<div class="dl-name">' + d.filename + '</div>' +
|
|
69
|
+
'<div class="dl-meta">' + formatBytes(d.receivedBytes) + ' / ' + formatBytes(d.totalBytes) + '</div>' +
|
|
70
|
+
(d.state === 'progressing' ? '<div class="progress-bar"><div class="progress-fill" style="width:' + pct + '%"></div></div>' : '') +
|
|
71
|
+
'</div>' +
|
|
72
|
+
'<span class="dl-status ' + d.state + '">' + d.state + '</span>' +
|
|
73
|
+
'<div class="dl-actions">' +
|
|
74
|
+
(d.state === 'completed' ? '<button onclick="api.showDownload(\'' + d.savePath.replace(/'/g, "\\'") + '\')">Show</button>' : '') +
|
|
75
|
+
'</div>' +
|
|
76
|
+
'</div>';
|
|
77
|
+
}).join('');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
clearBtn.addEventListener('click', async () => {
|
|
81
|
+
if (!api) return;
|
|
82
|
+
await api.clearDownloads();
|
|
83
|
+
load();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
load();
|
|
87
|
+
if (api) {
|
|
88
|
+
api.onDownloadStarted(() => load());
|
|
89
|
+
api.onDownloadProgress(() => load());
|
|
90
|
+
api.onDownloadDone(() => load());
|
|
91
|
+
}
|
|
92
|
+
</script>
|
|
93
|
+
</body>
|
|
94
|
+
</html>
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>History - Atlas Browser</title>
|
|
6
|
+
<style>
|
|
7
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
8
|
+
body { background: #0a0a0f; color: #e2e2e8; font-family: -apple-system, sans-serif; padding: 40px; max-width: 800px; margin: 0 auto; }
|
|
9
|
+
h1 { font-size: 24px; margin-bottom: 8px; }
|
|
10
|
+
.subtitle { color: #8888a0; font-size: 13px; margin-bottom: 24px; display: flex; align-items: center; gap: 12px; }
|
|
11
|
+
.clear-btn { background: none; border: 1px solid #1e1e2e; color: #8888a0; padding: 5px 14px; border-radius: 8px; cursor: pointer; font-size: 11px; }
|
|
12
|
+
.clear-btn:hover { border-color: #ef4444; color: #ef4444; }
|
|
13
|
+
.search { width: 100%; padding: 10px 16px; background: #12121a; border: 1px solid #1e1e2e; border-radius: 10px; color: #e2e2e8; font-size: 14px; outline: none; margin-bottom: 16px; }
|
|
14
|
+
.search:focus { border-color: #00d4ff; }
|
|
15
|
+
.day-header { font-size: 12px; color: #00d4ff; margin: 16px 0 8px; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
16
|
+
.history-item { display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-radius: 8px; cursor: pointer; transition: background 0.1s; }
|
|
17
|
+
.history-item:hover { background: #1a1a28; }
|
|
18
|
+
.h-time { font-size: 10px; color: #8888a0; font-family: 'SF Mono', monospace; min-width: 45px; }
|
|
19
|
+
.h-title { font-size: 13px; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
20
|
+
.h-url { font-size: 10px; color: #8888a0; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-family: 'SF Mono', monospace; }
|
|
21
|
+
.h-delete { opacity: 0; background: none; border: none; color: #8888a0; cursor: pointer; font-size: 14px; padding: 2px 6px; border-radius: 4px; }
|
|
22
|
+
.history-item:hover .h-delete { opacity: 1; }
|
|
23
|
+
.h-delete:hover { color: #ef4444; }
|
|
24
|
+
.empty { text-align: center; padding: 60px; color: #8888a0; }
|
|
25
|
+
.empty-icon { font-size: 48px; margin-bottom: 12px; }
|
|
26
|
+
</style>
|
|
27
|
+
</head>
|
|
28
|
+
<body>
|
|
29
|
+
<h1>🕓 History</h1>
|
|
30
|
+
<div class="subtitle">
|
|
31
|
+
<span>Recently visited pages</span>
|
|
32
|
+
<button class="clear-btn" id="clear-btn">Clear All History</button>
|
|
33
|
+
</div>
|
|
34
|
+
<input class="search" id="search" placeholder="Search history..." autofocus>
|
|
35
|
+
<div id="history-list"></div>
|
|
36
|
+
<script>
|
|
37
|
+
const api = window.browserAPI;
|
|
38
|
+
const list = document.getElementById('history-list');
|
|
39
|
+
const search = document.getElementById('search');
|
|
40
|
+
const clearBtn = document.getElementById('clear-btn');
|
|
41
|
+
let allHistory = [];
|
|
42
|
+
|
|
43
|
+
async function load() {
|
|
44
|
+
if (!api) {
|
|
45
|
+
list.innerHTML = '<div class="empty"><div class="empty-icon">🕓</div><p>History not available</p></div>';
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
allHistory = await api.getHistory();
|
|
49
|
+
render(allHistory);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function render(items) {
|
|
53
|
+
if (!items || items.length === 0) {
|
|
54
|
+
list.innerHTML = '<div class="empty"><div class="empty-icon">🕓</div><p>No history yet</p></div>';
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Group by day
|
|
59
|
+
const groups = {};
|
|
60
|
+
items.forEach(item => {
|
|
61
|
+
const day = new Date(item.timestamp).toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' });
|
|
62
|
+
if (!groups[day]) groups[day] = [];
|
|
63
|
+
groups[day].push(item);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
let html = '';
|
|
67
|
+
for (const [day, dayItems] of Object.entries(groups)) {
|
|
68
|
+
html += '<div class="day-header">' + day + '</div>';
|
|
69
|
+
dayItems.forEach(item => {
|
|
70
|
+
const time = new Date(item.timestamp).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
|
|
71
|
+
html += '<div class="history-item" data-url="' + item.url + '">' +
|
|
72
|
+
'<span class="h-time">' + time + '</span>' +
|
|
73
|
+
'<span class="h-title">' + (item.title || item.url) + '</span>' +
|
|
74
|
+
'<span class="h-url">' + item.url + '</span>' +
|
|
75
|
+
'<button class="h-delete" data-id="' + item.id + '">×</button>' +
|
|
76
|
+
'</div>';
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
list.innerHTML = html;
|
|
80
|
+
|
|
81
|
+
list.querySelectorAll('.history-item').forEach(el => {
|
|
82
|
+
el.addEventListener('click', (e) => {
|
|
83
|
+
if (e.target.classList.contains('h-delete')) return;
|
|
84
|
+
window.location.href = el.dataset.url;
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
list.querySelectorAll('.h-delete').forEach(btn => {
|
|
89
|
+
btn.addEventListener('click', async (e) => {
|
|
90
|
+
e.stopPropagation();
|
|
91
|
+
await api.deleteHistoryItem(btn.dataset.id);
|
|
92
|
+
load();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
search.addEventListener('input', () => {
|
|
98
|
+
const q = search.value.toLowerCase();
|
|
99
|
+
render(allHistory.filter(i => (i.title || '').toLowerCase().includes(q) || i.url.toLowerCase().includes(q)));
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
clearBtn.addEventListener('click', async () => {
|
|
103
|
+
if (!api || !confirm('Clear all browsing history?')) return;
|
|
104
|
+
await api.clearHistory();
|
|
105
|
+
load();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
load();
|
|
109
|
+
</script>
|
|
110
|
+
</body>
|
|
111
|
+
</html>
|
|
@@ -0,0 +1,152 @@
|
|
|
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>Atlas Browser</title>
|
|
7
|
+
<link rel="stylesheet" href="styles.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<!-- Loading Progress Bar -->
|
|
11
|
+
<div id="progress-bar" class="progress-bar-container hidden">
|
|
12
|
+
<div id="progress-fill" class="progress-fill"></div>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<!-- Context Menu -->
|
|
16
|
+
<div id="context-menu" class="context-menu hidden">
|
|
17
|
+
<div class="ctx-item" data-action="back">← Back</div>
|
|
18
|
+
<div class="ctx-item" data-action="forward">→ Forward</div>
|
|
19
|
+
<div class="ctx-item" data-action="reload">↻ Reload</div>
|
|
20
|
+
<div class="ctx-separator"></div>
|
|
21
|
+
<div class="ctx-item" data-action="newtab">Open in New Tab</div>
|
|
22
|
+
<div class="ctx-item" data-action="copyurl">Copy Page URL</div>
|
|
23
|
+
<div class="ctx-separator"></div>
|
|
24
|
+
<div class="ctx-item" data-action="devtools">Inspect Element</div>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<!-- Tab Bar -->
|
|
28
|
+
<div id="tab-bar" class="tab-bar">
|
|
29
|
+
<div class="tab-bar-drag-region"></div>
|
|
30
|
+
<div id="tabs-container" class="tabs-container"></div>
|
|
31
|
+
<button id="new-tab-btn" class="new-tab-btn" title="New Tab (Cmd+T)">+</button>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<!-- Navigation Bar -->
|
|
35
|
+
<div id="nav-bar" class="nav-bar">
|
|
36
|
+
<div class="nav-controls">
|
|
37
|
+
<button id="back-btn" class="nav-btn" title="Back" disabled>←</button>
|
|
38
|
+
<button id="forward-btn" class="nav-btn" title="Forward" disabled>→</button>
|
|
39
|
+
<button id="reload-btn" class="nav-btn" title="Reload">↻</button>
|
|
40
|
+
<button id="home-btn" class="nav-btn" title="Home">⌂</button>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div class="address-bar-wrapper">
|
|
44
|
+
<span id="ssl-indicator" class="ssl-indicator ssl-secure">🔒</span>
|
|
45
|
+
<input id="address-bar" class="address-bar" type="text" placeholder="Search with Search Angel or enter URL..." spellcheck="false">
|
|
46
|
+
<button id="bookmark-star" class="bookmark-star" title="Bookmark this page (Cmd+D)">☆</button>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div class="toolbar-actions">
|
|
50
|
+
<!-- Zoom controls -->
|
|
51
|
+
<div class="zoom-controls">
|
|
52
|
+
<button id="zoom-out" class="zoom-btn" title="Zoom Out">-</button>
|
|
53
|
+
<span id="zoom-level" class="zoom-level">100%</span>
|
|
54
|
+
<button id="zoom-in" class="zoom-btn" title="Zoom In">+</button>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div class="toolbar-separator"></div>
|
|
58
|
+
|
|
59
|
+
<!-- Shield (blocker) -->
|
|
60
|
+
<button id="shield-btn" class="toolbar-btn shield-active" title="Tracker Blocker">
|
|
61
|
+
🛡
|
|
62
|
+
<span id="blocked-count" class="blocked-badge">0</span>
|
|
63
|
+
</button>
|
|
64
|
+
|
|
65
|
+
<!-- Tor Toggle -->
|
|
66
|
+
<button id="tor-btn" class="toolbar-btn" title="Tor Mode">🧅</button>
|
|
67
|
+
|
|
68
|
+
<!-- Phantom Mode -->
|
|
69
|
+
<button id="phantom-btn" class="toolbar-btn" title="Phantom Mode - Isolated Docker Session">👻</button>
|
|
70
|
+
|
|
71
|
+
<!-- Downloads -->
|
|
72
|
+
<button id="downloads-btn" class="toolbar-btn" title="Downloads (Cmd+J)">⬇</button>
|
|
73
|
+
|
|
74
|
+
<!-- Internet Map -->
|
|
75
|
+
<button id="map-btn" class="toolbar-btn" title="Internet Map">🗺</button>
|
|
76
|
+
|
|
77
|
+
<!-- Settings -->
|
|
78
|
+
<button id="settings-btn" class="toolbar-btn" title="Settings">⚙</button>
|
|
79
|
+
|
|
80
|
+
<!-- Security Panel Toggle -->
|
|
81
|
+
<button id="security-btn" class="toolbar-btn" title="Security Info">🔍</button>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<!-- Phantom Mode Status Bar (hidden by default) -->
|
|
86
|
+
<div id="phantom-bar" class="phantom-bar hidden">
|
|
87
|
+
<span class="phantom-pulse"></span>
|
|
88
|
+
<span class="phantom-label">PHANTOM MODE</span>
|
|
89
|
+
<span class="phantom-info" id="phantom-info">Container starting...</span>
|
|
90
|
+
<span class="phantom-timer" id="phantom-timer">30:00</span>
|
|
91
|
+
<button id="phantom-destroy" class="phantom-destroy-btn">Destroy Container</button>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<!-- Bookmarks Bar -->
|
|
95
|
+
<div id="bookmarks-bar" class="bookmarks-bar">
|
|
96
|
+
<div id="bookmarks-container" class="bookmarks-container">
|
|
97
|
+
<span class="bookmarks-empty">Bookmark pages with ☆ to see them here</span>
|
|
98
|
+
</div>
|
|
99
|
+
<button id="all-bookmarks-btn" class="all-bookmarks-btn" title="All Bookmarks">📑</button>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<!-- Security Sidebar -->
|
|
103
|
+
<div id="security-sidebar" class="security-sidebar hidden">
|
|
104
|
+
<div class="sidebar-header">
|
|
105
|
+
<h3>Security & Privacy</h3>
|
|
106
|
+
<button id="close-sidebar" class="close-btn">×</button>
|
|
107
|
+
</div>
|
|
108
|
+
<div id="security-content" class="sidebar-content">
|
|
109
|
+
<div class="security-section">
|
|
110
|
+
<div class="privacy-score-container">
|
|
111
|
+
<div id="privacy-score" class="privacy-score">100</div>
|
|
112
|
+
<div class="privacy-score-label">Privacy Score</div>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="security-section">
|
|
116
|
+
<h4>Connection</h4>
|
|
117
|
+
<div class="info-rows">
|
|
118
|
+
<div class="info-row"><span class="label">Protocol</span><span id="proto-val" class="value">-</span></div>
|
|
119
|
+
<div class="info-row"><span class="label">Certificate</span><span id="cert-val" class="value">-</span></div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
<div class="security-section">
|
|
123
|
+
<h4>Trackers Blocked</h4>
|
|
124
|
+
<div class="info-rows">
|
|
125
|
+
<div class="info-row"><span class="label">This page</span><span id="trackers-page" class="value badge-green">0</span></div>
|
|
126
|
+
<div class="info-row"><span class="label">Total session</span><span id="trackers-total" class="value">0</span></div>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
<div class="security-section">
|
|
130
|
+
<h4>Cookies</h4>
|
|
131
|
+
<div class="info-rows">
|
|
132
|
+
<div class="info-row"><span class="label">Count</span><span id="cookies-count" class="value">0</span></div>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
<div class="security-section">
|
|
136
|
+
<h4>Tor Circuit</h4>
|
|
137
|
+
<div id="tor-circuit" class="tor-circuit">
|
|
138
|
+
<span class="tor-disabled">Tor is disabled</span>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="security-section">
|
|
142
|
+
<h4>Network Map</h4>
|
|
143
|
+
<canvas id="network-canvas" width="280" height="200"></canvas>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<script src="browser-ui.js"></script>
|
|
149
|
+
<script src="security-panel.js"></script>
|
|
150
|
+
<script src="network-map.js"></script>
|
|
151
|
+
</body>
|
|
152
|
+
</html>
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Internet Map - Atlas Browser</title>
|
|
6
|
+
<style>
|
|
7
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
8
|
+
body { background: #050508; color: #e2e2e8; font-family: -apple-system, sans-serif; overflow: hidden; height: 100vh; }
|
|
9
|
+
#canvas { width: 100%; height: 100%; display: block; }
|
|
10
|
+
.controls {
|
|
11
|
+
position: fixed; top: 16px; left: 16px; z-index: 10;
|
|
12
|
+
display: flex; gap: 8px; align-items: center;
|
|
13
|
+
}
|
|
14
|
+
.controls input {
|
|
15
|
+
background: rgba(18,18,26,0.9); border: 1px solid #1e1e2e; color: #e2e2e8;
|
|
16
|
+
padding: 8px 16px; border-radius: 10px; font-size: 13px; width: 320px;
|
|
17
|
+
outline: none; font-family: 'SF Mono', monospace; backdrop-filter: blur(8px);
|
|
18
|
+
}
|
|
19
|
+
.controls input:focus { border-color: #00d4ff; box-shadow: 0 0 10px rgba(0,212,255,0.15); }
|
|
20
|
+
.controls button {
|
|
21
|
+
background: rgba(18,18,26,0.9); border: 1px solid #1e1e2e; color: #00d4ff;
|
|
22
|
+
padding: 8px 16px; border-radius: 10px; cursor: pointer; font-size: 12px;
|
|
23
|
+
backdrop-filter: blur(8px);
|
|
24
|
+
}
|
|
25
|
+
.controls button:hover { background: rgba(0,212,255,0.1); border-color: #00d4ff; }
|
|
26
|
+
.info-panel {
|
|
27
|
+
position: fixed; bottom: 16px; right: 16px; z-index: 10;
|
|
28
|
+
background: rgba(18,18,26,0.95); border: 1px solid #1e1e2e;
|
|
29
|
+
border-radius: 12px; padding: 16px; min-width: 240px;
|
|
30
|
+
backdrop-filter: blur(8px); font-size: 12px;
|
|
31
|
+
}
|
|
32
|
+
.info-panel h3 { font-size: 13px; color: #00d4ff; margin-bottom: 8px; }
|
|
33
|
+
.info-row { display: flex; justify-content: space-between; padding: 3px 0; }
|
|
34
|
+
.info-row .lbl { color: #8888a0; }
|
|
35
|
+
.info-row .val { color: #e2e2e8; font-family: 'SF Mono', monospace; }
|
|
36
|
+
.legend {
|
|
37
|
+
position: fixed; bottom: 16px; left: 16px; z-index: 10;
|
|
38
|
+
background: rgba(18,18,26,0.9); border: 1px solid #1e1e2e;
|
|
39
|
+
border-radius: 10px; padding: 12px; font-size: 10px;
|
|
40
|
+
backdrop-filter: blur(8px);
|
|
41
|
+
}
|
|
42
|
+
.legend-item { display: flex; align-items: center; gap: 6px; margin: 3px 0; }
|
|
43
|
+
.legend-dot { width: 8px; height: 8px; border-radius: 50%; }
|
|
44
|
+
</style>
|
|
45
|
+
</head>
|
|
46
|
+
<body>
|
|
47
|
+
<div class="controls">
|
|
48
|
+
<input id="domain-input" type="text" placeholder="Enter domain to locate (e.g. google.com)" spellcheck="false">
|
|
49
|
+
<button id="locate-btn">Locate</button>
|
|
50
|
+
<button id="reset-btn">Reset View</button>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<canvas id="canvas"></canvas>
|
|
54
|
+
|
|
55
|
+
<div class="info-panel" id="info-panel">
|
|
56
|
+
<h3>🗺 Internet Map</h3>
|
|
57
|
+
<div class="info-row"><span class="lbl">Nodes</span><span class="val" id="node-count">0</span></div>
|
|
58
|
+
<div class="info-row"><span class="lbl">Connections</span><span class="val" id="edge-count">0</span></div>
|
|
59
|
+
<div class="info-row"><span class="lbl">Selected</span><span class="val" id="selected-node">-</span></div>
|
|
60
|
+
<div class="info-row"><span class="lbl">IP</span><span class="val" id="selected-ip">-</span></div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div class="legend">
|
|
64
|
+
<div class="legend-item"><div class="legend-dot" style="background:#00d4ff"></div> Backbone / ISP</div>
|
|
65
|
+
<div class="legend-item"><div class="legend-dot" style="background:#22c55e"></div> CDN / Cloud</div>
|
|
66
|
+
<div class="legend-item"><div class="legend-dot" style="background:#a855f7"></div> User-added domain</div>
|
|
67
|
+
<div class="legend-item"><div class="legend-dot" style="background:#eab308"></div> DNS Server</div>
|
|
68
|
+
<div class="legend-item"><div class="legend-dot" style="background:#ef4444"></div> Tor Node</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<script>
|
|
72
|
+
const api = window.browserAPI;
|
|
73
|
+
const canvas = document.getElementById('canvas');
|
|
74
|
+
const ctx = canvas.getContext('2d');
|
|
75
|
+
|
|
76
|
+
// ── Resize ──────────────────────────────────────────────
|
|
77
|
+
function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }
|
|
78
|
+
resize();
|
|
79
|
+
window.addEventListener('resize', resize);
|
|
80
|
+
|
|
81
|
+
// ── Colors ──────────────────────────────────────────────
|
|
82
|
+
const C = { bg: '#050508', accent: '#00d4ff', green: '#22c55e', purple: '#a855f7', amber: '#eab308', red: '#ef4444', muted: '#8888a0', text: '#e2e2e8' };
|
|
83
|
+
|
|
84
|
+
// ── Node types ──────────────────────────────────────────
|
|
85
|
+
const BACKBONE = [
|
|
86
|
+
{ id: 'ixp-ams', label: 'AMS-IX', type: 'backbone', x: 0, y: 0, r: 14 },
|
|
87
|
+
{ id: 'ixp-lon', label: 'LINX London', type: 'backbone', x: 0, y: 0, r: 12 },
|
|
88
|
+
{ id: 'ixp-ny', label: 'NY-IX', type: 'backbone', x: 0, y: 0, r: 14 },
|
|
89
|
+
{ id: 'ixp-sf', label: 'SFO-IX', type: 'backbone', x: 0, y: 0, r: 12 },
|
|
90
|
+
{ id: 'ixp-tok', label: 'JPIX Tokyo', type: 'backbone', x: 0, y: 0, r: 10 },
|
|
91
|
+
{ id: 'ixp-sg', label: 'SGIX', type: 'backbone', x: 0, y: 0, r: 10 },
|
|
92
|
+
{ id: 'ixp-fra', label: 'DE-CIX', type: 'backbone', x: 0, y: 0, r: 14 },
|
|
93
|
+
{ id: 'cdn-cf', label: 'Cloudflare', type: 'cdn', x: 0, y: 0, r: 10 },
|
|
94
|
+
{ id: 'cdn-aws', label: 'AWS', type: 'cdn', x: 0, y: 0, r: 10 },
|
|
95
|
+
{ id: 'cdn-gcp', label: 'Google Cloud', type: 'cdn', x: 0, y: 0, r: 10 },
|
|
96
|
+
{ id: 'cdn-azure', label: 'Azure', type: 'cdn', x: 0, y: 0, r: 8 },
|
|
97
|
+
{ id: 'cdn-akamai', label: 'Akamai', type: 'cdn', x: 0, y: 0, r: 8 },
|
|
98
|
+
{ id: 'dns-root', label: 'Root DNS', type: 'dns', x: 0, y: 0, r: 8 },
|
|
99
|
+
{ id: 'dns-cf', label: '1.1.1.1', type: 'dns', x: 0, y: 0, r: 6 },
|
|
100
|
+
{ id: 'dns-google', label: '8.8.8.8', type: 'dns', x: 0, y: 0, r: 6 },
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
const BACKBONE_EDGES = [
|
|
104
|
+
['ixp-ams', 'ixp-lon'], ['ixp-ams', 'ixp-fra'], ['ixp-lon', 'ixp-ny'],
|
|
105
|
+
['ixp-ny', 'ixp-sf'], ['ixp-fra', 'ixp-tok'], ['ixp-tok', 'ixp-sg'],
|
|
106
|
+
['ixp-sf', 'ixp-tok'], ['ixp-ny', 'ixp-ams'], ['ixp-lon', 'ixp-fra'],
|
|
107
|
+
['cdn-cf', 'ixp-ams'], ['cdn-cf', 'ixp-ny'], ['cdn-cf', 'ixp-sf'],
|
|
108
|
+
['cdn-aws', 'ixp-ny'], ['cdn-aws', 'ixp-fra'], ['cdn-aws', 'ixp-tok'],
|
|
109
|
+
['cdn-gcp', 'ixp-sf'], ['cdn-gcp', 'ixp-tok'], ['cdn-gcp', 'ixp-ams'],
|
|
110
|
+
['cdn-azure', 'ixp-lon'], ['cdn-azure', 'ixp-ny'],
|
|
111
|
+
['cdn-akamai', 'ixp-ams'], ['cdn-akamai', 'ixp-sf'],
|
|
112
|
+
['dns-root', 'ixp-ams'], ['dns-root', 'ixp-ny'],
|
|
113
|
+
['dns-cf', 'cdn-cf'], ['dns-google', 'cdn-gcp'],
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
let nodes = [...BACKBONE];
|
|
117
|
+
let edges = [...BACKBONE_EDGES];
|
|
118
|
+
let frame = 0;
|
|
119
|
+
let pan = { x: 0, y: 0 };
|
|
120
|
+
let zoom = 1;
|
|
121
|
+
let dragging = false;
|
|
122
|
+
let lastMouse = { x: 0, y: 0 };
|
|
123
|
+
let selectedNode = null;
|
|
124
|
+
|
|
125
|
+
// ── Layout nodes in circle ──────────────────────────────
|
|
126
|
+
function layout() {
|
|
127
|
+
const cx = canvas.width / 2;
|
|
128
|
+
const cy = canvas.height / 2;
|
|
129
|
+
const r = Math.min(cx, cy) * 0.6;
|
|
130
|
+
nodes.forEach((n, i) => {
|
|
131
|
+
if (n.type === 'user') return; // User nodes positioned separately
|
|
132
|
+
const angle = (i / BACKBONE.length) * Math.PI * 2 - Math.PI / 2;
|
|
133
|
+
n.x = cx + Math.cos(angle) * r * (0.6 + Math.random() * 0.4);
|
|
134
|
+
n.y = cy + Math.sin(angle) * r * (0.6 + Math.random() * 0.4);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
layout();
|
|
138
|
+
|
|
139
|
+
// ── Get node color ──────────────────────────────────────
|
|
140
|
+
function nodeColor(type) {
|
|
141
|
+
return { backbone: C.accent, cdn: C.green, dns: C.amber, user: C.purple, tor: C.red }[type] || C.muted;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ── Draw ────────────────────────────────────────────────
|
|
145
|
+
function draw() {
|
|
146
|
+
ctx.fillStyle = C.bg;
|
|
147
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
148
|
+
|
|
149
|
+
ctx.save();
|
|
150
|
+
ctx.translate(pan.x, pan.y);
|
|
151
|
+
ctx.scale(zoom, zoom);
|
|
152
|
+
|
|
153
|
+
// Edges
|
|
154
|
+
edges.forEach(([a, b]) => {
|
|
155
|
+
const na = nodes.find(n => n.id === a);
|
|
156
|
+
const nb = nodes.find(n => n.id === b);
|
|
157
|
+
if (!na || !nb) return;
|
|
158
|
+
ctx.strokeStyle = C.muted + '25';
|
|
159
|
+
ctx.lineWidth = 1;
|
|
160
|
+
ctx.beginPath();
|
|
161
|
+
ctx.moveTo(na.x, na.y);
|
|
162
|
+
ctx.lineTo(nb.x, nb.y);
|
|
163
|
+
ctx.stroke();
|
|
164
|
+
|
|
165
|
+
// Data packet
|
|
166
|
+
const t = ((frame * 0.5 + nodes.indexOf(na) * 10) % 120) / 120;
|
|
167
|
+
const px = na.x + (nb.x - na.x) * t;
|
|
168
|
+
const py = na.y + (nb.y - na.y) * t;
|
|
169
|
+
ctx.fillStyle = nodeColor(na.type) + '60';
|
|
170
|
+
ctx.beginPath();
|
|
171
|
+
ctx.arc(px, py, 1.5, 0, Math.PI * 2);
|
|
172
|
+
ctx.fill();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Nodes
|
|
176
|
+
nodes.forEach(n => {
|
|
177
|
+
const col = nodeColor(n.type);
|
|
178
|
+
// Glow
|
|
179
|
+
const g = ctx.createRadialGradient(n.x, n.y, 0, n.x, n.y, n.r * 3);
|
|
180
|
+
g.addColorStop(0, col + '20');
|
|
181
|
+
g.addColorStop(1, 'transparent');
|
|
182
|
+
ctx.fillStyle = g;
|
|
183
|
+
ctx.beginPath();
|
|
184
|
+
ctx.arc(n.x, n.y, n.r * 3, 0, Math.PI * 2);
|
|
185
|
+
ctx.fill();
|
|
186
|
+
|
|
187
|
+
// Circle
|
|
188
|
+
ctx.fillStyle = n === selectedNode ? '#fff' : col;
|
|
189
|
+
ctx.beginPath();
|
|
190
|
+
ctx.arc(n.x, n.y, n.r, 0, Math.PI * 2);
|
|
191
|
+
ctx.fill();
|
|
192
|
+
|
|
193
|
+
// Ring
|
|
194
|
+
if (n === selectedNode) {
|
|
195
|
+
ctx.strokeStyle = col;
|
|
196
|
+
ctx.lineWidth = 2;
|
|
197
|
+
ctx.beginPath();
|
|
198
|
+
ctx.arc(n.x, n.y, n.r + 4, 0, Math.PI * 2);
|
|
199
|
+
ctx.stroke();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Label
|
|
203
|
+
ctx.fillStyle = C.text;
|
|
204
|
+
ctx.font = `${Math.max(8, 10 / zoom)}px -apple-system, sans-serif`;
|
|
205
|
+
ctx.textAlign = 'center';
|
|
206
|
+
ctx.fillText(n.label, n.x, n.y + n.r + 14);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
ctx.restore();
|
|
210
|
+
|
|
211
|
+
// Update info
|
|
212
|
+
document.getElementById('node-count').textContent = nodes.length;
|
|
213
|
+
document.getElementById('edge-count').textContent = edges.length;
|
|
214
|
+
|
|
215
|
+
frame++;
|
|
216
|
+
requestAnimationFrame(draw);
|
|
217
|
+
}
|
|
218
|
+
draw();
|
|
219
|
+
|
|
220
|
+
// ── Mouse interaction ───────────────────────────────────
|
|
221
|
+
canvas.addEventListener('mousedown', (e) => {
|
|
222
|
+
dragging = true;
|
|
223
|
+
lastMouse = { x: e.clientX, y: e.clientY };
|
|
224
|
+
|
|
225
|
+
// Check click on node
|
|
226
|
+
const mx = (e.clientX - pan.x) / zoom;
|
|
227
|
+
const my = (e.clientY - pan.y) / zoom;
|
|
228
|
+
selectedNode = null;
|
|
229
|
+
for (const n of nodes) {
|
|
230
|
+
const dx = n.x - mx, dy = n.y - my;
|
|
231
|
+
if (dx * dx + dy * dy < (n.r + 5) * (n.r + 5)) {
|
|
232
|
+
selectedNode = n;
|
|
233
|
+
document.getElementById('selected-node').textContent = n.label;
|
|
234
|
+
document.getElementById('selected-ip').textContent = n.ip || '-';
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
canvas.addEventListener('mousemove', (e) => {
|
|
241
|
+
if (!dragging) return;
|
|
242
|
+
pan.x += e.clientX - lastMouse.x;
|
|
243
|
+
pan.y += e.clientY - lastMouse.y;
|
|
244
|
+
lastMouse = { x: e.clientX, y: e.clientY };
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
canvas.addEventListener('mouseup', () => { dragging = false; });
|
|
248
|
+
canvas.addEventListener('wheel', (e) => {
|
|
249
|
+
const factor = e.deltaY > 0 ? 0.9 : 1.1;
|
|
250
|
+
zoom = Math.max(0.2, Math.min(5, zoom * factor));
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// ── Locate domain ───────────────────────────────────────
|
|
254
|
+
const domainInput = document.getElementById('domain-input');
|
|
255
|
+
const locateBtn = document.getElementById('locate-btn');
|
|
256
|
+
const resetBtn = document.getElementById('reset-btn');
|
|
257
|
+
|
|
258
|
+
locateBtn.addEventListener('click', () => addDomain(domainInput.value.trim()));
|
|
259
|
+
domainInput.addEventListener('keydown', (e) => {
|
|
260
|
+
if (e.key === 'Enter') addDomain(domainInput.value.trim());
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
async function addDomain(domain) {
|
|
264
|
+
if (!domain) return;
|
|
265
|
+
domain = domain.replace(/^https?:\/\//, '').replace(/\/.*/, '');
|
|
266
|
+
|
|
267
|
+
// Check if already exists
|
|
268
|
+
if (nodes.find(n => n.id === 'user-' + domain)) {
|
|
269
|
+
selectedNode = nodes.find(n => n.id === 'user-' + domain);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// DNS lookup
|
|
274
|
+
let ip = '-';
|
|
275
|
+
if (api) {
|
|
276
|
+
const result = await api.dnsLookup(domain);
|
|
277
|
+
if (result.addresses.length > 0) ip = result.addresses[0];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Position near a random backbone node
|
|
281
|
+
const backbone = nodes.filter(n => n.type === 'backbone' || n.type === 'cdn');
|
|
282
|
+
const nearest = backbone[Math.floor(Math.random() * backbone.length)];
|
|
283
|
+
const angle = Math.random() * Math.PI * 2;
|
|
284
|
+
const dist = 80 + Math.random() * 60;
|
|
285
|
+
|
|
286
|
+
const newNode = {
|
|
287
|
+
id: 'user-' + domain,
|
|
288
|
+
label: domain,
|
|
289
|
+
type: 'user',
|
|
290
|
+
x: nearest.x + Math.cos(angle) * dist,
|
|
291
|
+
y: nearest.y + Math.sin(angle) * dist,
|
|
292
|
+
r: 8,
|
|
293
|
+
ip: ip,
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
nodes.push(newNode);
|
|
297
|
+
edges.push([nearest.id, newNode.id]);
|
|
298
|
+
selectedNode = newNode;
|
|
299
|
+
document.getElementById('selected-node').textContent = domain;
|
|
300
|
+
document.getElementById('selected-ip').textContent = ip;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
resetBtn.addEventListener('click', () => {
|
|
304
|
+
nodes = [...BACKBONE];
|
|
305
|
+
edges = [...BACKBONE_EDGES];
|
|
306
|
+
selectedNode = null;
|
|
307
|
+
pan = { x: 0, y: 0 };
|
|
308
|
+
zoom = 1;
|
|
309
|
+
layout();
|
|
310
|
+
});
|
|
311
|
+
</script>
|
|
312
|
+
</body>
|
|
313
|
+
</html>
|