cyberdash 1.0.1__tar.gz
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.
- cyberdash-1.0.1/PKG-INFO +11 -0
- cyberdash-1.0.1/README.md +1 -0
- cyberdash-1.0.1/pyproject.toml +18 -0
- cyberdash-1.0.1/setup.cfg +4 -0
- cyberdash-1.0.1/src/cyberdash/app.py +206 -0
- cyberdash-1.0.1/src/cyberdash/m.py +252 -0
- cyberdash-1.0.1/src/cyberdash/plugins/__init__.py +0 -0
- cyberdash-1.0.1/src/cyberdash/plugins/base.py +23 -0
- cyberdash-1.0.1/src/cyberdash/plugins/hardware.py +65 -0
- cyberdash-1.0.1/src/cyberdash/plugins/n.py +59 -0
- cyberdash-1.0.1/src/cyberdash/plugins/network.py +80 -0
- cyberdash-1.0.1/src/cyberdash/plugins/processes.py +12 -0
- cyberdash-1.0.1/src/cyberdash/plugins/shell.py +18 -0
- cyberdash-1.0.1/src/cyberdash.egg-info/PKG-INFO +11 -0
- cyberdash-1.0.1/src/cyberdash.egg-info/SOURCES.txt +17 -0
- cyberdash-1.0.1/src/cyberdash.egg-info/dependency_links.txt +1 -0
- cyberdash-1.0.1/src/cyberdash.egg-info/entry_points.txt +2 -0
- cyberdash-1.0.1/src/cyberdash.egg-info/requires.txt +3 -0
- cyberdash-1.0.1/src/cyberdash.egg-info/top_level.txt +1 -0
cyberdash-1.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cyberdash
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: A modern hacker-style TUI dashboard for Linux and Termux
|
|
5
|
+
Requires-Python: >=3.8
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: textual
|
|
8
|
+
Requires-Dist: psutil
|
|
9
|
+
Requires-Dist: requests
|
|
10
|
+
|
|
11
|
+
# cyberdash
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# cyberdash
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cyberdash"
|
|
7
|
+
version = "1.0.1"
|
|
8
|
+
description = "A modern hacker-style TUI dashboard for Linux and Termux"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"textual",
|
|
13
|
+
"psutil",
|
|
14
|
+
"requests"
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
cyberdash = "cyberdash.app:run"
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import random, os, socket, asyncio, subprocess
|
|
2
|
+
from collections import deque
|
|
3
|
+
from textual.app import App, ComposeResult
|
|
4
|
+
from textual.containers import Horizontal, Vertical
|
|
5
|
+
from textual.widgets import Header, Footer, Static, DataTable, Label, Input, RichLog, Sparkline
|
|
6
|
+
|
|
7
|
+
# Import Modular Plugins
|
|
8
|
+
from .plugins.base import ResourceBar
|
|
9
|
+
from .plugins.hardware import HardwareMonitor
|
|
10
|
+
from .plugins.network import NetworkTracker
|
|
11
|
+
from .plugins.processes import ProcessEngine
|
|
12
|
+
from .plugins.shell import ShellCore
|
|
13
|
+
|
|
14
|
+
class OverlordApp(App):
|
|
15
|
+
ENABLE_COMMAND_PALETTE = False
|
|
16
|
+
|
|
17
|
+
BINDINGS = [
|
|
18
|
+
("q", "quit", "Shutdown"),
|
|
19
|
+
("r", "refresh_net", "Re-trace IP"),
|
|
20
|
+
("l", "clear_logs", "Wipe Logs"),
|
|
21
|
+
("h", "help_menu", "Help"),
|
|
22
|
+
("s", "status_check", "Status"),
|
|
23
|
+
("k", "kill_process", "Kill Selected"),
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
CSS = """
|
|
27
|
+
Screen { background: #000; opacity: 0%; }
|
|
28
|
+
.panel { border: double #004400; background: #050505; padding: 1; transition: border 500ms; }
|
|
29
|
+
.glow { border: double #00FF00; }
|
|
30
|
+
#top-stats { height: 7; border: round #333; margin: 1; padding: 1; }
|
|
31
|
+
ResourceBar { width: 1fr; margin: 0 2; }
|
|
32
|
+
ProgressBar > .bar--bar { color: #00FF00 !important; background: #111; }
|
|
33
|
+
ProgressBar > .bar--complete { color: #00FF00 !important; }
|
|
34
|
+
#label { color: #00FF00; margin-bottom: 0; }
|
|
35
|
+
#pct-label { width: 6; margin-left: 1; color: #00FF00; }
|
|
36
|
+
#middle-container { height: 1fr; }
|
|
37
|
+
#left-col { width: 40%; }
|
|
38
|
+
#right-col { width: 60%; }
|
|
39
|
+
DataTable { height: 1fr; background: #050505; color: #00FF00; border: none; }
|
|
40
|
+
DataTable > .datatable--cursor { background: #00FF00; color: #000; }
|
|
41
|
+
RichLog { background: #000; border: tall #111; }
|
|
42
|
+
Input { border: tall #004400; color: #00FF00; height: 3; transition: border 200ms; }
|
|
43
|
+
Input:focus { border: tall #00FF00; }
|
|
44
|
+
#osc-header { height: 1; margin-bottom: 0; }
|
|
45
|
+
#osc-title { width: 1fr; color: #00FF00; }
|
|
46
|
+
#cpu-temp { width: 12; text-align: right; color: #00FF00; }
|
|
47
|
+
#cpu-sparkline { color: #00FF00; height: 3; min-height: 3; margin-top: 0; }
|
|
48
|
+
#net-id { height: 7; margin-bottom: 1; color: #00FF00; }
|
|
49
|
+
Footer { dock: bottom; height: 1; background: #000; color: #00FF00; margin-top: 1; }
|
|
50
|
+
Footer > .footer--key { color: #FFF; background: #000; text-style: bold; }
|
|
51
|
+
Footer > .footer--description { color: #00FF00; background: #000; margin-right: 2; }
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self):
|
|
55
|
+
super().__init__()
|
|
56
|
+
self.hw = HardwareMonitor()
|
|
57
|
+
self.net = NetworkTracker()
|
|
58
|
+
self.proc = ProcessEngine()
|
|
59
|
+
self.shell = ShellCore()
|
|
60
|
+
self.cpu_history = deque([0]*45, maxlen=45)
|
|
61
|
+
self.app_data_cache, self.dns_name = None, "Detecting..."
|
|
62
|
+
self.last_net_key = self.net.get_network_state_key()
|
|
63
|
+
self.HACKER_EVENTS = [
|
|
64
|
+
"[cyan][*][/] Memory optimized.", "[green][+][/] Sync: [b]COMPLETE[/]",
|
|
65
|
+
"[red][!][/] Ping blocked.", "[cyan][*][/] Buffer flushed.",
|
|
66
|
+
"[yellow][!][/] Partition 04: [u]ReadOnly[/]", "[green][+][/] Security: [b]ACTIVE[/]"
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
def compose(self) -> ComposeResult:
|
|
70
|
+
yield Header()
|
|
71
|
+
with Vertical():
|
|
72
|
+
with Horizontal(id="top-stats"):
|
|
73
|
+
yield ResourceBar("CPU LOAD", id="cpu-bar")
|
|
74
|
+
yield ResourceBar("RAM USAGE", id="ram-bar")
|
|
75
|
+
yield ResourceBar("STORAGE", id="disk-bar")
|
|
76
|
+
with Horizontal(id="middle-container"):
|
|
77
|
+
with Vertical(id="left-col", classes="panel"):
|
|
78
|
+
yield Label("[b][cyan]>_[/] PROCESS ENGINE")
|
|
79
|
+
yield DataTable(id="proc-table")
|
|
80
|
+
yield Input(placeholder="Root Shell Access...", id="cmd-input")
|
|
81
|
+
with Vertical(id="right-col", classes="panel"):
|
|
82
|
+
with Horizontal(id="osc-header"):
|
|
83
|
+
yield Label("[b]CPU OSCILLOSCOPE[/]", id="osc-title")
|
|
84
|
+
yield Label("--°C", id="cpu-temp")
|
|
85
|
+
yield Sparkline(id="cpu-sparkline")
|
|
86
|
+
yield Label("\n[b]NETWORK IDENTITY[/]")
|
|
87
|
+
yield Static("[dim]Requesting Satellite Data...[/]", id="net-id")
|
|
88
|
+
yield Label("\n[b]KERNEL LOGS[/]")
|
|
89
|
+
yield RichLog(id="sys-log", markup=True)
|
|
90
|
+
yield Footer()
|
|
91
|
+
|
|
92
|
+
# --- PROTECTED KILL ACTION ---
|
|
93
|
+
def action_kill_process(self):
|
|
94
|
+
table = self.query_one("#proc-table", DataTable)
|
|
95
|
+
log = self.query_one("#sys-log")
|
|
96
|
+
try:
|
|
97
|
+
row_index = table.cursor_row
|
|
98
|
+
row_data = table.get_row_at(row_index)
|
|
99
|
+
target_pid = int(row_data[0])
|
|
100
|
+
target_name = row_data[1]
|
|
101
|
+
|
|
102
|
+
# --- SUICIDE PROTECTION ---
|
|
103
|
+
my_pid = os.getpid()
|
|
104
|
+
if target_pid == my_pid:
|
|
105
|
+
log.write("[bold red][!][/] ACCESS DENIED: Suicide protocol blocked.\n")
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
# Execute Kill
|
|
109
|
+
self.shell.execute(f"kill -9 {target_pid}")
|
|
110
|
+
log.write(f"[bold red][!][/] SIGKILL SENT TO: {target_name} ({target_pid})\n")
|
|
111
|
+
except:
|
|
112
|
+
log.write("[red][!][/] Selection Error: No target PID found.\n")
|
|
113
|
+
|
|
114
|
+
def update_dashboard(self):
|
|
115
|
+
log = self.query_one("#sys-log")
|
|
116
|
+
table = self.query_one("#proc-table")
|
|
117
|
+
current_cursor = table.cursor_row
|
|
118
|
+
|
|
119
|
+
cpu, ram, disk, is_locked, temp = self.hw.get_stats()
|
|
120
|
+
temp_wid = self.query_one("#cpu-temp")
|
|
121
|
+
if temp:
|
|
122
|
+
t_color = "red" if temp > 70 else "yellow" if temp > 45 else "green"
|
|
123
|
+
temp_wid.update(f"[{t_color}]{temp:.1f}°C[/]")
|
|
124
|
+
else:
|
|
125
|
+
temp_wid.update("[red]N/A[/]")
|
|
126
|
+
|
|
127
|
+
if is_locked:
|
|
128
|
+
self.query_one("#cpu-bar").update_bar(0)
|
|
129
|
+
self.query_one("#cpu-bar").query_one("#pct-label").update("[red]LOCK[/]")
|
|
130
|
+
cpu_val = cpu
|
|
131
|
+
else:
|
|
132
|
+
self.query_one("#cpu-bar").update_bar(cpu)
|
|
133
|
+
cpu_val = cpu
|
|
134
|
+
|
|
135
|
+
self.query_one("#ram-bar").update_bar(ram)
|
|
136
|
+
self.query_one("#disk-bar").update_bar(disk)
|
|
137
|
+
self.cpu_history.append(cpu_val)
|
|
138
|
+
self.query_one("#cpu-sparkline").data = list(self.cpu_history)
|
|
139
|
+
|
|
140
|
+
ms = self.net.get_latency()
|
|
141
|
+
ms_text = f"[bold green]{ms}ms[/]" if ms > 0 else "[red]OFFLINE[/]"
|
|
142
|
+
if self.app_data_cache:
|
|
143
|
+
d = self.app_data_cache
|
|
144
|
+
self.query_one("#net-id").update(f"IP: [y]{d['ip']}[/]\nISP: [y]{d['isp']}[/]\nLOC: [y]{d['loc']}[/]\nDNS: [cyan]{self.dns_name}[/]\nLATENCY: {ms_text}")
|
|
145
|
+
|
|
146
|
+
table.clear()
|
|
147
|
+
for p in self.proc.get_top_processes():
|
|
148
|
+
table.add_row(str(p['pid']), p['name'][:12], f"{p['memory_percent']:.1f}%")
|
|
149
|
+
|
|
150
|
+
if current_cursor is not None and current_cursor < len(table.rows):
|
|
151
|
+
table.move_cursor(row=current_cursor)
|
|
152
|
+
|
|
153
|
+
if random.random() > 0.8:
|
|
154
|
+
log.write(random.choice(self.HACKER_EVENTS) + "\n")
|
|
155
|
+
|
|
156
|
+
async def on_mount(self):
|
|
157
|
+
self.screen.styles.animate("opacity", value=1.0, duration=1.0)
|
|
158
|
+
self.set_interval(1.5, self.pulse_borders)
|
|
159
|
+
table = self.query_one("#proc-table")
|
|
160
|
+
table.add_columns("PID", "NAME", "MEM%")
|
|
161
|
+
table.cursor_type = "row"
|
|
162
|
+
self.set_interval(2.0, self.update_dashboard)
|
|
163
|
+
self.run_worker(self.fetch_network_info, thread=True)
|
|
164
|
+
|
|
165
|
+
async def fetch_network_info(self):
|
|
166
|
+
data, dns = self.net.trace_identity(), self.net.get_dns_provider()
|
|
167
|
+
if data:
|
|
168
|
+
self.app_data_cache, self.dns_name = data, dns
|
|
169
|
+
self.query_one("#net-id").update(f"IP: [y]{data['ip']}[/]\nISP: [y]{data['isp']}[/]\nDNS: [cyan]{dns}[/]")
|
|
170
|
+
|
|
171
|
+
async def on_input_submitted(self, event: Input.Submitted):
|
|
172
|
+
cmd = event.value.strip()
|
|
173
|
+
if not cmd: return
|
|
174
|
+
log = self.query_one("#sys-log")
|
|
175
|
+
event.input.value = ""
|
|
176
|
+
if cmd.lower() in ["clear", "cls"]:
|
|
177
|
+
log.clear()
|
|
178
|
+
return
|
|
179
|
+
log.write(f"[bold cyan]#[/] {cmd}\n")
|
|
180
|
+
self.run_worker(lambda: self.shell_worker(cmd), thread=True)
|
|
181
|
+
|
|
182
|
+
def shell_worker(self, cmd):
|
|
183
|
+
output = self.shell.execute(cmd)
|
|
184
|
+
self.call_from_thread(self.display_output, output)
|
|
185
|
+
|
|
186
|
+
def display_output(self, output):
|
|
187
|
+
log = self.query_one("#sys-log")
|
|
188
|
+
if output:
|
|
189
|
+
for line in output.splitlines()[-8:]: log.write(f" [dim]{line}[/]\n")
|
|
190
|
+
log.scroll_end()
|
|
191
|
+
|
|
192
|
+
def pulse_borders(self):
|
|
193
|
+
for panel in self.query(".panel"): panel.toggle_class("glow")
|
|
194
|
+
def action_refresh_net(self): self.run_worker(self.fetch_network_info, thread=True)
|
|
195
|
+
def action_clear_logs(self): self.query_one("#sys-log").clear()
|
|
196
|
+
def action_help_menu(self): self.query_one("#sys-log").write("[bold yellow]=== COMMAND GUIDE ===[/]\n")
|
|
197
|
+
def action_status_check(self):
|
|
198
|
+
uptime = self.shell.execute("uptime -p")
|
|
199
|
+
self.query_one("#sys-log").write(f"[cyan][*][/] [white]Uptime:[/] {uptime.strip()}\n")
|
|
200
|
+
|
|
201
|
+
def run():
|
|
202
|
+
app = OverlordApp()
|
|
203
|
+
app.run()
|
|
204
|
+
|
|
205
|
+
if __name__ == "__main__":
|
|
206
|
+
run()
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import os
|
|
3
|
+
import socket
|
|
4
|
+
import asyncio
|
|
5
|
+
from collections import deque
|
|
6
|
+
from textual.app import App, ComposeResult
|
|
7
|
+
from textual.containers import Horizontal, Vertical
|
|
8
|
+
from textual.widgets import Header, Footer, Static, DataTable, Label, Input, RichLog, Sparkline
|
|
9
|
+
|
|
10
|
+
# Import Modular Plugins
|
|
11
|
+
from plugins.base import ResourceBar
|
|
12
|
+
from plugins.hardware import HardwareMonitor
|
|
13
|
+
from plugins.network import NetworkTracker
|
|
14
|
+
from plugins.processes import ProcessEngine
|
|
15
|
+
from plugins.shell import ShellCore
|
|
16
|
+
|
|
17
|
+
class OverlordApp(App):
|
|
18
|
+
ENABLE_COMMAND_PALETTE = False
|
|
19
|
+
|
|
20
|
+
# User Bindings
|
|
21
|
+
BINDINGS = [
|
|
22
|
+
("q", "quit", "Shutdown"),
|
|
23
|
+
("r", "refresh_net", "Re-trace IP"),
|
|
24
|
+
("l", "clear_logs", "Wipe Logs"),
|
|
25
|
+
("h", "help_menu", "Help"),
|
|
26
|
+
("s", "status_check", "Status"),
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
# Optimized Stealth CSS
|
|
30
|
+
CSS = """
|
|
31
|
+
Screen { background: #000; }
|
|
32
|
+
.panel { border: double #00FF00; background: #050505; padding: 1; }
|
|
33
|
+
|
|
34
|
+
#top-stats { height: 7; border: round #333; margin: 1; padding: 1; }
|
|
35
|
+
ResourceBar { width: 1fr; margin: 0 2; }
|
|
36
|
+
|
|
37
|
+
ProgressBar > .bar--bar { color: #00FF00 !important; background: #111; }
|
|
38
|
+
ProgressBar > .bar--complete { color: #00FF00 !important; }
|
|
39
|
+
|
|
40
|
+
#label { color: #00FF00; margin-bottom: 0; }
|
|
41
|
+
#pct-label { width: 6; margin-left: 1; color: #00FF00; }
|
|
42
|
+
|
|
43
|
+
#middle-container { height: 1fr; }
|
|
44
|
+
#left-col { width: 40%; }
|
|
45
|
+
#right-col { width: 60%; }
|
|
46
|
+
|
|
47
|
+
DataTable { height: 1fr; background: #050505; color: #00FF00; border: none; }
|
|
48
|
+
RichLog { background: #000; border: tall #111; }
|
|
49
|
+
|
|
50
|
+
Input { border: tall #00FF00; color: #00FF00; height: 3; }
|
|
51
|
+
Sparkline { color: #00FF00; height: 3; }
|
|
52
|
+
|
|
53
|
+
#net-id { height: 7; margin-bottom: 1; color: #00FF00; }
|
|
54
|
+
|
|
55
|
+
Footer { dock: bottom; height: 1; background: #000; color: #00FF00; margin-top: 1; }
|
|
56
|
+
Footer > .footer--key { color: #FFF; background: #000; }
|
|
57
|
+
Footer > .footer--description { color: #00FF00; background: #000; margin-right: 2; }
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(self):
|
|
61
|
+
super().__init__()
|
|
62
|
+
# Initialize Logic Plugins
|
|
63
|
+
self.hw = HardwareMonitor()
|
|
64
|
+
self.net = NetworkTracker()
|
|
65
|
+
self.proc = ProcessEngine()
|
|
66
|
+
self.shell = ShellCore()
|
|
67
|
+
|
|
68
|
+
# UI & Data State
|
|
69
|
+
self.cpu_history = deque([0]*40, maxlen=40)
|
|
70
|
+
self.app_data_cache = None
|
|
71
|
+
self.dns_name = "Detecting..."
|
|
72
|
+
self.last_net_key = self.net.get_network_state_key()
|
|
73
|
+
|
|
74
|
+
# System Event Templates
|
|
75
|
+
self.HACKER_EVENTS = [
|
|
76
|
+
"[cyan][*][/] Memory clusters optimized.",
|
|
77
|
+
"[green][+][/] Background sync: [bold white]COMPLETE[/]",
|
|
78
|
+
"[red][!][/] Unauthorized ping blocked.",
|
|
79
|
+
"[cyan][*][/] Kernel buffer flushed.",
|
|
80
|
+
"[yellow][!][/] Partition 04: [u]Read-only[/]",
|
|
81
|
+
"[green][+][/] Security protocol: [bold green]ACTIVE[/]",
|
|
82
|
+
"[dim][*] Signal telemetry recalibrated.[/]"
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
def compose(self) -> ComposeResult:
|
|
86
|
+
yield Header()
|
|
87
|
+
with Vertical():
|
|
88
|
+
# Hardware Status Bar
|
|
89
|
+
with Horizontal(id="top-stats"):
|
|
90
|
+
yield ResourceBar("CPU LOAD", id="cpu-bar")
|
|
91
|
+
yield ResourceBar("RAM USAGE", id="ram-bar")
|
|
92
|
+
yield ResourceBar("STORAGE", id="disk-bar")
|
|
93
|
+
|
|
94
|
+
# Interactive Core
|
|
95
|
+
with Horizontal(id="middle-container"):
|
|
96
|
+
# Shell & Process Side
|
|
97
|
+
with Vertical(id="left-col", classes="panel"):
|
|
98
|
+
yield Label("[b][cyan]>_[/] PROCESS ENGINE")
|
|
99
|
+
yield DataTable(id="proc-table")
|
|
100
|
+
yield Input(placeholder="Root Shell Access...", id="cmd-input")
|
|
101
|
+
|
|
102
|
+
# Diagnostics & Log Side
|
|
103
|
+
with Vertical(id="right-col", classes="panel"):
|
|
104
|
+
yield Label("[b]CPU OSCILLOSCOPE[/]")
|
|
105
|
+
yield Sparkline(id="cpu-sparkline")
|
|
106
|
+
|
|
107
|
+
yield Label("\n[b]NETWORK IDENTITY[/]")
|
|
108
|
+
yield Static("[dim]Requesting Satellite Data...[/]", id="net-id")
|
|
109
|
+
|
|
110
|
+
yield Label("\n[b]KERNEL LOGS[/]")
|
|
111
|
+
yield RichLog(id="sys-log", markup=True)
|
|
112
|
+
yield Footer()
|
|
113
|
+
|
|
114
|
+
# --- ACTIONS (Keyboard Shortcuts) ---
|
|
115
|
+
|
|
116
|
+
def action_help_menu(self):
|
|
117
|
+
log = self.query_one("#sys-log")
|
|
118
|
+
log.write("\n[bold yellow]=== COMMAND GUIDE ===[/]\n")
|
|
119
|
+
log.write("[white]Q[/]: Shutdown | [white]R[/]: IP Trace | [white]L[/]: Wipe Logs\n")
|
|
120
|
+
log.write("[white]H[/]: Help | [white]S[/]: Diagnostics\n")
|
|
121
|
+
log.write("[dim]Terminal: 'clear' to wipe, 'help' for guide.[/]\n")
|
|
122
|
+
|
|
123
|
+
def action_status_check(self):
|
|
124
|
+
log = self.query_one("#sys-log")
|
|
125
|
+
log.write("\n[bold green]>>> INITIATING DIAGNOSTICS...[/]\n")
|
|
126
|
+
uptime = self.shell.execute("uptime -p")
|
|
127
|
+
log.write(f"[cyan][*][/] [white]Uptime:[/] {uptime.strip()}\n")
|
|
128
|
+
log.write(f"[cyan][*][/] [white]DNS Service:[/] {self.dns_name}\n")
|
|
129
|
+
log.write("[bold green]>>> DIAGNOSTICS COMPLETE.[/]\n")
|
|
130
|
+
|
|
131
|
+
def action_refresh_net(self):
|
|
132
|
+
self.query_one("#net-id").update("[dim]Re-tracing...[/]")
|
|
133
|
+
self.run_worker(self.fetch_network_info, thread=True)
|
|
134
|
+
|
|
135
|
+
def action_clear_logs(self):
|
|
136
|
+
self.query_one("#sys-log").clear()
|
|
137
|
+
|
|
138
|
+
# --- CORE WORKERS & UPDATERS ---
|
|
139
|
+
|
|
140
|
+
def on_mount(self):
|
|
141
|
+
log = self.query_one("#sys-log")
|
|
142
|
+
self.query_one("#proc-table").add_columns("PID", "NAME", "MEM%")
|
|
143
|
+
|
|
144
|
+
# Initial Boot UI
|
|
145
|
+
log.write("[yellow][!][/] KERNEL BOOT SEQUENCE INITIATED...\n")
|
|
146
|
+
log.write("[cyan][*][/] MODULE: HardwareMonitor... [green]LOADED[/]\n")
|
|
147
|
+
log.write("[cyan][*][/] MODULE: NetworkTracker... [green]LOADED[/]\n")
|
|
148
|
+
log.write("[white][!][/] SYSTEM OVERLORD [bold green]ONLINE[/].\n")
|
|
149
|
+
|
|
150
|
+
# Start intervals
|
|
151
|
+
self.set_interval(2.0, self.update_dashboard)
|
|
152
|
+
self.run_worker(self.fetch_network_info, thread=True)
|
|
153
|
+
|
|
154
|
+
async def fetch_network_info(self):
|
|
155
|
+
"""Fetch heavy network data (IP/ISP/DNS) in background."""
|
|
156
|
+
data = self.net.trace_identity()
|
|
157
|
+
dns = self.net.get_dns_provider()
|
|
158
|
+
if data:
|
|
159
|
+
self.app_data_cache = data
|
|
160
|
+
self.dns_name = dns
|
|
161
|
+
val = f"IP: [y]{data['ip']}[/]\nISP: [y]{data['isp']}[/]\nDNS: [cyan]{dns}[/]"
|
|
162
|
+
self.query_one("#net-id").update(val)
|
|
163
|
+
|
|
164
|
+
def update_dashboard(self):
|
|
165
|
+
"""Main refresh loop for hardware and live latency."""
|
|
166
|
+
log = self.query_one("#sys-log")
|
|
167
|
+
|
|
168
|
+
# 1. AUTO-REFRESH NETWORK ON CONNECTIVITY CHANGE
|
|
169
|
+
current_net_key = self.net.get_network_state_key()
|
|
170
|
+
if current_net_key != self.last_net_key:
|
|
171
|
+
self.last_net_key = current_net_key
|
|
172
|
+
log.write("[yellow][!][/] Network change detected. Re-tracing...\n")
|
|
173
|
+
self.run_worker(self.fetch_network_info, thread=True)
|
|
174
|
+
|
|
175
|
+
# 2. Hardware Resource Update
|
|
176
|
+
cpu, ram, disk = self.hw.get_stats()
|
|
177
|
+
if cpu == -1.0:
|
|
178
|
+
self.query_one("#cpu-bar").update_bar(0)
|
|
179
|
+
self.query_one("#cpu-bar").query_one("#pct-label").update("[red]LOCK[/]")
|
|
180
|
+
cpu_val = 0
|
|
181
|
+
else:
|
|
182
|
+
self.query_one("#cpu-bar").update_bar(cpu)
|
|
183
|
+
cpu_val = cpu
|
|
184
|
+
|
|
185
|
+
self.query_one("#ram-bar").update_bar(ram)
|
|
186
|
+
self.query_one("#disk-bar").update_bar(disk)
|
|
187
|
+
|
|
188
|
+
# Sparkline update
|
|
189
|
+
self.cpu_history.append(cpu_val)
|
|
190
|
+
self.query_one("#cpu-sparkline").data = list(self.cpu_history)
|
|
191
|
+
|
|
192
|
+
# 3. Network Latency Update (LIVE)
|
|
193
|
+
ms = self.net.get_latency()
|
|
194
|
+
latency_color = "green" if ms < 100 else "yellow" if ms < 300 else "red"
|
|
195
|
+
ms_text = f"[bold {latency_color}]{ms}ms[/]" if ms > 0 else "[red]OFFLINE[/]"
|
|
196
|
+
|
|
197
|
+
if self.app_data_cache:
|
|
198
|
+
d = self.app_data_cache
|
|
199
|
+
self.query_one("#net-id").update(
|
|
200
|
+
f"IP: [y]{d['ip']}[/]\nISP: [y]{d['isp']}[/]\n"
|
|
201
|
+
f"LOC: [y]{d['loc']}[/]\nDNS: [cyan]{self.dns_name}[/]\n"
|
|
202
|
+
f"LATENCY: {ms_text}"
|
|
203
|
+
)
|
|
204
|
+
elif ms == -1:
|
|
205
|
+
self.query_one("#net-id").update("[red]NO INTERNET CONNECTION[/]")
|
|
206
|
+
|
|
207
|
+
# 4. Process Table Update
|
|
208
|
+
table = self.query_one("#proc-table")
|
|
209
|
+
table.clear()
|
|
210
|
+
for p in self.proc.get_top_processes():
|
|
211
|
+
table.add_row(str(p['pid']), p['name'][:12], f"{p['memory_percent']:.1f}%")
|
|
212
|
+
|
|
213
|
+
# 5. Background Activity Logs
|
|
214
|
+
if random.random() > 0.8:
|
|
215
|
+
log.write(random.choice(self.HACKER_EVENTS) + "\n")
|
|
216
|
+
|
|
217
|
+
async def on_input_submitted(self, event: Input.Submitted):
|
|
218
|
+
cmd = event.value.strip()
|
|
219
|
+
log = self.query_one("#sys-log")
|
|
220
|
+
event.input.value = ""
|
|
221
|
+
|
|
222
|
+
if not cmd: return
|
|
223
|
+
|
|
224
|
+
if cmd.lower() in ["clear", "cls"]:
|
|
225
|
+
log.clear()
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
if cmd.lower() == "help":
|
|
229
|
+
self.action_help_menu()
|
|
230
|
+
return
|
|
231
|
+
|
|
232
|
+
# NEW: Run the shell command in a background worker
|
|
233
|
+
log.write(f"[bold cyan]#[/] {cmd}\n")
|
|
234
|
+
|
|
235
|
+
# We define a small internal function to run the command
|
|
236
|
+
def run_shell():
|
|
237
|
+
output = self.shell.execute(cmd)
|
|
238
|
+
# Use call_from_thread to safely update the UI from the worker
|
|
239
|
+
self.call_from_thread(self.post_shell_output, output)
|
|
240
|
+
|
|
241
|
+
self.run_worker(run_shell, thread=True)
|
|
242
|
+
|
|
243
|
+
def post_shell_output(self, output: str):
|
|
244
|
+
"""Callback to print the result once the background task finishes."""
|
|
245
|
+
log = self.query_one("#sys-log")
|
|
246
|
+
if output:
|
|
247
|
+
for line in output.splitlines()[-15:]: # Increased to 15 lines
|
|
248
|
+
log.write(f" [dim]{line}[/]\n")
|
|
249
|
+
log.scroll_end()
|
|
250
|
+
|
|
251
|
+
if __name__ == "__main__":
|
|
252
|
+
OverlordApp().run()
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from textual.widgets import Static, Label, ProgressBar
|
|
2
|
+
from textual.containers import Horizontal
|
|
3
|
+
|
|
4
|
+
class ResourceBar(Static):
|
|
5
|
+
def __init__(self, name: str, **kwargs):
|
|
6
|
+
super().__init__(**kwargs)
|
|
7
|
+
self.resource_name = name
|
|
8
|
+
|
|
9
|
+
def compose(self):
|
|
10
|
+
yield Label(f"[b]{self.resource_name}[/]", id="label")
|
|
11
|
+
with Horizontal():
|
|
12
|
+
yield ProgressBar(total=100, show_eta=False, show_percentage=False, id="bar")
|
|
13
|
+
yield Label("0%", id="pct-label")
|
|
14
|
+
|
|
15
|
+
def update_bar(self, value: float):
|
|
16
|
+
try:
|
|
17
|
+
bar = self.query_one("#bar", ProgressBar)
|
|
18
|
+
# FIX: Animate the 'progress' attribute of the bar widget directly
|
|
19
|
+
bar.animate("progress", value=float(value), duration=0.5)
|
|
20
|
+
# Update the text label
|
|
21
|
+
self.query_one("#pct-label").update(f"{int(value)}%")
|
|
22
|
+
except:
|
|
23
|
+
pass
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import psutil, shutil, os, random, subprocess, json
|
|
2
|
+
# At the top:
|
|
3
|
+
from .base import ResourceBar
|
|
4
|
+
|
|
5
|
+
class HardwareMonitor:
|
|
6
|
+
def __init__(self):
|
|
7
|
+
try:
|
|
8
|
+
psutil.cpu_percent(interval=None)
|
|
9
|
+
except:
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
def get_cpu_temp(self):
|
|
13
|
+
"""Tries 3 methods to get temperature: Real, Battery, or Estimated."""
|
|
14
|
+
# --- Method 1: Real Hardware Sensors (SysFS) ---
|
|
15
|
+
for i in range(15):
|
|
16
|
+
path = f"/sys/class/thermal/thermal_zone{i}/temp"
|
|
17
|
+
try:
|
|
18
|
+
if os.path.exists(path):
|
|
19
|
+
with open(path, "r") as f:
|
|
20
|
+
t = int(f.read().strip())
|
|
21
|
+
if t > 1000: t /= 1000
|
|
22
|
+
if 25 < t < 95: return t
|
|
23
|
+
except: continue
|
|
24
|
+
|
|
25
|
+
# --- Method 2: Termux-API (Battery Temp Proxy) ---
|
|
26
|
+
try:
|
|
27
|
+
# This calls the termux-api command
|
|
28
|
+
res = subprocess.check_output(["termux-battery-status"], timeout=1)
|
|
29
|
+
data = json.loads(res)
|
|
30
|
+
return data.get("temperature") # Returns float like 38.5
|
|
31
|
+
except:
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
# --- Method 3: Thermal Proxy (Simulated based on Load) ---
|
|
35
|
+
# If we can't read anything, we simulate 'System Warmth'
|
|
36
|
+
# based on current CPU usage to keep the dashboard 'alive'.
|
|
37
|
+
try:
|
|
38
|
+
load = psutil.cpu_percent()
|
|
39
|
+
# Base temp 32C + (Load * 0.2) + random fluctuation
|
|
40
|
+
estimate = 32 + (load * 0.15) + random.uniform(-1, 1)
|
|
41
|
+
return estimate
|
|
42
|
+
except:
|
|
43
|
+
return random.uniform(35, 42)
|
|
44
|
+
|
|
45
|
+
def get_stats(self):
|
|
46
|
+
try:
|
|
47
|
+
cpu = psutil.cpu_percent(interval=0.1)
|
|
48
|
+
is_locked = False
|
|
49
|
+
except PermissionError:
|
|
50
|
+
cpu = random.uniform(1.0, 5.0)
|
|
51
|
+
is_locked = True
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
ram = psutil.virtual_memory().percent
|
|
55
|
+
except:
|
|
56
|
+
ram = 0.0
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
usage = shutil.disk_usage(os.path.expanduser("~"))
|
|
60
|
+
disk = (usage.used / usage.total) * 100
|
|
61
|
+
except:
|
|
62
|
+
disk = 0.0
|
|
63
|
+
|
|
64
|
+
temp = self.get_cpu_temp()
|
|
65
|
+
return cpu, ram, disk, is_locked, temp
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
class NetworkTracker:
|
|
4
|
+
def trace_identity(self):
|
|
5
|
+
headers = {"User-Agent": "TermuxCyberDash/1.2"}
|
|
6
|
+
# List of URLs to try
|
|
7
|
+
urls = ["https://ipapi.co/json/", "http://ip-api.com/json/"]
|
|
8
|
+
|
|
9
|
+
for url in urls:
|
|
10
|
+
try:
|
|
11
|
+
# Short 3-second timeout to prevent UI hanging
|
|
12
|
+
res = requests.get(url, headers=headers, timeout=3).json()
|
|
13
|
+
if res:
|
|
14
|
+
return {
|
|
15
|
+
"ip": res.get('ip') or res.get('query'),
|
|
16
|
+
"isp": res.get('org') or res.get('isp'),
|
|
17
|
+
"loc": f"{res.get('city')}, {res.get('country_name') or res.get('country')}"
|
|
18
|
+
}
|
|
19
|
+
except:
|
|
20
|
+
continue
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
def get_latency(self):
|
|
24
|
+
"""Measure latency (ms) to Cloudflare DNS."""
|
|
25
|
+
target = "1.1.1.1"
|
|
26
|
+
try:
|
|
27
|
+
start = time.time()
|
|
28
|
+
# Try a TCP connection to port 53 (DNS)
|
|
29
|
+
socket.create_connection((target, 53), timeout=2)
|
|
30
|
+
end = time.time()
|
|
31
|
+
return int((end - start) * 1000)
|
|
32
|
+
except:
|
|
33
|
+
return -1
|
|
34
|
+
|
|
35
|
+
def get_dns_provider(self):
|
|
36
|
+
"""Identify current DNS provider."""
|
|
37
|
+
try:
|
|
38
|
+
# On Termux/Android, we check getprop
|
|
39
|
+
dns_ip = subprocess.getoutput("getprop net.dns1").strip()
|
|
40
|
+
if not dns_ip:
|
|
41
|
+
# Fallback for standard Linux
|
|
42
|
+
with open("/etc/resolv.conf", "r") as f:
|
|
43
|
+
for line in f:
|
|
44
|
+
if line.startswith("nameserver"):
|
|
45
|
+
dns_ip = line.split()[1]
|
|
46
|
+
break
|
|
47
|
+
|
|
48
|
+
# Map common IPs to names
|
|
49
|
+
mapping = {
|
|
50
|
+
"1.1.1.1": "Cloudflare",
|
|
51
|
+
"1.0.0.1": "Cloudflare",
|
|
52
|
+
"8.8.8.8": "Google Public DNS",
|
|
53
|
+
"8.8.4.4": "Google Public DNS",
|
|
54
|
+
"9.9.9.9": "Quad9",
|
|
55
|
+
"208.67.222.222": "OpenDNS"
|
|
56
|
+
}
|
|
57
|
+
return mapping.get(dns_ip, dns_ip if dns_ip else "System Default")
|
|
58
|
+
except:
|
|
59
|
+
return "Internal/Protected"
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import socket
|
|
3
|
+
import time
|
|
4
|
+
import subprocess
|
|
5
|
+
import psutil
|
|
6
|
+
|
|
7
|
+
class NetworkTracker:
|
|
8
|
+
def trace_identity(self):
|
|
9
|
+
"""Fetch ISP/IP info."""
|
|
10
|
+
headers = {"User-Agent": "TermuxCyberDash/1.2"}
|
|
11
|
+
try:
|
|
12
|
+
res = requests.get("https://ipapi.co/json/", headers=headers, timeout=5).json()
|
|
13
|
+
if res and not res.get("error"):
|
|
14
|
+
return {
|
|
15
|
+
"ip": res.get('ip') or res.get('query'),
|
|
16
|
+
"isp": res.get('org') or res.get('isp'),
|
|
17
|
+
"loc": f"{res.get('city')}, {res.get('country_name') or res.get('country')}"
|
|
18
|
+
}
|
|
19
|
+
except:
|
|
20
|
+
pass
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
def get_network_state_key(self):
|
|
24
|
+
"""
|
|
25
|
+
Creates a fingerprint of the network.
|
|
26
|
+
Now watches local IPs AND System DNS properties.
|
|
27
|
+
"""
|
|
28
|
+
key = ""
|
|
29
|
+
try:
|
|
30
|
+
# 1. Watch Local IP changes (Wi-Fi vs Data)
|
|
31
|
+
interfaces = psutil.net_if_addrs()
|
|
32
|
+
for snic in interfaces:
|
|
33
|
+
for addr in interfaces[snic]:
|
|
34
|
+
key += str(addr.address)
|
|
35
|
+
|
|
36
|
+
# 2. Watch Android DNS properties
|
|
37
|
+
# If you switch Private DNS, these properties often change
|
|
38
|
+
key += subprocess.getoutput("getprop net.dns1")
|
|
39
|
+
key += subprocess.getoutput("getprop net.dns2")
|
|
40
|
+
|
|
41
|
+
return key
|
|
42
|
+
except:
|
|
43
|
+
return key
|
|
44
|
+
|
|
45
|
+
def get_latency(self):
|
|
46
|
+
"""Measure latency (ms)."""
|
|
47
|
+
targets = [("1.1.1.1", 53), ("8.8.8.8", 53), ("google.com", 80)]
|
|
48
|
+
for host, port in targets:
|
|
49
|
+
try:
|
|
50
|
+
start = time.time()
|
|
51
|
+
socket.create_connection((host, port), timeout=2)
|
|
52
|
+
end = time.time()
|
|
53
|
+
return int((end - start) * 1000)
|
|
54
|
+
except:
|
|
55
|
+
continue
|
|
56
|
+
return -1
|
|
57
|
+
|
|
58
|
+
def get_dns_provider(self):
|
|
59
|
+
"""Identifies the DNS provider handling your traffic."""
|
|
60
|
+
try:
|
|
61
|
+
# Check Cloudflare's diagnostic trace (Very fast)
|
|
62
|
+
trace = requests.get("https://1.1.1.1/cdn-cgi/trace", timeout=2).text
|
|
63
|
+
if "dns=on" in trace: return "Cloudflare (1.1.1.1)"
|
|
64
|
+
if "warp=on" in trace: return "Cloudflare WARP"
|
|
65
|
+
except:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
# Server-side DNS leak probe
|
|
70
|
+
res = requests.get("http://edns.ip-api.com/json", timeout=2).json()
|
|
71
|
+
dns_data = res.get("dns", {})
|
|
72
|
+
geo = dns_data.get("geo", "")
|
|
73
|
+
if "Cloudflare" in geo: return "Cloudflare DNS"
|
|
74
|
+
if "Google" in geo: return "Google DNS"
|
|
75
|
+
if "OpenDNS" in geo: return "OpenDNS"
|
|
76
|
+
|
|
77
|
+
ip = dns_data.get("ip", "")
|
|
78
|
+
return f"Resolver ({ip[:15]})" if ip else "ISP Default"
|
|
79
|
+
except:
|
|
80
|
+
return "ISP / Private DNS"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import psutil
|
|
2
|
+
|
|
3
|
+
class ProcessEngine:
|
|
4
|
+
def get_top_processes(self, limit=8):
|
|
5
|
+
procs = []
|
|
6
|
+
for p in psutil.process_iter(['pid', 'name', 'memory_percent']):
|
|
7
|
+
try:
|
|
8
|
+
if p.info['name'] and p.info['memory_percent'] is not None:
|
|
9
|
+
procs.append(p.info)
|
|
10
|
+
except (psutil.AccessDenied, psutil.NoSuchProcess):
|
|
11
|
+
continue
|
|
12
|
+
return sorted(procs, key=lambda x: x['memory_percent'], reverse=True)[:limit]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
|
|
3
|
+
class ShellCore:
|
|
4
|
+
def execute(self, cmd: str):
|
|
5
|
+
try:
|
|
6
|
+
# Increased timeout to 300 seconds (5 minutes) for big tasks like 'pkg up'
|
|
7
|
+
result = subprocess.run(
|
|
8
|
+
cmd,
|
|
9
|
+
shell=True,
|
|
10
|
+
capture_output=True,
|
|
11
|
+
text=True,
|
|
12
|
+
timeout=300
|
|
13
|
+
)
|
|
14
|
+
return result.stdout if result.stdout else result.stderr
|
|
15
|
+
except subprocess.TimeoutExpired:
|
|
16
|
+
return "Error: Command timed out. Task is too large for dashboard shell."
|
|
17
|
+
except Exception as e:
|
|
18
|
+
return f"Error: {str(e)}"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cyberdash
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: A modern hacker-style TUI dashboard for Linux and Termux
|
|
5
|
+
Requires-Python: >=3.8
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: textual
|
|
8
|
+
Requires-Dist: psutil
|
|
9
|
+
Requires-Dist: requests
|
|
10
|
+
|
|
11
|
+
# cyberdash
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/cyberdash/app.py
|
|
4
|
+
src/cyberdash/m.py
|
|
5
|
+
src/cyberdash.egg-info/PKG-INFO
|
|
6
|
+
src/cyberdash.egg-info/SOURCES.txt
|
|
7
|
+
src/cyberdash.egg-info/dependency_links.txt
|
|
8
|
+
src/cyberdash.egg-info/entry_points.txt
|
|
9
|
+
src/cyberdash.egg-info/requires.txt
|
|
10
|
+
src/cyberdash.egg-info/top_level.txt
|
|
11
|
+
src/cyberdash/plugins/__init__.py
|
|
12
|
+
src/cyberdash/plugins/base.py
|
|
13
|
+
src/cyberdash/plugins/hardware.py
|
|
14
|
+
src/cyberdash/plugins/n.py
|
|
15
|
+
src/cyberdash/plugins/network.py
|
|
16
|
+
src/cyberdash/plugins/processes.py
|
|
17
|
+
src/cyberdash/plugins/shell.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cyberdash
|