Fred808 commited on
Commit
e1b4496
·
verified ·
1 Parent(s): b570bcf

Upload 17 files

Browse files
.dockerignore ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ venv/
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ *.pyd
6
+ .Python
7
+ .git/
8
+ .gitignore
9
+ *.md
10
+ .DS_Store
11
+ .vscode/
12
+ .idea/
13
+ *.log
14
+ .env
15
+ node_modules/
16
+ *.tmp
17
+ *.temp
18
+
Dockerfile ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ # Set working directory
4
+ WORKDIR /app
5
+
6
+ # Install system dependencies
7
+ RUN apt-get update && apt-get install -y \
8
+ gcc \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Copy requirements first for better caching
12
+ COPY requirements.txt .
13
+
14
+ # Install Python dependencies
15
+ RUN pip install --no-cache-dir -r requirements.txt
16
+
17
+ # Copy the application code
18
+ COPY src/ ./src/
19
+
20
+ # Create necessary directories
21
+ RUN mkdir -p src/database
22
+
23
+ # Set environment variables
24
+ ENV FLASK_APP=src/main.py
25
+ ENV FLASK_ENV=production
26
+ ENV PYTHONPATH=/app
27
+
28
+ # Expose port (Hugging Face Spaces uses port 7860)
29
+ EXPOSE 7860
30
+
31
+ # Create a non-root user for security
32
+ RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
33
+ USER appuser
34
+
35
+ # Command to run the application
36
+ CMD ["python", "src/main.py"]
37
+
requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ blinker==1.9.0
2
+ click==8.2.1
3
+ Flask==3.1.1
4
+ flask-cors==6.0.0
5
+ Flask-SQLAlchemy==3.1.1
6
+ greenlet==3.2.3
7
+ itsdangerous==2.2.0
8
+ Jinja2==3.1.6
9
+ MarkupSafe==3.0.2
10
+ SQLAlchemy==2.0.41
11
+ typing_extensions==4.14.0
12
+ Werkzeug==3.1.3
src/__init__.py ADDED
File without changes
src/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (150 Bytes). View file
 
src/database/app.db ADDED
Binary file (16.4 kB). View file
 
src/main.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ # DON'T CHANGE THIS !!!
4
+ sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
5
+
6
+ from flask import Flask, send_from_directory
7
+ from flask_cors import CORS
8
+ from src.models.user import db
9
+ from src.routes.user import user_bp
10
+ from src.routes.scanner import scanner_bp
11
+
12
+ app = Flask(__name__, static_folder=os.path.join(os.path.dirname(__file__), 'static'))
13
+ app.config['SECRET_KEY'] = 'asdf#FGSgvasgf$5$WGT'
14
+
15
+ # Enable CORS for all routes
16
+ CORS(app)
17
+
18
+ app.register_blueprint(user_bp, url_prefix='/api')
19
+ app.register_blueprint(scanner_bp, url_prefix='/api')
20
+
21
+ # uncomment if you need to use database
22
+ app.config['SQLALCHEMY_DATABASE_URI'] = f"sqlite:///{os.path.join(os.path.dirname(__file__), 'database', 'app.db')}"
23
+ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
24
+ db.init_app(app)
25
+ with app.app_context():
26
+ db.create_all()
27
+
28
+ @app.route('/', defaults={'path': ''})
29
+ @app.route('/<path:path>')
30
+ def serve(path):
31
+ static_folder_path = app.static_folder
32
+ if static_folder_path is None:
33
+ return "Static folder not configured", 404
34
+
35
+ if path != "" and os.path.exists(os.path.join(static_folder_path, path)):
36
+ return send_from_directory(static_folder_path, path)
37
+ else:
38
+ index_path = os.path.join(static_folder_path, 'index.html')
39
+ if os.path.exists(index_path):
40
+ return send_from_directory(static_folder_path, 'index.html')
41
+ else:
42
+ return "index.html not found", 404
43
+
44
+
45
+ if __name__ == '__main__':
46
+ import os
47
+ port = int(os.environ.get('PORT', 7860))
48
+ app.run(host='0.0.0.0', port=port, debug=False)
src/models/__pycache__/user.cpython-311.pyc ADDED
Binary file (1.3 kB). View file
 
src/models/user.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask_sqlalchemy import SQLAlchemy
2
+
3
+ db = SQLAlchemy()
4
+
5
+ class User(db.Model):
6
+ id = db.Column(db.Integer, primary_key=True)
7
+ username = db.Column(db.String(80), unique=True, nullable=False)
8
+ email = db.Column(db.String(120), unique=True, nullable=False)
9
+
10
+ def __repr__(self):
11
+ return f'<User {self.username}>'
12
+
13
+ def to_dict(self):
14
+ return {
15
+ 'id': self.id,
16
+ 'username': self.username,
17
+ 'email': self.email
18
+ }
src/routes/__pycache__/scanner.cpython-311.pyc ADDED
Binary file (9.84 kB). View file
 
src/routes/__pycache__/user.cpython-311.pyc ADDED
Binary file (3.4 kB). View file
 
