mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-03 18:22:57 +00:00
hot fix #1
This commit is contained in:
@@ -0,0 +1,479 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Browser-based Terminal Tamagotchi
|
||||
Terminal interface in browser with real-time updates
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import random
|
||||
import subprocess
|
||||
from datetime import datetime
|
||||
from flask import Flask, render_template, jsonify
|
||||
from flask_socketio import SocketIO, emit
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SECRET_KEY'] = 'terminal-tamagotchi'
|
||||
socketio = SocketIO(app, cors_allowed_origins="*")
|
||||
|
||||
class BrowserTerminalTamagotchi:
|
||||
def __init__(self, project_name="MyClub"):
|
||||
self.project_name = project_name
|
||||
self.stats_file = os.path.join(os.path.dirname(__file__), 'stats.md')
|
||||
self.stats = {}
|
||||
self.last_check = time.time()
|
||||
self.typing_detected = False
|
||||
self.recent_changes = 0
|
||||
|
||||
# Pet state
|
||||
self.pet = {
|
||||
'name': project_name.lower() + '-chi',
|
||||
'mood': 'content',
|
||||
'evolution': 'baby',
|
||||
'level': 1,
|
||||
'energy': 100,
|
||||
'hunger': 0,
|
||||
'coding': 0,
|
||||
'face': '(o.o)',
|
||||
'activity': 'idle'
|
||||
}
|
||||
|
||||
# ASCII faces
|
||||
self.faces = {
|
||||
'baby': {
|
||||
'content': ['(o.o)', '(O.o)', '(o.O)'],
|
||||
'coding': ['(>.<)', '(>_<)', '(>.>_)'],
|
||||
'happy': ['(^.^)', '(^.^)', '(^o^)'],
|
||||
'tired': ['(-_-)', '(=_=)', '(x_x)']
|
||||
},
|
||||
'child': {
|
||||
'content': ['(o.o)', '(^.^)', '(•.•)'],
|
||||
'coding': ['(>_<)', '(>o<)', '(x.x)'],
|
||||
'happy': ['(^.^)', '(◕‿◕)', '(´•ᴗ•`)'],
|
||||
'tired': ['(-_-)', '(¬_¬)', '(=_=)']
|
||||
},
|
||||
'teen': {
|
||||
'content': ['(o.o)', '(^.^)', '(•_•)'],
|
||||
'coding': ['(>_<)', '(x_x)', '(>.<)'],
|
||||
'happy': ['(^.^)', '(◕‿◕)', '(´•ᴗ•`)'],
|
||||
'tired': ['(-_-)', '(¬_¬)', '(=_=)']
|
||||
},
|
||||
'adult': {
|
||||
'content': ['(o.o)', '(^.^)', '(•_•)'],
|
||||
'coding': ['(>_<)', '(x_x)', '(>.<)'],
|
||||
'happy': ['(^.^)', '(◕‿◕)', '(´•ᴗ•`)'],
|
||||
'tired': ['(-_-)', '(¬_¬)', '(=_=)']
|
||||
},
|
||||
'master': {
|
||||
'content': ['(o.o)', '(^.^)', '(•_•)'],
|
||||
'coding': ['(>_<)', '(x_x)', '(>.<)'],
|
||||
'happy': ['(^.^)', '(◕‿◕)', '(´•ᴗ•`)'],
|
||||
'tired': ['(-_-)', '(¬_¬)', '(=_=)']
|
||||
}
|
||||
}
|
||||
|
||||
def get_git_status(self):
|
||||
"""Get git repository status"""
|
||||
try:
|
||||
project_root = os.path.dirname(os.path.dirname(self.stats_file))
|
||||
|
||||
# Check if we're in a git repository
|
||||
git_dir = os.path.join(project_root, '.git')
|
||||
if not os.path.exists(git_dir):
|
||||
return {'status': 'no_repo', 'branch': None, 'changes': []}
|
||||
|
||||
# Try different git status commands to get all files
|
||||
commands = [
|
||||
['git', 'status', '--porcelain', '--ignored=traditional'],
|
||||
['git', 'status', '--porcelain', '--ignored'],
|
||||
['git', 'status', '--porcelain']
|
||||
]
|
||||
|
||||
result = None
|
||||
for cmd in commands:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
cwd=project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
)
|
||||
if result.returncode == 0:
|
||||
break
|
||||
except:
|
||||
continue
|
||||
|
||||
if not result or result.returncode != 0:
|
||||
return {'status': 'error', 'branch': None, 'changes': []}
|
||||
|
||||
# Get current branch
|
||||
branch_result = subprocess.run(
|
||||
['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
|
||||
cwd=project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5
|
||||
)
|
||||
|
||||
current_branch = branch_result.stdout.strip() if branch_result.returncode == 0 else 'unknown'
|
||||
|
||||
# Parse git status output
|
||||
changes = []
|
||||
for line in result.stdout.strip().split('\n'):
|
||||
if line and len(line) >= 3:
|
||||
status_code = line[:2]
|
||||
file_path = line[3:]
|
||||
|
||||
if status_code == '??':
|
||||
change_type = 'untracked'
|
||||
elif status_code == '!!':
|
||||
change_type = 'ignored'
|
||||
elif status_code == ' M':
|
||||
change_type = 'modified'
|
||||
elif status_code == 'A':
|
||||
change_type = 'added'
|
||||
elif status_code == 'D':
|
||||
change_type = 'deleted'
|
||||
elif status_code[0] in ['M', 'A', 'D']:
|
||||
change_type = 'staged'
|
||||
else:
|
||||
change_type = 'changed'
|
||||
|
||||
changes.append({
|
||||
'file': file_path,
|
||||
'type': change_type,
|
||||
'status': status_code
|
||||
})
|
||||
|
||||
# Debug: print what we found
|
||||
print(f"Git status found {len(changes)} changes")
|
||||
|
||||
return {
|
||||
'status': 'clean' if not changes else 'dirty',
|
||||
'branch': current_branch,
|
||||
'changes': changes,
|
||||
'total_changes': len(changes)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"Git status error: {e}") # Debug output
|
||||
return {'status': 'error', 'branch': None, 'changes': []}
|
||||
|
||||
def scan_project_files(self):
|
||||
"""Scan project files - using exact same logic as project_stats.py"""
|
||||
try:
|
||||
project_root = os.path.dirname(os.path.dirname(self.stats_file))
|
||||
current_time = time.time()
|
||||
|
||||
# Use exact same constants as project_stats.py + tamagotchi_env
|
||||
IGNORED_DIRS = {
|
||||
'.git',
|
||||
'.git_backup',
|
||||
'__pycache__',
|
||||
'node_modules',
|
||||
'dist',
|
||||
'build',
|
||||
'.next',
|
||||
'.cache',
|
||||
'.venv',
|
||||
'venv',
|
||||
'tamagotchi_env', # Exclude tamagotchi virtual environment
|
||||
}
|
||||
|
||||
ALLOWED_EXTS = {
|
||||
'.tsx',
|
||||
'.css',
|
||||
'.go',
|
||||
'.ts',
|
||||
'.js',
|
||||
'.html',
|
||||
'.sql',
|
||||
'.py',
|
||||
}
|
||||
|
||||
file_stats = {
|
||||
'directories': 0,
|
||||
'files': 0,
|
||||
'lines': 0,
|
||||
'extensions': {},
|
||||
'recent_changes': 0,
|
||||
'current_file': None, # Track most recently modified file
|
||||
'recent_files': [] # List of recently modified files
|
||||
}
|
||||
|
||||
for current_dir, dirs, files in os.walk(project_root):
|
||||
dirs[:] = [d for d in dirs if d not in IGNORED_DIRS]
|
||||
|
||||
# Only count directories that are NOT the root directory (same as project_stats.py)
|
||||
if current_dir != project_root:
|
||||
file_stats['directories'] += 1
|
||||
|
||||
for name in files:
|
||||
ext = os.path.splitext(name)[1]
|
||||
if ext not in ALLOWED_EXTS:
|
||||
continue
|
||||
|
||||
file_stats['files'] += 1
|
||||
path = os.path.join(current_dir, name)
|
||||
ext_key = ext or '<no_ext>'
|
||||
line_count = 0
|
||||
|
||||
try:
|
||||
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
for _ in f:
|
||||
line_count += 1
|
||||
except Exception:
|
||||
line_count = 0
|
||||
|
||||
file_stats['lines'] += line_count
|
||||
|
||||
if ext_key not in file_stats['extensions']:
|
||||
file_stats['extensions'][ext_key] = {'files': 0, 'lines': 0}
|
||||
file_stats['extensions'][ext_key]['files'] += 1
|
||||
file_stats['extensions'][ext_key]['lines'] += line_count
|
||||
|
||||
# Check for recent changes (last 30 seconds)
|
||||
try:
|
||||
mtime = os.path.getmtime(path)
|
||||
if current_time - mtime < 30:
|
||||
file_stats['recent_changes'] += 1
|
||||
|
||||
# Track the most recently modified file
|
||||
rel_path = os.path.relpath(path, project_root)
|
||||
file_info = {
|
||||
'path': rel_path,
|
||||
'name': name,
|
||||
'ext': ext,
|
||||
'mtime': mtime,
|
||||
'size': os.path.getsize(path)
|
||||
}
|
||||
|
||||
# Update current file (most recent)
|
||||
if not file_stats['current_file'] or mtime > file_stats['current_file']['mtime']:
|
||||
file_stats['current_file'] = file_info
|
||||
|
||||
# Add to recent files list (keep last 5)
|
||||
file_stats['recent_files'].append(file_info)
|
||||
file_stats['recent_files'] = sorted(
|
||||
file_stats['recent_files'],
|
||||
key=lambda x: x['mtime'],
|
||||
reverse=True
|
||||
)[:5]
|
||||
|
||||
# Debug output
|
||||
print(f"Recent change detected: {rel_path} ({ext})")
|
||||
except:
|
||||
pass
|
||||
|
||||
return file_stats
|
||||
|
||||
except Exception as e:
|
||||
return {'directories': 0, 'files': 0, 'lines': 0, 'extensions': {}, 'recent_changes': 0}
|
||||
|
||||
def update_pet_state(self, file_stats):
|
||||
"""Update pet based on file stats"""
|
||||
self.recent_changes = file_stats['recent_changes']
|
||||
self.typing_detected = self.recent_changes > 0
|
||||
|
||||
lines = file_stats['lines']
|
||||
if lines > 200000:
|
||||
self.pet['evolution'] = 'master'
|
||||
elif lines > 100000:
|
||||
self.pet['evolution'] = 'adult'
|
||||
elif lines > 50000:
|
||||
self.pet['evolution'] = 'teen'
|
||||
elif lines > 10000:
|
||||
self.pet['evolution'] = 'child'
|
||||
else:
|
||||
self.pet['evolution'] = 'baby'
|
||||
|
||||
self.pet['level'] = 1 + (file_stats['files'] // 50) + (lines // 10000)
|
||||
|
||||
if self.typing_detected:
|
||||
self.pet['mood'] = 'coding'
|
||||
self.pet['activity'] = 'coding'
|
||||
self.pet['coding'] = min(100, self.pet['coding'] + self.recent_changes * 10)
|
||||
self.pet['energy'] = min(100, self.pet['energy'] + 5)
|
||||
else:
|
||||
self.pet['activity'] = 'idle'
|
||||
self.pet['coding'] = max(0, self.pet['coding'] - 2)
|
||||
|
||||
if self.pet['energy'] > 70:
|
||||
self.pet['mood'] = 'content'
|
||||
elif self.pet['energy'] > 40:
|
||||
self.pet['mood'] = 'tired'
|
||||
else:
|
||||
self.pet['mood'] = 'tired'
|
||||
|
||||
self.pet['hunger'] = min(100, self.pet['hunger'] + 1)
|
||||
self.pet['energy'] = max(0, self.pet['energy'] - 1)
|
||||
|
||||
evolution_faces = self.faces.get(self.pet['evolution'], self.faces['baby'])
|
||||
mood_faces = evolution_faces.get(self.pet['mood'], evolution_faces['content'])
|
||||
self.pet['face'] = random.choice(mood_faces)
|
||||
|
||||
def get_terminal_output(self):
|
||||
"""Generate ultra-compact terminal-style output"""
|
||||
lines = []
|
||||
|
||||
# Header
|
||||
lines.append(f"[{datetime.now().strftime('%H:%M:%S')}] {self.pet['name'].upper()} TERMINAL")
|
||||
lines.append("=" * 50)
|
||||
|
||||
# Pet and status on one line
|
||||
lines.append(f"[{self.pet['face']}] {self.pet['mood'].upper()} | LVL:{self.pet['level']} | {self.pet['evolution'].upper()}")
|
||||
|
||||
# Status bars on one line
|
||||
energy_bar = "#" * int(self.pet['energy']//10) + " " * (10 - int(self.pet['energy']//10))
|
||||
hunger_bar = "#" * int(self.pet['hunger']//10) + " " * (10 - int(self.pet['hunger']//10))
|
||||
coding_bar = "#" * int(self.pet['coding']//10) + " " * (10 - int(self.pet['coding']//10))
|
||||
lines.append(f"E:[{energy_bar}] H:[{hunger_bar}] C:[{coding_bar}]")
|
||||
|
||||
# Current activity (compact)
|
||||
if self.typing_detected and self.stats.get('current_file'):
|
||||
current_file = self.stats['current_file']
|
||||
file_name = current_file['path'].split('/')[-1]
|
||||
lines.append(f"EDITING: {file_name} ({current_file['ext']})")
|
||||
elif self.typing_detected:
|
||||
lines.append(f"ACTIVITY: {self.recent_changes} files changed")
|
||||
else:
|
||||
lines.append(f"Activity: {self.pet['activity'].upper()}")
|
||||
|
||||
# Git status (compact)
|
||||
git_status = self.get_git_status()
|
||||
if git_status['status'] == 'no_repo':
|
||||
lines.append("GIT: Not a git repository")
|
||||
elif git_status['status'] == 'error':
|
||||
lines.append("GIT: Error checking git")
|
||||
elif git_status['status'] == 'clean':
|
||||
lines.append(f"GIT: Clean ({git_status['branch']})")
|
||||
else:
|
||||
untracked = len([c for c in git_status['changes'] if c['type'] == 'untracked'])
|
||||
modified = len([c for c in git_status['changes'] if c['type'] == 'modified'])
|
||||
staged = len([c for c in git_status['changes'] if c['type'] == 'staged'])
|
||||
deleted = len([c for c in git_status['changes'] if c['type'] == 'deleted'])
|
||||
ignored = len([c for c in git_status['changes'] if c['type'] == 'ignored'])
|
||||
|
||||
lines.append(f"GIT: {git_status['total_changes']} files ({git_status['branch']})")
|
||||
lines.append(f" U:{untracked} M:{modified} S:{staged} D:{deleted} I:{ignored}")
|
||||
|
||||
# Recent files (compact)
|
||||
if self.stats.get('recent_files'):
|
||||
recent_names = [f['path'].split('/')[-1] for f in self.stats['recent_files'][:2]]
|
||||
lines.append(f"RECENT: {', '.join(recent_names)}")
|
||||
|
||||
# Project stats (compact)
|
||||
lines.append(f"PROJECT: {self.stats.get('files', 0)} files | {self.stats.get('lines', 0):,} lines")
|
||||
|
||||
# Top languages (compact)
|
||||
extensions = self.stats.get('extensions', {})
|
||||
if extensions:
|
||||
sorted_exts = sorted(extensions.items(), key=lambda x: x[1]['lines'], reverse=True)[:3]
|
||||
top_langs = [f"{ext}({data['files']})" for ext, data in sorted_exts]
|
||||
lines.append(f"LANGUAGES: {', '.join(top_langs)}")
|
||||
|
||||
lines.append("CONTROLS: [f]eed [p]lay [r]efresh [q]uit")
|
||||
lines.append("=" * 50)
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def feed_pet(self):
|
||||
"""Feed the pet"""
|
||||
self.pet['hunger'] = max(0, self.pet['hunger'] - 30)
|
||||
self.pet['energy'] = min(100, self.pet['energy'] + 10)
|
||||
self.pet['mood'] = 'content'
|
||||
|
||||
def play_with_pet(self):
|
||||
"""Play with pet"""
|
||||
self.pet['energy'] = max(0, self.pet['energy'] - 10)
|
||||
self.pet['mood'] = 'content'
|
||||
self.pet['activity'] = 'playing'
|
||||
|
||||
# Global instance
|
||||
tamagotchi = None
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""Main terminal page"""
|
||||
return render_template('terminal.html')
|
||||
|
||||
@app.route('/api/terminal-data')
|
||||
def get_terminal_data():
|
||||
"""Get terminal output as JSON"""
|
||||
return jsonify({
|
||||
'output': tamagotchi.get_terminal_output(),
|
||||
'typing_detected': tamagotchi.typing_detected,
|
||||
'recent_changes': tamagotchi.recent_changes
|
||||
})
|
||||
|
||||
@socketio.on('connect')
|
||||
def handle_connect():
|
||||
"""Handle client connection"""
|
||||
emit('terminal_update', {
|
||||
'output': tamagotchi.get_terminal_output(),
|
||||
'typing_detected': tamagotchi.typing_detected,
|
||||
'recent_changes': tamagotchi.recent_changes
|
||||
})
|
||||
|
||||
@socketio.on('command')
|
||||
def handle_command(data):
|
||||
"""Handle terminal commands"""
|
||||
cmd = data.get('command', '').lower().strip()
|
||||
|
||||
if cmd == 'f':
|
||||
tamagotchi.feed_pet()
|
||||
print(f"Feed command received - Hunger: {tamagotchi.pet['hunger']}%")
|
||||
elif cmd == 'p':
|
||||
tamagotchi.play_with_pet()
|
||||
print(f"Play command received - Energy: {tamagotchi.pet['energy']}%")
|
||||
elif cmd == 'r':
|
||||
print("Refresh command received - stats refresh automatically")
|
||||
elif cmd == 'q':
|
||||
emit('quit', {'message': 'Terminal session ended'})
|
||||
return
|
||||
|
||||
# Send updated state
|
||||
emit('terminal_update', {
|
||||
'output': tamagotchi.get_terminal_output(),
|
||||
'typing_detected': tamagotchi.typing_detected,
|
||||
'recent_changes': tamagotchi.recent_changes
|
||||
})
|
||||
|
||||
def background_monitor():
|
||||
"""Background thread to monitor files and broadcast updates"""
|
||||
while True:
|
||||
try:
|
||||
# Scan files and update pet
|
||||
file_stats = tamagotchi.scan_project_files()
|
||||
tamagotchi.stats = file_stats
|
||||
tamagotchi.update_pet_state(file_stats)
|
||||
|
||||
# Broadcast update to all clients
|
||||
socketio.emit('terminal_update', {
|
||||
'output': tamagotchi.get_terminal_output(),
|
||||
'typing_detected': tamagotchi.typing_detected,
|
||||
'recent_changes': tamagotchi.recent_changes
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Monitor error: {e}")
|
||||
|
||||
time.sleep(2) # Update every 2 seconds
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Initialize tamagotchi
|
||||
project_name = "MyClub"
|
||||
tamagotchi = BrowserTerminalTamagotchi(project_name)
|
||||
|
||||
# Start background monitoring
|
||||
import threading
|
||||
monitor_thread = threading.Thread(target=background_monitor, daemon=True)
|
||||
monitor_thread.start()
|
||||
|
||||
print("🖥️ Browser Terminal Tamagotchi Starting...")
|
||||
print("🌐 Open your browser to: http://localhost:5000")
|
||||
print("⌨️ Terminal interface in browser!")
|
||||
print("🔥 Real-time file monitoring!")
|
||||
|
||||
socketio.run(app, host='0.0.0.0', port=5000, debug=False)
|
||||
@@ -0,0 +1,181 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Form Testing Script for PHP Application
|
||||
Tests form submission with valid data every 20 seconds
|
||||
"""
|
||||
|
||||
import requests
|
||||
import time
|
||||
import random
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
class FormTester:
|
||||
def __init__(self, base_url="http://192.168.2.38"):
|
||||
self.base_url = base_url
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update({
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
||||
})
|
||||
|
||||
def generate_test_data(self):
|
||||
"""Generate realistic test data for the form"""
|
||||
# Extended list of company names with various Czech company types
|
||||
company_prefixes = [
|
||||
"Česká", "Slovenská", "Evropská", "Mezinárodní", "Regionální", "Místní",
|
||||
"Nová", "Moderní", "Tradiční", "První", "Jednotná", "Spojená"
|
||||
]
|
||||
|
||||
company_main = [
|
||||
"Technologická", "Průmyslová", "Obchodní", "Stavební", "Dopravní",
|
||||
"Energetická", "Zemědělská", "Potravinářská", "Textilní", "Kovovýroba",
|
||||
"Softwarová", "Marketingová", "Finanční", "Logistická", "Vzdělávací"
|
||||
]
|
||||
|
||||
company_suffixes = [
|
||||
"s.r.o.", "a.s.", "o.s.", "v.o.s.", "k.s.", "Ltd.", "Inc.", "GmbH",
|
||||
"Společnost", "Firma", "Podnik", "Závod", "Družstvo", "Holding"
|
||||
]
|
||||
|
||||
# Extended list of Czech cities and towns
|
||||
cities = [
|
||||
"Praha", "Brno", "Ostrava", "Plzeň", "Liberec", "Olomouc", "Budějovice",
|
||||
"Hradec Králové", "Ústí nad Labem", "Pardubice", "Karlovy Vary", "Jihlava",
|
||||
"Zlín", "Mladá Boleslav", "Kladno", "Most", "Opava", "Frýdek-Místek",
|
||||
"Teplice", "Chomutov", "Jablonec nad Nisou", "Mělník", "Prostějov",
|
||||
"Přerov", "Trutnov", "Tábor", "Kolín", "Kroměříž", "Havířov", "České Budějovice",
|
||||
"Děčín", "Krnov", "Šumperk", "Orlová", "Litvínov", "Vsetín", "Valašské Meziříčí",
|
||||
"Česká Lípa", "Beroun", "Žďár nad Sázavou", "Klatovy", "Rumburk", "Vrchlabí"
|
||||
]
|
||||
|
||||
# Generate random company name with structure
|
||||
prefix = random.choice(company_prefixes)
|
||||
main = random.choice(company_main)
|
||||
suffix = random.choice(company_suffixes)
|
||||
|
||||
# Sometimes add additional descriptors
|
||||
if random.random() < 0.3: # 30% chance to add extra descriptor
|
||||
descriptors = ["Group", "International", "Central", "Premium", "Plus", "Max"]
|
||||
company_name = f"{prefix} {main} {random.choice(descriptors)} {suffix}"
|
||||
else:
|
||||
company_name = f"{prefix} {main} {suffix}"
|
||||
|
||||
# Sometimes add a number
|
||||
if random.random() < 0.2: # 20% chance to add number
|
||||
company_name += f" {random.randint(1, 500)}"
|
||||
|
||||
# Generate random city
|
||||
city = random.choice(cities)
|
||||
|
||||
# Generate random founding date (between 1950 and 2024)
|
||||
start_year = 1950
|
||||
end_year = 2024
|
||||
year = random.randint(start_year, end_year)
|
||||
month = random.randint(1, 12)
|
||||
|
||||
# Generate valid day for the month
|
||||
if month in [4, 6, 9, 11]:
|
||||
day = random.randint(1, 30)
|
||||
elif month == 2:
|
||||
# Handle leap years
|
||||
if (year % 400 == 0) or (year % 100 != 0 and year % 4 == 0):
|
||||
day = random.randint(1, 29)
|
||||
else:
|
||||
day = random.randint(1, 28)
|
||||
else:
|
||||
day = random.randint(1, 31)
|
||||
|
||||
founding_date = f"{year:04d}-{month:02d}-{day:02d}"
|
||||
|
||||
return {
|
||||
'nazev': company_name,
|
||||
'mesto': city,
|
||||
'datum_zalozeni': founding_date,
|
||||
'odeslat': 'Uložit do databáze'
|
||||
}
|
||||
|
||||
def submit_form(self, form_data):
|
||||
"""Submit the form with provided data"""
|
||||
try:
|
||||
response = self.session.post(
|
||||
f"{self.base_url}/zapisfirem.php",
|
||||
data=form_data,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Form submitted:")
|
||||
print(f" Name: {form_data['nazev']}")
|
||||
print(f" City: {form_data['mesto']}")
|
||||
print(f" Date: {form_data['datum_zalozeni']}")
|
||||
print(f" Status Code: {response.status_code}")
|
||||
print(f" Response Length: {len(response.text)} characters")
|
||||
|
||||
# Check for success message
|
||||
if "Firma byla úspěšně vložena do databáze" in response.text:
|
||||
print(" ✅ Success: Company inserted into database")
|
||||
elif "Firma byla �sp�n� vlo�ena do datab�ze" in response.text: # Handle encoding issues
|
||||
print(" ✅ Success: Company inserted into database (encoding detected)")
|
||||
else:
|
||||
print(" ⚠️ Unknown response - check if submission was successful")
|
||||
|
||||
print("-" * 60)
|
||||
|
||||
return response.status_code == 200
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"❌ Error submitting form: {e}")
|
||||
return False
|
||||
|
||||
def run_test(self, interval_seconds=20, max_iterations=None):
|
||||
"""Run the form testing loop"""
|
||||
print(f"🧪 Starting form testing...")
|
||||
print(f"📡 Target: {self.base_url}/zapisfirem.php")
|
||||
print(f"⏰ Interval: {interval_seconds} seconds")
|
||||
print(f"🔄 Max iterations: {'Unlimited' if max_iterations is None else max_iterations}")
|
||||
print("=" * 60)
|
||||
|
||||
iteration = 0
|
||||
|
||||
try:
|
||||
while True:
|
||||
if max_iterations and iteration >= max_iterations:
|
||||
print(f"🏁 Reached maximum iterations ({max_iterations})")
|
||||
break
|
||||
|
||||
iteration += 1
|
||||
print(f"📝 Iteration {iteration}")
|
||||
|
||||
# Generate and submit test data
|
||||
form_data = self.generate_test_data()
|
||||
success = self.submit_form(form_data)
|
||||
|
||||
if not success:
|
||||
print("⚠️ Submission failed, but continuing...")
|
||||
|
||||
# Wait for the specified interval
|
||||
if max_iterations is None or iteration < max_iterations:
|
||||
print(f"⏳ Waiting {interval_seconds} seconds...")
|
||||
time.sleep(interval_seconds)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n🛑 Testing stopped by user after {iteration} iterations")
|
||||
except Exception as e:
|
||||
print(f"❌ Unexpected error: {e}")
|
||||
|
||||
print("🏁 Form testing completed")
|
||||
|
||||
def main():
|
||||
"""Main function to run the tester"""
|
||||
print("PHP Form Testing Script")
|
||||
print("======================")
|
||||
print("This script will submit form data every 0.5 seconds to test your PHP application.")
|
||||
print("Press Ctrl+C to stop the testing at any time.")
|
||||
print()
|
||||
|
||||
# Create and run the tester
|
||||
tester = FormTester()
|
||||
|
||||
# Run with 0.5-second interval for faster testing (you can change this)
|
||||
tester.run_test(interval_seconds=0.001)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,200 @@
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import List, Dict
|
||||
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font
|
||||
from openpyxl.utils import get_column_letter
|
||||
|
||||
|
||||
MATCH_FIELDS: List[Dict[str, str]] = [
|
||||
{
|
||||
"name": "competition_code",
|
||||
"label": "Kód soutěže",
|
||||
"help": "Např. A1A. Použije se, pokud není vyplněno competition_external_id.",
|
||||
},
|
||||
{
|
||||
"name": "competition_external_id",
|
||||
"label": "External ID soutěže (UUID)",
|
||||
"help": "UUID soutěže z odkazu na fotbal.cz. Má přednost před competition_code.",
|
||||
},
|
||||
{"name": "round", "label": "Kolo", "help": "Např. 2. kolo, 10. kolo atd."},
|
||||
{
|
||||
"name": "is_home",
|
||||
"label": "Domácí / venku",
|
||||
"help": "Hodnota home/away (nebo 1/0, true/false, yes/no).",
|
||||
},
|
||||
{"name": "opponent_name", "label": "Soupeř", "help": "Název soupeře."},
|
||||
{
|
||||
"name": "opponent_club_link",
|
||||
"label": "Odkaz na klub soupeře",
|
||||
"help": "URL na klub soupeře z fotbal.cz – z URL se přečte UUID pro loga.",
|
||||
},
|
||||
{
|
||||
"name": "external_match_id",
|
||||
"label": "External ID zápasu (UUID)",
|
||||
"help": "UUID zápasu; pokud není, vezme se UUID z match_link.",
|
||||
},
|
||||
{
|
||||
"name": "kickoff_date",
|
||||
"label": "Datum výkopu (YYYY-MM-DD)",
|
||||
"help": "Datum zápasu, např. 2025-03-15.",
|
||||
},
|
||||
{
|
||||
"name": "kickoff_time",
|
||||
"label": "Čas výkopu (HH:MM)",
|
||||
"help": "Čas zápasu, např. 17:00.",
|
||||
},
|
||||
{
|
||||
"name": "score_fulltime",
|
||||
"label": "Konečný stav",
|
||||
"help": "Např. 2:1. Může být prázdné pro budoucí zápasy.",
|
||||
},
|
||||
{
|
||||
"name": "score_halftime",
|
||||
"label": "Poločasu",
|
||||
"help": "Např. 1:0. Volitelné.",
|
||||
},
|
||||
{
|
||||
"name": "match_link",
|
||||
"label": "Odkaz na zápas",
|
||||
"help": "URL na detail zápasu na is.fotbal.cz.",
|
||||
},
|
||||
{"name": "venue", "label": "Hřiště", "help": "Název hřiště / stadionu."},
|
||||
{"name": "note", "label": "Poznámka", "help": "Libovolná poznámka k zápasu."},
|
||||
]
|
||||
|
||||
|
||||
TABLE_FIELDS: List[Dict[str, str]] = [
|
||||
{
|
||||
"name": "competition_code",
|
||||
"label": "Kód soutěže",
|
||||
"help": "Např. A1A. Použije se, pokud není vyplněno competition_external_id.",
|
||||
},
|
||||
{
|
||||
"name": "competition_external_id",
|
||||
"label": "External ID soutěže (UUID)",
|
||||
"help": "UUID soutěže z odkazu na fotbal.cz. Má přednost před competition_code.",
|
||||
},
|
||||
{"name": "rank", "label": "Pořadí", "help": "Pozice v tabulce, např. 1."},
|
||||
{"name": "team_name", "label": "Tým", "help": "Název týmu."},
|
||||
{
|
||||
"name": "team_club_link",
|
||||
"label": "Odkaz na klub týmu",
|
||||
"help": "URL na klub z fotbal.cz – z URL se přečte UUID pro loga.",
|
||||
},
|
||||
{"name": "played", "label": "Zápasy", "help": "Počet odehraných zápasů."},
|
||||
{"name": "wins", "label": "Výhry", "help": "Počet výher."},
|
||||
{"name": "draws", "label": "Remízy", "help": "Počet remíz."},
|
||||
{"name": "losses", "label": "Prohry", "help": "Počet proher."},
|
||||
{
|
||||
"name": "score",
|
||||
"label": "Skóre",
|
||||
"help": "Souhrnné skóre ve formátu góly:inkasované, např. 45:17.",
|
||||
},
|
||||
{"name": "points", "label": "Body", "help": "Počet bodů v tabulce."},
|
||||
]
|
||||
|
||||
|
||||
def _auto_width(sheet):
|
||||
for column_cells in sheet.columns:
|
||||
max_length = 0
|
||||
column = column_cells[0].column
|
||||
for cell in column_cells:
|
||||
value = str(cell.value) if cell.value is not None else ""
|
||||
if len(value) > max_length:
|
||||
max_length = len(value)
|
||||
adjusted = max(max_length + 2, 12)
|
||||
sheet.column_dimensions[get_column_letter(column)].width = adjusted
|
||||
|
||||
|
||||
def generate_workbook(output_dir: Path) -> None:
|
||||
wb = Workbook()
|
||||
|
||||
ws_matches = wb.active
|
||||
ws_matches.title = "matches"
|
||||
ws_matches.append([f["name"] for f in MATCH_FIELDS])
|
||||
for cell in ws_matches[1]:
|
||||
cell.font = Font(bold=True)
|
||||
_auto_width(ws_matches)
|
||||
|
||||
ws_tables = wb.create_sheet(title="tables")
|
||||
ws_tables.append([f["name"] for f in TABLE_FIELDS])
|
||||
for cell in ws_tables[1]:
|
||||
cell.font = Font(bold=True)
|
||||
_auto_width(ws_tables)
|
||||
|
||||
path = output_dir / "manual_facr_templates.xlsx"
|
||||
wb.save(path)
|
||||
|
||||
|
||||
def generate_forms_spec(output_dir: Path) -> None:
|
||||
spec = {
|
||||
"matches_form": {
|
||||
"title": "Manuální zápasy (FAČR)",
|
||||
"description": "Formulář pro ruční zadání zápasů ve struktuře odpovídající CSV/XLSX/JSON importu.",
|
||||
"fields": [
|
||||
{
|
||||
"name": f["name"],
|
||||
"label": f["label"],
|
||||
"help_text": f["help"],
|
||||
"type": "text",
|
||||
"required": f["name"]
|
||||
in {"competition_code", "competition_external_id", "is_home", "opponent_name"},
|
||||
}
|
||||
for f in MATCH_FIELDS
|
||||
],
|
||||
},
|
||||
"tables_form": {
|
||||
"title": "Manuální tabulky (FAČR)",
|
||||
"description": "Formulář pro ruční zadání tabulek soutěží ve struktuře odpovídající CSV/XLSX/JSON importu.",
|
||||
"fields": [
|
||||
{
|
||||
"name": f["name"],
|
||||
"label": f["label"],
|
||||
"help_text": f["help"],
|
||||
"type": "text",
|
||||
"required": f["name"]
|
||||
in {"competition_code", "competition_external_id", "rank", "team_name"},
|
||||
}
|
||||
for f in TABLE_FIELDS
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
path = output_dir / "manual_facr_google_forms.json"
|
||||
path.write_text(json.dumps(spec, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"Generate manual FACR Excel templates (matches/tables) and a Google Forms "
|
||||
"field specification from a single schema."
|
||||
)
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output-dir",
|
||||
type=str,
|
||||
default=None,
|
||||
help=(
|
||||
"Output directory for generated files. "
|
||||
"Defaults to <project_root>/static/manual-facr."
|
||||
),
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.output_dir:
|
||||
output_dir = Path(args.output_dir)
|
||||
else:
|
||||
output_dir = Path(__file__).resolve().parents[1] / "static" / "manual-facr"
|
||||
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
generate_workbook(output_dir)
|
||||
generate_forms_spec(output_dir)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import argparse
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
|
||||
IGNORED_DIRS = {
|
||||
'.git',
|
||||
'.git_backup',
|
||||
'__pycache__',
|
||||
'node_modules',
|
||||
'dist',
|
||||
'build',
|
||||
'.next',
|
||||
'.cache',
|
||||
'.venv',
|
||||
'venv',
|
||||
}
|
||||
|
||||
ALLOWED_EXTS = {
|
||||
'.tsx',
|
||||
'.css',
|
||||
'.go',
|
||||
'.ts',
|
||||
'.js',
|
||||
'.html',
|
||||
'.sql',
|
||||
'.py',
|
||||
}
|
||||
|
||||
|
||||
def walk_project(root: str):
|
||||
for current_dir, dirs, files in os.walk(root):
|
||||
dirs[:] = [d for d in dirs if d not in IGNORED_DIRS]
|
||||
yield current_dir, dirs, files
|
||||
|
||||
|
||||
def count_stats(root: str):
|
||||
total_dirs = 0
|
||||
total_files = 0
|
||||
total_lines = 0
|
||||
by_ext = defaultdict(lambda: [0, 0]) # ext -> [file_count, line_count]
|
||||
|
||||
for current_dir, dirs, files in walk_project(root):
|
||||
if current_dir != root:
|
||||
total_dirs += 1
|
||||
|
||||
for name in files:
|
||||
ext = os.path.splitext(name)[1]
|
||||
if ext not in ALLOWED_EXTS:
|
||||
continue
|
||||
|
||||
total_files += 1
|
||||
path = os.path.join(current_dir, name)
|
||||
ext_key = ext or '<no_ext>'
|
||||
line_count = 0
|
||||
try:
|
||||
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
for _ in f:
|
||||
line_count += 1
|
||||
except Exception:
|
||||
line_count = 0
|
||||
|
||||
total_lines += line_count
|
||||
by_ext[ext_key][0] += 1
|
||||
by_ext[ext_key][1] += line_count
|
||||
|
||||
return total_dirs, total_files, total_lines, by_ext
|
||||
|
||||
|
||||
def print_tree(root: str, max_depth: int | None = None):
|
||||
tree_output = []
|
||||
root = os.path.abspath(root)
|
||||
for current_dir, dirs, files in walk_project(root):
|
||||
rel = os.path.relpath(current_dir, root)
|
||||
if rel == '.':
|
||||
depth = 0
|
||||
name = os.path.basename(root.rstrip(os.sep)) or root
|
||||
else:
|
||||
depth = rel.count(os.sep) + 1
|
||||
name = os.path.basename(current_dir)
|
||||
|
||||
if max_depth is not None and depth > max_depth:
|
||||
dirs[:] = []
|
||||
continue
|
||||
|
||||
indent = ' ' * depth
|
||||
tree_output.append(f"{indent}{name}/")
|
||||
|
||||
file_indent = ' ' * (depth + 1)
|
||||
for filename in sorted(files):
|
||||
tree_output.append(f"{file_indent}{filename}")
|
||||
|
||||
return tree_output
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Project statistics: files, folders, lines, and structure.')
|
||||
parser.add_argument('path', nargs='?', default='.', help='Root path (default: current directory)')
|
||||
parser.add_argument('--max-tree-depth', type=int, default=3, help='Max depth for printed tree (default: 3)')
|
||||
parser.add_argument('--output-md', action='store_true', help='Output to stats.md file instead of console')
|
||||
args = parser.parse_args()
|
||||
|
||||
root = os.path.abspath(args.path)
|
||||
|
||||
total_dirs, total_files, total_lines, by_ext = count_stats(root)
|
||||
tree_lines = print_tree(root, max_depth=args.max_tree_depth)
|
||||
|
||||
if args.output_md:
|
||||
# Get script directory for output file
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
output_file = os.path.join(script_dir, 'stats.md')
|
||||
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
f.write(f"# Project Statistics\n\n")
|
||||
f.write(f"**Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
||||
f.write(f"**Path:** `{root}`\n\n")
|
||||
|
||||
f.write("## Summary\n\n")
|
||||
f.write(f"- **Total directories:** {total_dirs}\n")
|
||||
f.write(f"- **Total files:** {total_files}\n")
|
||||
f.write(f"- **Total lines (approximate):** {total_lines}\n\n")
|
||||
|
||||
f.write("## Lines by Extension\n\n")
|
||||
f.write("| Extension | Files | Lines |\n")
|
||||
f.write("|-----------|-------|-------|\n")
|
||||
|
||||
for ext, (file_count, line_count) in sorted(by_ext.items(), key=lambda kv: kv[1][1], reverse=True):
|
||||
label = ext if ext else '<no_ext>'
|
||||
f.write(f"| {label} | {file_count} | {line_count} |\n")
|
||||
|
||||
f.write(f"\n## Directory Tree (max depth {args.max_tree_depth})\n\n")
|
||||
f.write("```\n")
|
||||
for line in tree_lines:
|
||||
f.write(line + "\n")
|
||||
f.write("```\n")
|
||||
|
||||
print(f"Statistics saved to: {output_file}")
|
||||
else:
|
||||
# Original console output
|
||||
print(f"Analyzing project at: {root}")
|
||||
print()
|
||||
|
||||
print('=== SUMMARY ===')
|
||||
print(f"Total directories: {total_dirs}")
|
||||
print(f"Total files: {total_files}")
|
||||
print(f"Total lines (approximate): {total_lines}")
|
||||
print()
|
||||
|
||||
print('=== LINES BY EXTENSION ===')
|
||||
for ext, (file_count, line_count) in sorted(by_ext.items(), key=lambda kv: kv[1][1], reverse=True):
|
||||
label = ext if ext else '<no_ext>'
|
||||
print(f"{label:10} files={file_count:6} lines={line_count:10}")
|
||||
|
||||
print()
|
||||
print(f"=== DIRECTORY TREE (max depth {args.max_tree_depth}) ===")
|
||||
for line in tree_lines:
|
||||
print(line)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1 @@
|
||||
psutil>=5.9.0
|
||||
@@ -0,0 +1,3 @@
|
||||
flask>=2.3.0
|
||||
flask-socketio>=5.3.0
|
||||
psutil>=5.9.0
|
||||
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Project Tamagotchi - Browser Terminal
|
||||
Single launcher for the browser-based terminal tamagotchi
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("🐣 PROJECT TAMAGOTCHI - BROWSER TERMINAL")
|
||||
print("=" * 60)
|
||||
print("Features:")
|
||||
print(" • Terminal interface in your browser")
|
||||
print(" • Real-time file monitoring (every 2 seconds)")
|
||||
print(" • ASCII display (no emojis)")
|
||||
print(" • Activity detection when you edit files")
|
||||
print(" • Command history (use arrow keys)")
|
||||
print(" • Terminal effects and animations")
|
||||
print()
|
||||
print("🎮 Controls in browser:")
|
||||
print(" • f - Feed the tamagotchi")
|
||||
print(" • p - Play with the tamagotchi")
|
||||
print(" • r - Refresh stats (automatic)")
|
||||
print(" • q - Quit session")
|
||||
print()
|
||||
print("🔧 How it works:")
|
||||
print(" • Scans your project for code files")
|
||||
print(" • Detects file changes in real-time")
|
||||
print(" • Updates tamagotchi mood based on activity")
|
||||
print(" • Shows project statistics")
|
||||
print()
|
||||
|
||||
# Check dependencies
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
venv_python = os.path.join(script_dir, 'tamagotchi_env', 'bin', 'python')
|
||||
|
||||
if not os.path.exists(venv_python):
|
||||
print("📦 Setting up environment...")
|
||||
try:
|
||||
subprocess.run([sys.executable, '-m', 'venv', 'tamagotchi_env'],
|
||||
cwd=script_dir, check=True, capture_output=True)
|
||||
print("✅ Virtual environment created")
|
||||
except subprocess.CalledProcessError:
|
||||
print("❌ Failed to create virtual environment")
|
||||
return 1
|
||||
|
||||
# Install dependencies if needed
|
||||
print("📦 Checking dependencies...")
|
||||
try:
|
||||
venv_pip = os.path.join(script_dir, 'tamagotchi_env', 'bin', 'pip')
|
||||
result = subprocess.run([venv_pip, 'install', 'flask', 'flask-socketio'],
|
||||
cwd=script_dir, check=True, capture_output=True)
|
||||
print("✅ Dependencies ready")
|
||||
except subprocess.CalledProcessError:
|
||||
print("✅ Dependencies already installed")
|
||||
|
||||
# Get project name
|
||||
project_name = "MyClub"
|
||||
if len(sys.argv) > 1:
|
||||
project_name = sys.argv[1]
|
||||
|
||||
print(f"📊 Project: {project_name}")
|
||||
print("🌐 Starting browser terminal...")
|
||||
print("-" * 60)
|
||||
print("🌐 OPEN YOUR BROWSER TO: http://localhost:5000")
|
||||
print("⌨️ Terminal interface with real-time updates!")
|
||||
print("🔥 Edit files to see activity detection!")
|
||||
print("📱 Works on mobile too!")
|
||||
print("🛑 Press Ctrl+C to stop the server")
|
||||
print("=" * 60)
|
||||
|
||||
# Launch browser terminal
|
||||
terminal_script = os.path.join(script_dir, 'browser_terminal_tamagotchi.py')
|
||||
|
||||
if os.path.exists(terminal_script):
|
||||
try:
|
||||
subprocess.run([venv_python, terminal_script, project_name])
|
||||
except KeyboardInterrupt:
|
||||
print("\n👋 Browser terminal stopped! Thanks for playing!")
|
||||
return 0
|
||||
else:
|
||||
print(f"❌ Error: {terminal_script} not found!")
|
||||
return 1
|
||||
|
||||
if __name__ == '__main__':
|
||||
exit(main())
|
||||
# Testing file tracking Mon Jan 12 17:44:15 CET 2026
|
||||
# Testing stable terminal Mon Jan 12 17:46:45 CET 2026
|
||||
# Testing bigger terminal Mon Jan 12 17:50:30 CET 2026
|
||||
# Testing much bigger UI Mon Jan 12 17:51:32 CET 2026
|
||||
+1157
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,335 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Terminal Tamagotchi</title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #000;
|
||||
font-family: 'Courier New', 'Monaco', 'Menlo', monospace;
|
||||
color: #00ff00;
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.terminal-header {
|
||||
background: #333;
|
||||
padding: 8px;
|
||||
border-bottom: 2px solid #666;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.terminal-button {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.terminal-button.close { background: #ff5f56; }
|
||||
.terminal-button.minimize { background: #ffbd2e; }
|
||||
.terminal-button.maximize { background: #27c93f; }
|
||||
|
||||
.terminal-title {
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.terminal-body {
|
||||
flex: 1;
|
||||
padding: 25px;
|
||||
overflow: hidden; /* No scrolling at all */
|
||||
font-size: 18px; /* Much bigger font */
|
||||
line-height: 1.5; /* More spacing */
|
||||
white-space: pre-wrap;
|
||||
background: #0a0a0a;
|
||||
max-height: calc(100vh - 120px); /* More height */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.terminal-body::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.terminal-body::-webkit-scrollbar-track {
|
||||
background: #1a1a1a;
|
||||
}
|
||||
|
||||
.terminal-body::-webkit-scrollbar-thumb {
|
||||
background: #333;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.terminal-body::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
|
||||
.terminal-input {
|
||||
background: #0a0a0a;
|
||||
border: none;
|
||||
color: #00ff00;
|
||||
font-family: 'Courier New', 'Monaco', 'Menlo', monospace;
|
||||
font-size: 18px; /* Match terminal body */
|
||||
padding: 12px 25px; /* Bigger padding */
|
||||
outline: none;
|
||||
width: 100%;
|
||||
border-top: 1px solid #333;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.terminal-input::placeholder {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.typing-indicator {
|
||||
color: #ffff00;
|
||||
animation: blink 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 50% { opacity: 1; }
|
||||
51%, 100% { opacity: 0; }
|
||||
}
|
||||
|
||||
.cursor {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 16px;
|
||||
background: #00ff00;
|
||||
animation: cursor-blink 1s infinite;
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
@keyframes cursor-blink {
|
||||
0%, 49% { opacity: 1; }
|
||||
50%, 100% { opacity: 0; }
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
background: #333;
|
||||
color: #00ff00;
|
||||
padding: 8px 25px; /* Bigger padding */
|
||||
font-size: 14px; /* Bigger status bar */
|
||||
border-top: 1px solid #666;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-bar > div {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status-bar > div:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.status-bar > div:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.activity-detected {
|
||||
color: #ff6b6b;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Scanlines effect */
|
||||
.terminal-body::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(
|
||||
transparent 50%,
|
||||
rgba(0, 255, 0, 0.03) 50%
|
||||
);
|
||||
background-size: 100% 4px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.terminal-wrapper {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.terminal-content {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="terminal-header">
|
||||
<button class="terminal-button close"></button>
|
||||
<button class="terminal-button minimize"></button>
|
||||
<button class="terminal-button maximize"></button>
|
||||
<div class="terminal-title">terminal-tamagotchi@myclub: ~$</div>
|
||||
</div>
|
||||
|
||||
<div class="terminal-wrapper">
|
||||
<div class="terminal-body" id="terminal">
|
||||
<div class="terminal-content" id="terminalContent">
|
||||
Initializing terminal monitor...
|
||||
Scanning project files...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="text" class="terminal-input" id="terminalInput" placeholder="Enter command (f=feed, p=play, r=refresh, q=quit)..." autocomplete="off">
|
||||
|
||||
<div class="status-bar">
|
||||
<div id="statusLeft">Ready</div>
|
||||
<div id="statusCenter">No activity</div>
|
||||
<div id="statusRight">Connected</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const socket = io();
|
||||
let commandHistory = [];
|
||||
let historyIndex = -1;
|
||||
|
||||
const terminal = document.getElementById('terminalContent');
|
||||
const input = document.getElementById('terminalInput');
|
||||
const statusLeft = document.getElementById('statusLeft');
|
||||
const statusCenter = document.getElementById('statusCenter');
|
||||
const statusRight = document.getElementById('statusRight');
|
||||
|
||||
function updateTerminal(data) {
|
||||
terminal.textContent = data.output;
|
||||
|
||||
// Update status - simplified to avoid conflicts
|
||||
if (data.typing_detected) {
|
||||
statusLeft.textContent = `ACTIVITY: ${data.recent_changes} files`;
|
||||
statusLeft.className = 'activity-detected';
|
||||
|
||||
// Extract current file info from terminal output
|
||||
const lines = data.output.split('\n');
|
||||
const currentFileLine = lines.find(line => line.includes('File:'));
|
||||
if (currentFileLine) {
|
||||
const fileName = currentFileLine.split('File:')[1]?.trim();
|
||||
statusCenter.textContent = `Editing: ${fileName}`;
|
||||
} else {
|
||||
statusCenter.textContent = 'Files being modified...';
|
||||
}
|
||||
} else {
|
||||
statusLeft.textContent = 'Monitoring...';
|
||||
statusLeft.className = '';
|
||||
statusCenter.textContent = 'No activity';
|
||||
}
|
||||
|
||||
// Auto-scroll to bottom
|
||||
const terminalBody = document.getElementById('terminal');
|
||||
terminalBody.scrollTop = terminalBody.scrollHeight;
|
||||
}
|
||||
|
||||
function sendCommand(cmd) {
|
||||
if (!cmd.trim()) return;
|
||||
|
||||
// Add to history
|
||||
commandHistory.push(cmd);
|
||||
historyIndex = commandHistory.length;
|
||||
|
||||
// Show command in terminal
|
||||
terminal.textContent += `\n$ ${cmd}\n`;
|
||||
|
||||
// Send to server
|
||||
console.log("Sending command:", cmd); // Debug log
|
||||
socket.emit('command', { command: cmd });
|
||||
|
||||
// Clear input
|
||||
input.value = '';
|
||||
}
|
||||
|
||||
// Socket events
|
||||
socket.on('connect', () => {
|
||||
statusRight.textContent = 'Connected';
|
||||
statusRight.style.color = '#00ff00';
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
statusRight.textContent = 'Disconnected';
|
||||
statusRight.style.color = '#ff0000';
|
||||
});
|
||||
|
||||
socket.on('terminal_update', (data) => {
|
||||
updateTerminal(data);
|
||||
});
|
||||
|
||||
socket.on('quit', (data) => {
|
||||
terminal.textContent += `\n${data.message}\nConnection closed.\n`;
|
||||
input.disabled = true;
|
||||
input.placeholder = 'Session ended';
|
||||
});
|
||||
|
||||
// Input handling
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
sendCommand(input.value);
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
if (historyIndex > 0) {
|
||||
historyIndex--;
|
||||
input.value = commandHistory[historyIndex];
|
||||
}
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
if (historyIndex < commandHistory.length - 1) {
|
||||
historyIndex++;
|
||||
input.value = commandHistory[historyIndex];
|
||||
} else {
|
||||
historyIndex = commandHistory.length;
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Focus input on page load
|
||||
window.addEventListener('load', () => {
|
||||
input.focus();
|
||||
|
||||
// Load initial data
|
||||
fetch('/api/terminal-data')
|
||||
.then(response => response.json())
|
||||
.then(data => updateTerminal(data))
|
||||
.catch(error => {
|
||||
terminal.textContent += `Error loading data: ${error}\n`;
|
||||
});
|
||||
});
|
||||
|
||||
// Keep focus on terminal
|
||||
document.addEventListener('click', () => {
|
||||
input.focus();
|
||||
});
|
||||
|
||||
// Add some terminal-like effects
|
||||
setInterval(() => {
|
||||
if (Math.random() < 0.01) { // 1% chance of flicker
|
||||
terminal.style.opacity = '0.9';
|
||||
setTimeout(() => {
|
||||
terminal.style.opacity = '1';
|
||||
}, 50);
|
||||
}
|
||||
}, 1000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user