src/routes/scanner.py ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import socket
2
+ import threading
3
+ import time
4
+ from flask import Blueprint, request, jsonify
5
+ from flask_cors import cross_origin
6
+
7
+ scanner_bp = Blueprint('scanner', __name__)
8
+
9
+ class PortScanner:
10
+ def __init__(self, target_ip, start_port=1, end_port=1000, timeout=1, threads=100):
11
+ self.target_ip = target_ip
12
+ self.start_port = start_port
13
+ self.end_port = end_port
14
+ self.timeout = timeout
15
+ self.threads = threads
16
+ self.open_ports = []
17
+ self.lock = threading.Lock()
18
+ self.progress = 0
19
+ self.total_ports = end_port - start_port + 1
20
+
21
+ def scan_port(self, port):
22
+ try:
23
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
24
+ sock.settimeout(self.timeout)
25
+ result = sock.connect_ex((self.target_ip, port))
26
+
27
+ if result == 0:
28
+ # Try to get service name
29
+ try:
30
+ service = socket.getservbyport(port)
31
+ except:
32
+ service = "unknown"
33
+
34
+ with self.lock:
35
+ self.open_ports.append({
36
+ 'port': port,
37
+ 'service': service,
38
+ 'status': 'open'
39
+ })
40
+
41
+ sock.close()
42
+
43
+ except Exception as e:
44
+ pass
45
+
46
+ with self.lock:
47
+ self.progress += 1
48
+
49
+ def scan_range(self, port_list):
50
+ for port in port_list:
51
+ self.scan_port(port)
52
+
53
+ def start_scan(self):
54
+ ports = list(range(self.start_port, self.end_port + 1))
55
+ chunk_size = len(ports) // self.threads
56
+
57
+ if chunk_size == 0:
58
+ chunk_size = 1
59
+
60
+ threads = []
61
+
62
+ for i in range(0, len(ports), chunk_size):
63
+ chunk = ports[i:i + chunk_size]
64
+ thread = threading.Thread(target=self.scan_range, args=(chunk,))
65
+ threads.append(thread)
66
+ thread.start()
67
+
68
+ for thread in threads:
69
+ thread.join()
70
+
71
+ return sorted(self.open_ports, key=lambda x: x['port'])
72
+
73
+ # Global scanner instance for progress tracking
74
+ current_scanner = None
75
+
76
+ @scanner_bp.route('/scan', methods=['POST'])
77
+ @cross_origin()
78
+ def scan_ports():
79
+ global current_scanner
80
+
81
+ try:
82
+ data = request.get_json()
83
+
84
+ if not data:
85
+ return jsonify({'error': 'No data provided'}), 400
86
+
87
+ target_ip = data.get('ip')
88
+ start_port = int(data.get('start_port', 1))
89
+ end_port = int(data.get('end_port', 1000))
90
+ timeout = float(data.get('timeout', 1))
91
+ threads = int(data.get('threads', 100))
92
+
93
+ if not target_ip:
94
+ return jsonify({'error': 'IP address is required'}), 400
95
+
96
+ # Validate IP address
97
+ try:
98
+ socket.inet_aton(target_ip)
99
+ except socket.error:
100
+ return jsonify({'error': 'Invalid IP address format'}), 400
101
+
102
+ # Validate port range
103
+ if start_port < 1 or end_port > 65535 or start_port > end_port:
104
+ return jsonify({'error': 'Invalid port range'}), 400
105
+
106
+ # Limit port range for safety
107
+ if end_port - start_port > 10000:
108
+ return jsonify({'error': 'Port range too large (max 10000 ports)'}), 400
109
+
110
+ current_scanner = PortScanner(target_ip, start_port, end_port, timeout, threads)
111
+
112
+ start_time = time.time()
113
+ open_ports = current_scanner.start_scan()
114
+ end_time = time.time()
115
+
116
+ scan_duration = round(end_time - start_time, 2)
117
+
118
+ return jsonify({
119
+ 'success': True,
120
+ 'target_ip': target_ip,
121
+ 'scan_range': f"{start_port}-{end_port}",
122
+ 'open_ports': open_ports,
123
+ 'total_open': len(open_ports),
124
+ 'total_scanned': current_scanner.total_ports,
125
+ 'scan_duration': scan_duration
126
+ })
127
+
128
+ except Exception as e:
129
+ return jsonify({'error': str(e)}), 500
130
+
131
+ @scanner_bp.route('/progress', methods=['GET'])
132
+ @cross_origin()
133
+ def get_progress():
134
+ global current_scanner
135
+
136
+ if current_scanner is None:
137
+ return jsonify({'progress': 0, 'total': 0})
138
+
139
+ return jsonify({
140
+ 'progress': current_scanner.progress,
141
+ 'total': current_scanner.total_ports,
142
+ 'percentage': round((current_scanner.progress / current_scanner.total_ports) * 100, 2) if current_scanner.total_ports > 0 else 0
143
+ })
144
+
145
+ @scanner_bp.route('/common-ports', methods=['GET'])
146
+ @cross_origin()
147
+ def get_common_ports():
148
+ common_ports = {
149
+ 'web': [80, 443, 8080, 8443, 3000, 8000],
150
+ 'mail': [25, 110, 143, 993, 995, 587],
151
+ 'file': [21, 22, 23, 69, 115, 2049],
152
+ 'database': [1433, 1521, 3306, 5432, 6379, 27017],
153
+ 'remote': [22, 23, 3389, 5900, 5901],
154
+ 'other': [53, 67, 68, 123, 161, 162, 389, 636, 1723]
155
+ }
156
+
157
+ return jsonify(common_ports)
158
+
159
+ @scanner_bp.route('/ping', methods=['POST'])
160
+ @cross_origin()
161
+ def ping_host():
162
+ try:
163
+ data = request.get_json()
164
+ target_ip = data.get('ip')
165
+
166
+ if not target_ip:
167
+ return jsonify({'error': 'IP address is required'}), 400
168
+
169
+ # Simple connectivity test using socket
170
+ try:
171
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
172
+ sock.settimeout(3)
173
+ result = sock.connect_ex((target_ip, 80)) # Try port 80
174
+ sock.close()
175
+
176
+ if result == 0:
177
+ status = 'reachable'
178
+ else:
179
+ # Try another common port
180
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
181
+ sock.settimeout(3)
182
+ result = sock.connect_ex((target_ip, 443)) # Try port 443
183
+ sock.close()
184
+ status = 'reachable' if result == 0 else 'unreachable'
185
+
186
+ except Exception:
187
+ status = 'unreachable'
188
+
189
+ return jsonify({
190
+ 'ip': target_ip,
191
+ 'status': status
192
+ })
193
+
194
+ except Exception as e:
195
+ return jsonify({'error': str(e)}), 500
196
+
src/routes/user.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Blueprint, jsonify, request
2
+ from src.models.user import User, db
3
+
4
+ user_bp = Blueprint('user', __name__)
5
+
6
+ @user_bp.route('/users', methods=['GET'])
7
+ def get_users():
8
+ users = User.query.all()
9
+ return jsonify([user.to_dict() for user in users])
10
+
11
+ @user_bp.route('/users', methods=['POST'])
12
+ def create_user():
13
+
14
+ data = request.json
15
+ user = User(username=data['username'], email=data['email'])
16
+ db.session.add(user)
17
+ db.session.commit()
18
+ return jsonify(user.to_dict()), 201
19
+
20
+ @user_bp.route('/users/<int:user_id>', methods=['GET'])
21
+ def get_user(user_id):
22
+ user = User.query.get_or_404(user_id)
23
+ return jsonify(user.to_dict())
24
+
25
+ @user_bp.route('/users/<int:user_id>', methods=['PUT'])
26
+ def update_user(user_id):
27
+ user = User.query.get_or_404(user_id)
28
+ data = request.json
29
+ user.username = data.get('username', user.username)
30
+ user.email = data.get('email', user.email)
31
+ db.session.commit()
32
+ return jsonify(user.to_dict())
33
+
34
+ @user_bp.route('/users/<int:user_id>', methods=['DELETE'])
35
+ def delete_user(user_id):
36
+ user = User.query.get_or_404(user_id)
37
+ db.session.delete(user)
38
+ db.session.commit()
39
+ return '', 204
src/static/favicon.ico ADDED
src/static/index.html ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Port Scanner - Network Security Tool</title>
7
+ <link rel="stylesheet" href="styles.css">
8
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
9
+ </head>
10
+ <body>
11
+ <div class="container">
12
+ <header class="header">
13
+ <div class="header-content">
14
+ <h1><i class="fas fa-network-wired"></i> Port Scanner</h1>
15
+ <p>Professional network port scanning tool</p>
16
+ </div>
17
+ </header>
18
+
19
+ <main class="main-content">
20
+ <div class="scanner-form">
21
+ <div class="form-group">
22
+ <label for="target-ip">
23
+ <i class="fas fa-globe"></i> Target IP Address
24
+ </label>
25
+ <input type="text" id="target-ip" placeholder="e.g., 192.168.1.1 or 8.8.8.8" required>
26
+ <button id="ping-btn" class="ping-btn" title="Test connectivity">
27
+ <i class="fas fa-satellite-dish"></i> Ping
28
+ </button>
29
+ </div>
30
+
31
+ <div class="form-row">
32
+ <div class="form-group">
33
+ <label for="start-port">
34
+ <i class="fas fa-play"></i> Start Port
35
+ </label>
36
+ <input type="number" id="start-port" value="1" min="1" max="65535">
37
+ </div>
38
+ <div class="form-group">
39
+ <label for="end-port">
40
+ <i class="fas fa-stop"></i> End Port
41
+ </label>
42
+ <input type="number" id="end-port" value="1000" min="1" max="65535">
43
+ </div>
44
+ </div>
45
+
46
+ <div class="form-row">
47
+ <div class="form-group">
48
+ <label for="timeout">
49
+ <i class="fas fa-clock"></i> Timeout (seconds)
50
+ </label>
51
+ <input type="number" id="timeout" value="1" min="0.1" max="10" step="0.1">
52
+ </div>
53
+ <div class="form-group">
54
+ <label for="threads">
55
+ <i class="fas fa-layer-group"></i> Threads
56
+ </label>
57
+ <input type="number" id="threads" value="100" min="1" max="500">
58
+ </div>
59
+ </div>
60
+
61
+ <div class="preset-ports">
62
+ <label>Quick Presets:</label>
63
+ <div class="preset-buttons">
64
+ <button class="preset-btn" data-start="1" data-end="1000">Common (1-1000)</button>
65
+ <button class="preset-btn" data-start="1" data-end="65535">All Ports</button>
66
+ <button class="preset-btn" data-start="80" data-end="443">Web (80-443)</button>
67
+ <button class="preset-btn" data-start="20" data-end="25">FTP/SSH (20-25)</button>
68
+ </div>
69
+ </div>
70
+
71
+ <button id="scan-btn" class="scan-btn">
72
+ <i class="fas fa-search"></i> Start Scan
73
+ </button>
74
+ </div>
75
+
76
+ <div class="progress-section" id="progress-section" style="display: none;">
77
+ <div class="progress-bar">
78
+ <div class="progress-fill" id="progress-fill"></div>
79
+ </div>
80
+ <div class="progress-text" id="progress-text">Preparing scan...</div>
81
+ </div>
82
+
83
+ <div class="results-section" id="results-section" style="display: none;">
84
+ <div class="results-header">
85
+ <h2><i class="fas fa-list-ul"></i> Scan Results</h2>
86
+ <div class="results-summary" id="results-summary"></div>
87
+ </div>
88
+
89
+ <div class="results-actions">
90
+ <button id="export-btn" class="export-btn">
91
+ <i class="fas fa-download"></i> Export Results
92
+ </button>
93
+ <button id="clear-btn" class="clear-btn">
94
+ <i class="fas fa-trash"></i> Clear Results
95
+ </button>
96
+ </div>
97
+
98
+ <div class="results-table-container">
99
+ <table class="results-table" id="results-table">
100
+ <thead>
101
+ <tr>
102
+ <th>Port</th>
103
+ <th>Status</th>
104
+ <th>Service</th>
105
+ <th>Protocol</th>
106
+ </tr>
107
+ </thead>
108
+ <tbody id="results-tbody">
109
+ </tbody>
110
+ </table>
111
+ </div>
112
+ </div>
113
+
114
+ <div class="info-section">
115
+ <div class="info-card">
116
+ <h3><i class="fas fa-info-circle"></i> About Port Scanning</h3>
117
+ <p>Port scanning is a method for determining which ports on a network are open and could be receiving or sending data. This tool uses TCP connect scanning to test connectivity to specific ports on target systems.</p>
118
+
119
+ <div class="common-ports">
120
+ <h4>Common Ports:</h4>
121
+ <div class="port-list">
122
+ <span class="port-item">22 (SSH)</span>
123
+ <span class="port-item">23 (Telnet)</span>
124
+ <span class="port-item">25 (SMTP)</span>
125
+ <span class="port-item">53 (DNS)</span>
126
+ <span class="port-item">80 (HTTP)</span>
127
+ <span class="port-item">110 (POP3)</span>
128
+ <span class="port-item">143 (IMAP)</span>
129
+ <span class="port-item">443 (HTTPS)</span>
130
+ <span class="port-item">993 (IMAPS)</span>
131
+ <span class="port-item">995 (POP3S)</span>
132
+ </div>
133
+ </div>
134
+ </div>
135
+ </div>
136
+ </main>
137
+
138
+ <footer class="footer">
139
+ <p>&copy; 2024 Port Scanner Tool. Use responsibly and only on networks you own or have permission to test.</p>
140
+ </footer>
141
+ </div>
142
+
143
+ <div id="notification" class="notification"></div>
144
+
145
+ <script src="script.js"></script>
146
+ </body>
147
+ </html>
148
+
src/static/script.js ADDED
@@ -0,0 +1,302 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class PortScanner {
2
+ constructor() {
3
+ this.isScanning = false;
4
+ this.progressInterval = null;
5
+ this.initializeEventListeners();
6
+ }
7
+
8
+ initializeEventListeners() {
9
+ // Form elements
10
+ this.targetIpInput = document.getElementById('target-ip');
11
+ this.startPortInput = document.getElementById('start-port');
12
+ this.endPortInput = document.getElementById('end-port');
13
+ this.timeoutInput = document.getElementById('timeout');
14
+ this.threadsInput = document.getElementById('threads');
15
+
16
+ // Buttons
17
+ this.scanBtn = document.getElementById('scan-btn');
18
+ this.pingBtn = document.getElementById('ping-btn');
19
+ this.exportBtn = document.getElementById('export-btn');
20
+ this.clearBtn = document.getElementById('clear-btn');
21
+
22
+ // Sections
23
+ this.progressSection = document.getElementById('progress-section');
24
+ this.resultsSection = document.getElementById('results-section');
25
+
26
+ // Event listeners
27
+ this.scanBtn.addEventListener('click', () => this.startScan());
28
+ this.pingBtn.addEventListener('click', () => this.pingHost());
29
+ this.exportBtn.addEventListener('click', () => this.exportResults());
30
+ this.clearBtn.addEventListener('click', () => this.clearResults());
31
+
32
+ // Preset buttons
33
+ document.querySelectorAll('.preset-btn').forEach(btn => {
34
+ btn.addEventListener('click', (e) => {
35
+ const start = e.target.dataset.start;
36
+ const end = e.target.dataset.end;
37
+ this.startPortInput.value = start;
38
+ this.endPortInput.value = end;
39
+ });
40
+ });
41
+
42
+ // Form validation
43
+ this.targetIpInput.addEventListener('input', () => this.validateForm());
44
+ this.startPortInput.addEventListener('input', () => this.validateForm());
45
+ this.endPortInput.addEventListener('input', () => this.validateForm());
46
+ }
47
+
48
+ validateForm() {
49
+ const ip = this.targetIpInput.value.trim();
50
+ const startPort = parseInt(this.startPortInput.value);
51
+ const endPort = parseInt(this.endPortInput.value);
52
+
53
+ let isValid = true;
54
+
55
+ // IP validation
56
+ if (!ip || !this.isValidIP(ip)) {
57
+ isValid = false;
58
+ }
59
+
60
+ // Port validation
61
+ if (startPort < 1 || endPort > 65535 || startPort > endPort) {
62
+ isValid = false;
63
+ }
64
+
65
+ this.scanBtn.disabled = !isValid || this.isScanning;
66
+ return isValid;
67
+ }
68
+
69
+ isValidIP(ip) {
70
+ const ipRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
71
+ return ipRegex.test(ip);
72
+ }
73
+
74
+ async pingHost() {
75
+ const ip = this.targetIpInput.value.trim();
76
+
77
+ if (!ip || !this.isValidIP(ip)) {
78
+ this.showNotification('Please enter a valid IP address', 'error');
79
+ return;
80
+ }
81
+
82
+ this.pingBtn.disabled = true;
83
+ this.pingBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Pinging...';
84
+
85
+ try {
86
+ const response = await fetch('/api/ping', {
87
+ method: 'POST',
88
+ headers: {
89
+ 'Content-Type': 'application/json',
90
+ },
91
+ body: JSON.stringify({ ip: ip })
92
+ });
93
+
94
+ const data = await response.json();
95
+
96
+ if (response.ok) {
97
+ const status = data.status === 'reachable' ? 'Host is reachable' : 'Host appears unreachable';
98
+ const type = data.status === 'reachable' ? 'success' : 'error';
99
+ this.showNotification(status, type);
100
+ } else {
101
+ this.showNotification(data.error || 'Ping failed', 'error');
102
+ }
103
+ } catch (error) {
104
+ this.showNotification('Network error during ping', 'error');
105
+ } finally {
106
+ this.pingBtn.disabled = false;
107
+ this.pingBtn.innerHTML = '<i class="fas fa-satellite-dish"></i> Ping';
108
+ }
109
+ }
110
+
111
+ async startScan() {
112
+ if (!this.validateForm()) {
113
+ this.showNotification('Please check your input values', 'error');
114
+ return;
115
+ }
116
+
117
+ const scanData = {
118
+ ip: this.targetIpInput.value.trim(),
119
+ start_port: parseInt(this.startPortInput.value),
120
+ end_port: parseInt(this.endPortInput.value),
121
+ timeout: parseFloat(this.timeoutInput.value),
122
+ threads: parseInt(this.threadsInput.value)
123
+ };
124
+
125
+ // Check port range limit
126
+ const portRange = scanData.end_port - scanData.start_port + 1;
127
+ if (portRange > 10000) {
128
+ this.showNotification('Port range too large (max 10000 ports)', 'error');
129
+ return;
130
+ }
131
+
132
+ this.isScanning = true;
133
+ this.scanBtn.disabled = true;
134
+ this.scanBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Scanning...';
135
+
136
+ // Show progress section
137
+ this.progressSection.style.display = 'block';
138
+ this.resultsSection.style.display = 'none';
139
+
140
+ // Start progress monitoring
141
+ this.startProgressMonitoring();
142
+
143
+ try {
144
+ const response = await fetch('/api/scan', {
145
+ method: 'POST',
146
+ headers: {
147
+ 'Content-Type': 'application/json',
148
+ },
149
+ body: JSON.stringify(scanData)
150
+ });
151
+
152
+ const data = await response.json();
153
+
154
+ if (response.ok) {
155
+ this.displayResults(data);
156
+ this.showNotification(`Scan completed! Found ${data.total_open} open ports`, 'success');
157
+ } else {
158
+ this.showNotification(data.error || 'Scan failed', 'error');
159
+ }
160
+ } catch (error) {
161
+ this.showNotification('Network error during scan', 'error');
162
+ } finally {
163
+ this.stopProgressMonitoring();
164
+ this.isScanning = false;
165
+ this.scanBtn.disabled = false;
166
+ this.scanBtn.innerHTML = '<i class="fas fa-search"></i> Start Scan';
167
+ this.progressSection.style.display = 'none';
168
+ }
169
+ }
170
+
171
+ startProgressMonitoring() {
172
+ this.progressInterval = setInterval(async () => {
173
+ try {
174
+ const response = await fetch('/api/progress');
175
+ const data = await response.json();
176
+
177
+ const progressFill = document.getElementById('progress-fill');
178
+ const progressText = document.getElementById('progress-text');
179
+
180
+ progressFill.style.width = `${data.percentage}%`;
181
+ progressText.textContent = `Scanning... ${data.progress}/${data.total} ports (${data.percentage}%)`;
182
+ } catch (error) {
183
+ console.error('Error fetching progress:', error);
184
+ }
185
+ }, 500);
186
+ }
187
+
188
+ stopProgressMonitoring() {
189
+ if (this.progressInterval) {
190
+ clearInterval(this.progressInterval);
191
+ this.progressInterval = null;
192
+ }
193
+ }
194
+
195
+ displayResults(data) {
196
+ const resultsSection = document.getElementById('results-section');
197
+ const resultsSummary = document.getElementById('results-summary');
198
+ const resultsTableBody = document.getElementById('results-tbody');
199
+
200
+ // Update summary
201
+ resultsSummary.innerHTML = `
202
+ <strong>Target:</strong> ${data.target_ip} |
203
+ <strong>Range:</strong> ${data.scan_range} |
204
+ <strong>Open Ports:</strong> ${data.total_open}/${data.total_scanned} |
205
+ <strong>Duration:</strong> ${data.scan_duration}s
206
+ `;
207
+
208
+ // Clear previous results
209
+ resultsTableBody.innerHTML = '';
210
+
211
+ // Add results to table
212
+ if (data.open_ports && data.open_ports.length > 0) {
213
+ data.open_ports.forEach(port => {
214
+ const row = document.createElement('tr');
215
+ row.innerHTML = `
216
+ <td><strong>${port.port}</strong></td>
217
+ <td><span class="status-${port.status}">${port.status.toUpperCase()}</span></td>
218
+ <td>${port.service || 'unknown'}</td>
219
+ <td>TCP</td>
220
+ `;
221
+ resultsTableBody.appendChild(row);
222
+ });
223
+ } else {
224
+ const row = document.createElement('tr');
225
+ row.innerHTML = '<td colspan="4" style="text-align: center; color: #666;">No open ports found</td>';
226
+ resultsTableBody.appendChild(row);
227
+ }
228
+
229
+ // Show results section
230
+ resultsSection.style.display = 'block';
231
+
232
+ // Store results for export
233
+ this.lastScanResults = data;
234
+ }
235
+
236
+ exportResults() {
237
+ if (!this.lastScanResults) {
238
+ this.showNotification('No results to export', 'error');
239
+ return;
240
+ }
241
+
242
+ const data = this.lastScanResults;
243
+ let csvContent = 'Port,Status,Service,Protocol\n';
244
+
245
+ if (data.open_ports && data.open_ports.length > 0) {
246
+ data.open_ports.forEach(port => {
247
+ csvContent += `${port.port},${port.status},${port.service || 'unknown'},TCP\n`;
248
+ });
249
+ }
250
+
251
+ // Create and download file
252
+ const blob = new Blob([csvContent], { type: 'text/csv' });
253
+ const url = window.URL.createObjectURL(blob);
254
+ const a = document.createElement('a');
255
+ a.href = url;
256
+ a.download = `port_scan_${data.target_ip}_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.csv`;
257
+ document.body.appendChild(a);
258
+ a.click();
259
+ document.body.removeChild(a);
260
+ window.URL.revokeObjectURL(url);
261
+
262
+ this.showNotification('Results exported successfully', 'success');
263
+ }
264
+
265
+ clearResults() {
266
+ this.resultsSection.style.display = 'none';
267
+ this.lastScanResults = null;
268
+ this.showNotification('Results cleared', 'info');
269
+ }
270
+
271
+ showNotification(message, type = 'info') {
272
+ const notification = document.getElementById('notification');
273
+ notification.textContent = message;
274
+ notification.className = `notification ${type}`;
275
+ notification.classList.add('show');
276
+
277
+ setTimeout(() => {
278
+ notification.classList.remove('show');
279
+ }, 4000);
280
+ }
281
+ }
282
+
283
+ // Initialize the application when DOM is loaded
284
+ document.addEventListener('DOMContentLoaded', () => {
285
+ new PortScanner();
286
+ });
287
+
288
+ // Add some utility functions for better UX
289
+ document.addEventListener('keydown', (e) => {
290
+ if (e.key === 'Enter' && e.target.tagName === 'INPUT') {
291
+ const scanner = new PortScanner();
292
+ if (scanner.validateForm()) {
293
+ scanner.startScan();
294
+ }
295
+ }
296
+ });
297
+
298
+ // Auto-focus on IP input when page loads
299
+ window.addEventListener('load', () => {
300
+ document.getElementById('target-ip').focus();
301
+ });
302
+
src/static/styles.css ADDED
@@ -0,0 +1,445 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
9
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
10
+ min-height: 100vh;
11
+ color: #333;
12
+ }
13
+
14
+ .container {
15
+ max-width: 1200px;
16
+ margin: 0 auto;
17
+ padding: 20px;
18
+ }
19
+
20
+ .header {
21
+ text-align: center;
22
+ margin-bottom: 40px;
23
+ color: white;
24
+ }
25
+
26
+ .header-content h1 {
27
+ font-size: 3rem;
28
+ margin-bottom: 10px;
29
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
30
+ }
31
+
32
+ .header-content p {
33
+ font-size: 1.2rem;
34
+ opacity: 0.9;
35
+ }
36
+
37
+ .main-content {
38
+ background: white;
39
+ border-radius: 20px;
40
+ padding: 40px;
41
+ box-shadow: 0 20px 40px rgba(0,0,0,0.1);
42
+ margin-bottom: 30px;
43
+ }
44
+
45
+ .scanner-form {
46
+ margin-bottom: 30px;
47
+ }
48
+
49
+ .form-group {
50
+ margin-bottom: 25px;
51
+ position: relative;
52
+ }
53
+
54
+ .form-group label {
55
+ display: block;
56
+ margin-bottom: 8px;
57
+ font-weight: 600;
58
+ color: #555;
59
+ font-size: 1rem;
60
+ }
61
+
62
+ .form-group label i {
63
+ margin-right: 8px;
64
+ color: #667eea;
65
+ }
66
+
67
+ .form-group input {
68
+ width: 100%;
69
+ padding: 15px;
70
+ border: 2px solid #e1e5e9;
71
+ border-radius: 10px;
72
+ font-size: 1rem;
73
+ transition: all 0.3s ease;
74
+ background: #f8f9fa;
75
+ }
76
+
77
+ .form-group input:focus {
78
+ outline: none;
79
+ border-color: #667eea;
80
+ background: white;
81
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
82
+ }
83
+
84
+ .form-row {
85
+ display: grid;
86
+ grid-template-columns: 1fr 1fr;
87
+ gap: 20px;
88
+ }
89
+
90
+ .ping-btn {
91
+ position: absolute;
92
+ right: 5px;
93
+ top: 35px;
94
+ background: #28a745;
95
+ color: white;
96
+ border: none;
97
+ padding: 10px 15px;
98
+ border-radius: 8px;
99
+ cursor: pointer;
100
+ transition: all 0.3s ease;
101
+ font-size: 0.9rem;
102
+ }
103
+
104
+ .ping-btn:hover {
105
+ background: #218838;
106
+ transform: translateY(-2px);
107
+ }
108
+
109
+ .preset-ports {
110
+ margin-bottom: 30px;
111
+ }
112
+
113
+ .preset-ports label {
114
+ display: block;
115
+ margin-bottom: 15px;
116
+ font-weight: 600;
117
+ color: #555;
118
+ }
119
+
120
+ .preset-buttons {
121
+ display: flex;
122
+ gap: 10px;
123
+ flex-wrap: wrap;
124
+ }
125
+
126
+ .preset-btn {
127
+ background: #f8f9fa;
128
+ border: 2px solid #e1e5e9;
129
+ padding: 10px 20px;
130
+ border-radius: 25px;
131
+ cursor: pointer;
132
+ transition: all 0.3s ease;
133
+ font-size: 0.9rem;
134
+ font-weight: 500;
135
+ }
136
+
137
+ .preset-btn:hover {
138
+ background: #667eea;
139
+ color: white;
140
+ border-color: #667eea;
141
+ transform: translateY(-2px);
142
+ }
143
+
144
+ .scan-btn {
145
+ width: 100%;
146
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
147
+ color: white;
148
+ border: none;
149
+ padding: 18px;
150
+ border-radius: 12px;
151
+ font-size: 1.2rem;
152
+ font-weight: 600;
153
+ cursor: pointer;
154
+ transition: all 0.3s ease;
155
+ text-transform: uppercase;
156
+ letter-spacing: 1px;
157
+ }
158
+
159
+ .scan-btn:hover {
160
+ transform: translateY(-3px);
161
+ box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3);
162
+ }
163
+
164
+ .scan-btn:disabled {
165
+ opacity: 0.6;
166
+ cursor: not-allowed;
167
+ transform: none;
168
+ }
169
+
170
+ .progress-section {
171
+ margin: 30px 0;
172
+ text-align: center;
173
+ }
174
+
175
+ .progress-bar {
176
+ width: 100%;
177
+ height: 8px;
178
+ background: #e1e5e9;
179
+ border-radius: 4px;
180
+ overflow: hidden;
181
+ margin-bottom: 15px;
182
+ }
183
+
184
+ .progress-fill {
185
+ height: 100%;
186
+ background: linear-gradient(90deg, #667eea, #764ba2);
187
+ width: 0%;
188
+ transition: width 0.3s ease;
189
+ border-radius: 4px;
190
+ }
191
+
192
+ .progress-text {
193
+ font-size: 1rem;
194
+ color: #666;
195
+ font-weight: 500;
196
+ }
197
+
198
+ .results-section {
199
+ margin-top: 40px;
200
+ }
201
+
202
+ .results-header {
203
+ display: flex;
204
+ justify-content: space-between;
205
+ align-items: center;
206
+ margin-bottom: 25px;
207
+ flex-wrap: wrap;
208
+ gap: 15px;
209
+ }
210
+
211
+ .results-header h2 {
212
+ color: #333;
213
+ font-size: 1.8rem;
214
+ }
215
+
216
+ .results-summary {
217
+ background: #f8f9fa;
218
+ padding: 15px 20px;
219
+ border-radius: 10px;
220
+ font-weight: 500;
221
+ color: #555;
222
+ }
223
+
224
+ .results-actions {
225
+ display: flex;
226
+ gap: 15px;
227
+ margin-bottom: 25px;
228
+ flex-wrap: wrap;
229
+ }
230
+
231
+ .export-btn, .clear-btn {
232
+ padding: 12px 24px;
233
+ border: none;
234
+ border-radius: 8px;
235
+ cursor: pointer;
236
+ font-weight: 500;
237
+ transition: all 0.3s ease;
238
+ }
239
+
240
+ .export-btn {
241
+ background: #28a745;
242
+ color: white;
243
+ }
244
+
245
+ .export-btn:hover {
246
+ background: #218838;
247
+ transform: translateY(-2px);
248
+ }
249
+
250
+ .clear-btn {
251
+ background: #dc3545;
252
+ color: white;
253
+ }
254
+
255
+ .clear-btn:hover {
256
+ background: #c82333;
257
+ transform: translateY(-2px);
258
+ }
259
+
260
+ .results-table-container {
261
+ overflow-x: auto;
262
+ border-radius: 12px;
263
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
264
+ }
265
+
266
+ .results-table {
267
+ width: 100%;
268
+ border-collapse: collapse;
269
+ background: white;
270
+ }
271
+
272
+ .results-table th {
273
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
274
+ color: white;
275
+ padding: 15px;
276
+ text-align: left;
277
+ font-weight: 600;
278
+ text-transform: uppercase;
279
+ letter-spacing: 0.5px;
280
+ }
281
+
282
+ .results-table td {
283
+ padding: 15px;
284
+ border-bottom: 1px solid #e1e5e9;
285
+ transition: background 0.3s ease;
286
+ }
287
+
288
+ .results-table tbody tr:hover {
289
+ background: #f8f9fa;
290
+ }
291
+
292
+ .status-open {
293
+ color: #28a745;
294
+ font-weight: 600;
295
+ }
296
+
297
+ .status-closed {
298
+ color: #dc3545;
299
+ font-weight: 600;
300
+ }
301
+
302
+ .info-section {
303
+ margin-top: 40px;
304
+ }
305
+
306
+ .info-card {
307
+ background: #f8f9fa;
308
+ padding: 30px;
309
+ border-radius: 15px;
310
+ border-left: 5px solid #667eea;
311
+ }
312
+
313
+ .info-card h3 {
314
+ color: #333;
315
+ margin-bottom: 15px;
316
+ font-size: 1.4rem;
317
+ }
318
+
319
+ .info-card p {
320
+ color: #666;
321
+ line-height: 1.6;
322
+ margin-bottom: 20px;
323
+ }
324
+
325
+ .common-ports h4 {
326
+ color: #555;
327
+ margin-bottom: 15px;
328
+ font-size: 1.1rem;
329
+ }
330
+
331
+ .port-list {
332
+ display: flex;
333
+ flex-wrap: wrap;
334
+ gap: 10px;
335
+ }
336
+
337
+ .port-item {
338
+ background: white;
339
+ padding: 8px 15px;
340
+ border-radius: 20px;
341
+ font-size: 0.9rem;
342
+ font-weight: 500;
343
+ color: #667eea;
344
+ border: 2px solid #e1e5e9;
345
+ transition: all 0.3s ease;
346
+ }
347
+
348
+ .port-item:hover {
349
+ background: #667eea;
350
+ color: white;
351
+ transform: translateY(-2px);
352
+ }
353
+
354
+ .footer {
355
+ text-align: center;
356
+ color: white;
357
+ opacity: 0.8;
358
+ font-size: 0.9rem;
359
+ margin-top: 30px;
360
+ }
361
+
362
+ .notification {
363
+ position: fixed;
364
+ top: 20px;
365
+ right: 20px;
366
+ padding: 15px 25px;
367
+ border-radius: 8px;
368
+ color: white;
369
+ font-weight: 500;
370
+ z-index: 1000;
371
+ transform: translateX(400px);
372
+ transition: transform 0.3s ease;
373
+ max-width: 300px;
374
+ }
375
+
376
+ .notification.show {
377
+ transform: translateX(0);
378
+ }
379
+
380
+ .notification.success {
381
+ background: #28a745;
382
+ }
383
+
384
+ .notification.error {
385
+ background: #dc3545;
386
+ }
387
+
388
+ .notification.info {
389
+ background: #17a2b8;
390
+ }
391
+
392
+ /* Responsive Design */
393
+ @media (max-width: 768px) {
394
+ .container {
395
+ padding: 15px;
396
+ }
397
+
398
+ .header-content h1 {
399
+ font-size: 2rem;
400
+ }
401
+
402
+ .main-content {
403
+ padding: 25px;
404
+ }
405
+
406
+ .form-row {
407
+ grid-template-columns: 1fr;
408
+ }
409
+
410
+ .preset-buttons {
411
+ justify-content: center;
412
+ }
413
+
414
+ .results-header {
415
+ flex-direction: column;
416
+ align-items: stretch;
417
+ }
418
+
419
+ .results-actions {
420
+ justify-content: center;
421
+ }
422
+
423
+ .ping-btn {
424
+ position: static;
425
+ margin-top: 10px;
426
+ width: 100%;
427
+ }
428
+ }
429
+
430
+ @media (max-width: 480px) {
431
+ .header-content h1 {
432
+ font-size: 1.8rem;
433
+ }
434
+
435
+ .main-content {
436
+ padding: 20px;
437
+ }
438
+
439
+ .results-table th,
440
+ .results-table td {
441
+ padding: 10px 8px;
442
+ font-size: 0.9rem;
443
+ }
444
+ }
445
+