Updated online users.
This commit is contained in:
		
							parent
							
								
									2fd01a5ab7
								
							
						
					
					
						commit
						03f699e448
					
				
							
								
								
									
										2558
									
								
								gitlog.jsonl
									
									
									
									
									
								
							
							
						
						
									
										2558
									
								
								gitlog.jsonl
									
									
									
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										289
									
								
								gitlog.py
									
									
									
									
									
								
							
							
						
						
									
										289
									
								
								gitlog.py
									
									
									
									
									
								
							| @ -1,289 +0,0 @@ | |||||||
| import http.server |  | ||||||
| import socketserver |  | ||||||
| import json |  | ||||||
| import os |  | ||||||
| import subprocess |  | ||||||
| from urllib.parse import parse_qs, urlparse |  | ||||||
| import mimetypes |  | ||||||
| import html |  | ||||||
| 
 |  | ||||||
| # --- Theme selection (choose one: "light1", "light2", "dark1", "dark2") --- |  | ||||||
| THEME = "light1"  # Change this to "light2", "dark1", or "dark2" as desired |  | ||||||
| 
 |  | ||||||
| THEMES = { |  | ||||||
|     "light1": """ |  | ||||||
|         body { font-family: Arial, sans-serif; background: #f6f8fa; margin: 0; padding: 0; color: #222; } |  | ||||||
|         .container { max-width: 960px; margin: auto; padding: 2em; } |  | ||||||
|         .date-header { font-size: 1.2em; margin-top: 2em; border-bottom: 1px solid #ccc; } |  | ||||||
|         .commit { margin: 1em 0; padding: 1em; background: #fff; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,0.07); } |  | ||||||
|         .hash { color: #555; font-family: monospace; } |  | ||||||
|         .diff { white-space: pre-wrap; background: #f1f1f1; padding: 1em; border-radius: 4px; overflow-x: auto; } |  | ||||||
|         ul { list-style: none; padding-left: 0; } |  | ||||||
|         li { margin: 0.3em 0; } |  | ||||||
|         a { text-decoration: none; color: #0366d6; } |  | ||||||
|         a:hover { text-decoration: underline; } |  | ||||||
|     """, |  | ||||||
|     "light2": """ |  | ||||||
|         body { font-family: 'Segoe UI', sans-serif; background: #fdf6e3; margin: 0; padding: 0; color: #333; } |  | ||||||
|         .container { max-width: 900px; margin: auto; padding: 2em; } |  | ||||||
|         .date-header { font-size: 1.2em; margin-top: 2em; border-bottom: 2px solid #e1c699; color: #b58900; } |  | ||||||
|         .commit { margin: 1em 0; padding: 1em; background: #fffbe6; border-radius: 6px; box-shadow: 0 1px 3px rgba(200,180,100,0.08); } |  | ||||||
|         .hash { color: #b58900; font-family: monospace; } |  | ||||||
|         .diff { white-space: pre-wrap; background: #f5e9c9; padding: 1em; border-radius: 4px; overflow-x: auto; } |  | ||||||
|         ul { list-style: none; padding-left: 0; } |  | ||||||
|         li { margin: 0.3em 0; } |  | ||||||
|         a { text-decoration: none; color: #b58900; } |  | ||||||
|         a:hover { text-decoration: underline; color: #cb4b16; } |  | ||||||
|     """, |  | ||||||
|     "dark1": """ |  | ||||||
|         body { font-family: Arial, sans-serif; background: #181a1b; margin: 0; padding: 0; color: #eaeaea; } |  | ||||||
|         .container { max-width: 960px; margin: auto; padding: 2em; } |  | ||||||
|         .date-header { font-size: 1.2em; margin-top: 2em; border-bottom: 1px solid #333; color: #8ab4f8; } |  | ||||||
|         .commit { margin: 1em 0; padding: 1em; background: #23272b; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,0.18); } |  | ||||||
|         .hash { color: #8ab4f8; font-family: monospace; } |  | ||||||
|         .diff { white-space: pre-wrap; background: #23272b; padding: 1em; border-radius: 4px; overflow-x: auto; } |  | ||||||
|         ul { list-style: none; padding-left: 0; } |  | ||||||
|         li { margin: 0.3em 0; } |  | ||||||
|         a { text-decoration: none; color: #8ab4f8; } |  | ||||||
|         a:hover { text-decoration: underline; color: #bb86fc; } |  | ||||||
|     """, |  | ||||||
|     "dark2": """ |  | ||||||
|         body { font-family: 'Fira Sans', sans-serif; background: #121212; margin: 0; padding: 0; color: #d0d0d0; } |  | ||||||
|         .container { max-width: 900px; margin: auto; padding: 2em; } |  | ||||||
|         .date-header { font-size: 1.2em; margin-top: 2em; border-bottom: 2px solid #444; color: #ffb86c; } |  | ||||||
|         .commit { margin: 1em 0; padding: 1em; background: #22223b; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,0.22); } |  | ||||||
|         .hash { color: #ffb86c; font-family: monospace; } |  | ||||||
|         .diff { white-space: pre-wrap; background: #282a36; padding: 1em; border-radius: 4px; overflow-x: auto; } |  | ||||||
|         ul { list-style: none; padding-left: 0; } |  | ||||||
|         li { margin: 0.3em 0; } |  | ||||||
|         a { text-decoration: none; color: #ffb86c; } |  | ||||||
|         a:hover { text-decoration: underline; color: #8be9fd; } |  | ||||||
|     """, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| def HTML_TEMPLATE(content, theme=THEME): |  | ||||||
|     return f""" |  | ||||||
| <!DOCTYPE html> |  | ||||||
| <html lang=\"en\"> |  | ||||||
| <head> |  | ||||||
|     <meta charset=\"UTF-8\"> |  | ||||||
|     <title>Git Log Viewer</title> |  | ||||||
|     <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css\"> |  | ||||||
|     <style> |  | ||||||
|         {THEMES[theme]} |  | ||||||
|     </style> |  | ||||||
|     <script src=\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js\"></script> |  | ||||||
|     <script>hljs.highlightAll();</script> |  | ||||||
| </head> |  | ||||||
| <body> |  | ||||||
|     <div class=\"container\"> |  | ||||||
|         {content} |  | ||||||
|     </div> |  | ||||||
| </body> |  | ||||||
| </html> |  | ||||||
| """ |  | ||||||
| 
 |  | ||||||
| REPO_ROOT = os.path.abspath(".") |  | ||||||
| LOG_FILE = os.path.join(REPO_ROOT, "gitlog.jsonl") |  | ||||||
| 
 |  | ||||||
| PORT = 8481 |  | ||||||
| 
 |  | ||||||
| def format_diff_to_html(diff_text: str) -> str: |  | ||||||
|     lines = diff_text.strip().splitlines() |  | ||||||
|     html_lines = ['<div style="font-family: monospace; white-space: pre;">'] |  | ||||||
|     while not lines[3].startswith('diff'): |  | ||||||
|         lines.pop(3) |  | ||||||
|     lines.insert(3, "") |  | ||||||
|     for line in lines: |  | ||||||
|         escaped = html.escape(line) |  | ||||||
|         if "//" in line: |  | ||||||
|             continue  |  | ||||||
|         if "#" in line: |  | ||||||
|             continue |  | ||||||
|         if "/*" in line: |  | ||||||
|             continue |  | ||||||
|         if "*/" in line: |  | ||||||
|             continue |  | ||||||
|         if line.startswith('+++') or line.startswith('---'): |  | ||||||
|             html_lines.append(f'<div style="color: #0000aa;">{escaped}</div>') |  | ||||||
|         elif line.startswith('@@'): |  | ||||||
|             html_lines.append(f'<div style="color: #005cc5;">{escaped}</div>') |  | ||||||
|         elif line.startswith('+'): |  | ||||||
|             html_lines.append(f'<div style="color: #22863a;">{escaped}</div>') |  | ||||||
|         elif line.startswith('-'): |  | ||||||
|             html_lines.append(f'<div style="color: #b31d28;">{escaped}</div>') |  | ||||||
|         elif line.startswith('\\'): |  | ||||||
|             html_lines.append(f'<div style="color: #6a737d;">{escaped}</div>') |  | ||||||
|         else: |  | ||||||
|             html_lines.append(f'<div>{escaped}</div>') |  | ||||||
|     html_lines.append('</div>') |  | ||||||
|     return '\n'.join(html_lines) |  | ||||||
| 
 |  | ||||||
| def parse_logs(): |  | ||||||
|     logs = [] |  | ||||||
|     if not os.path.exists(LOG_FILE): |  | ||||||
|         return [] |  | ||||||
|     lines = [] |  | ||||||
|     with open(LOG_FILE, "r", encoding="utf-8") as f: |  | ||||||
|         for line in f: |  | ||||||
|             if line.strip(): |  | ||||||
|                 if line.strip() not in lines: |  | ||||||
|                     lines.append(line.strip()) |  | ||||||
|                     logs.append(json.loads(line.strip())) |  | ||||||
|     return logs |  | ||||||
| 
 |  | ||||||
| def group_by_date(logs): |  | ||||||
|     grouped = {} |  | ||||||
|     for entry in logs: |  | ||||||
|         date = entry["date"] |  | ||||||
|         grouped.setdefault(date, []).append(entry) |  | ||||||
|     return dict(sorted(grouped.items(), reverse=True)) |  | ||||||
| 
 |  | ||||||
| def get_git_diff(commit_hash): |  | ||||||
|     try: |  | ||||||
|         result = subprocess.run( |  | ||||||
|             ["git", "-C", REPO_ROOT, "show", commit_hash, "--no-color"], |  | ||||||
|             stdout=subprocess.PIPE, |  | ||||||
|             stderr=subprocess.PIPE, |  | ||||||
|             text=True, |  | ||||||
|             check=True, |  | ||||||
|         ) |  | ||||||
|         return result.stdout |  | ||||||
|     except subprocess.CalledProcessError as e: |  | ||||||
|         return f"Error retrieving diff: {e.stderr}" |  | ||||||
| 
 |  | ||||||
| def list_directory(path, base_url="/browse?path="): |  | ||||||
|     try: |  | ||||||
|         entries = os.listdir(path) |  | ||||||
|     except OSError: |  | ||||||
|         return "<div>Cannot access directory.</div>" |  | ||||||
| 
 |  | ||||||
|     entries.sort() |  | ||||||
|     content = "<ul>" |  | ||||||
|     # Parent directory link |  | ||||||
|     parent = os.path.dirname(path) |  | ||||||
|     if os.path.abspath(path) != REPO_ROOT: |  | ||||||
|         parent_rel = os.path.relpath(parent, REPO_ROOT) |  | ||||||
|         content += f"<li><a href='{base_url}{html.escape(parent_rel)}'>.. (parent directory)</a></li>" |  | ||||||
| 
 |  | ||||||
|     for entry in entries: |  | ||||||
|         full_path = os.path.join(path, entry) |  | ||||||
|         rel_path = os.path.relpath(full_path, REPO_ROOT) |  | ||||||
|         if os.path.isdir(full_path): |  | ||||||
|             content += f"<li>📁 <a href='{base_url}{html.escape(rel_path)}'>{html.escape(entry)}/</a></li>" |  | ||||||
|         else: |  | ||||||
|             content += f"<li>📄 <a href='{base_url}{html.escape(rel_path)}'>{html.escape(entry)}</a></li>" |  | ||||||
|     content += "</ul>" |  | ||||||
|     return content |  | ||||||
| 
 |  | ||||||
| def read_file_content(path): |  | ||||||
|     try: |  | ||||||
|         with open(path, "r", encoding="utf-8") as f: |  | ||||||
|             return f.read() |  | ||||||
|     except Exception as e: |  | ||||||
|         return f"Error reading file: {e}" |  | ||||||
| 
 |  | ||||||
| def get_language_class(filename): |  | ||||||
|     ext = os.path.splitext(filename)[1].lower() |  | ||||||
|     return { |  | ||||||
|         '.py': 'python', |  | ||||||
|         '.js': 'javascript', |  | ||||||
|         '.html': 'html', |  | ||||||
|         '.css': 'css', |  | ||||||
|         '.json': 'json', |  | ||||||
|         '.sh': 'bash', |  | ||||||
|         '.md': 'markdown', |  | ||||||
|         '.c': 'c', |  | ||||||
|         '.cpp': 'cpp', |  | ||||||
|         '.h': 'cpp', |  | ||||||
|         '.java': 'java', |  | ||||||
|         '.rb': 'ruby', |  | ||||||
|         '.go': 'go', |  | ||||||
|         '.php': 'php', |  | ||||||
|         '.rs': 'rust', |  | ||||||
|         '.ts': 'typescript', |  | ||||||
|         '.xml': 'xml', |  | ||||||
|         '.yml': 'yaml', |  | ||||||
|         '.yaml': 'yaml', |  | ||||||
|     }.get(ext, '') |  | ||||||
| 
 |  | ||||||
| class GitLogHandler(http.server.SimpleHTTPRequestHandler): |  | ||||||
|     def do_GET(self): |  | ||||||
|         parsed = urlparse(self.path) |  | ||||||
|         if parsed.path == "/": |  | ||||||
|             self.send_response(200) |  | ||||||
|             self.send_header("Content-type", "text/html") |  | ||||||
|             self.end_headers() |  | ||||||
|             logs = parse_logs() |  | ||||||
|             grouped = group_by_date(logs) |  | ||||||
|             content = "<p><a href='/browse'>Browse Files</a></p>" |  | ||||||
|             for date, commits in grouped.items(): |  | ||||||
|                 content += f"<div class='date-header'>{date}</div>" |  | ||||||
|                 for c in commits: |  | ||||||
|                     commit_link = f"/diff?hash={c['commit']}" |  | ||||||
|                     content += f""" |  | ||||||
|                     <div class='commit'> |  | ||||||
|                         <div><strong>{c['line'].splitlines()[0]}</strong></div> |  | ||||||
|                         <div class='hash'><a href='{commit_link}'>{c['commit']}</a></div> |  | ||||||
|                     </div> |  | ||||||
|                     """ |  | ||||||
|             self.wfile.write(HTML_TEMPLATE(content).encode("utf-8")) |  | ||||||
|         elif parsed.path == "/diff": |  | ||||||
|             qs = parse_qs(parsed.query) |  | ||||||
|             commit = qs.get("hash", [""])[0] |  | ||||||
|             diff = format_diff_to_html(get_git_diff(html.escape(commit))) |  | ||||||
|             diff_html = f"<h2>Commit: {commit}</h2><div class='diff'>{diff}</div><p><a href='/'>← Back to commits</a></p>" |  | ||||||
|             self.send_response(200) |  | ||||||
|             self.send_header("Content-type", "text/html") |  | ||||||
|             self.end_headers() |  | ||||||
|             self.wfile.write(HTML_TEMPLATE(diff_html).encode("utf-8")) |  | ||||||
|         elif parsed.path == "/browse": |  | ||||||
|             qs = parse_qs(parsed.query) |  | ||||||
|             rel_path = qs.get("path", [""])[0] |  | ||||||
|             abs_path = os.path.abspath(os.path.join(REPO_ROOT, rel_path)) |  | ||||||
|             # Security: prevent escaping the repo root |  | ||||||
|             if not abs_path.startswith(REPO_ROOT): |  | ||||||
|                 self.send_error(403, "Forbidden") |  | ||||||
|                 return |  | ||||||
| 
 |  | ||||||
|             if os.path.isdir(abs_path): |  | ||||||
|                 content = f"<h2>Browsing: /{html.escape(rel_path)}</h2>" |  | ||||||
|                 content += list_directory(abs_path) |  | ||||||
|                 content += "<p><a href='/'>← Back to commits</a></p>" |  | ||||||
|                 self.send_response(200) |  | ||||||
|                 self.send_header("Content-type", "text/html") |  | ||||||
|                 self.end_headers() |  | ||||||
|                 self.wfile.write(HTML_TEMPLATE(content).encode("utf-8")) |  | ||||||
|             elif os.path.isfile(abs_path): |  | ||||||
|                 file_content = read_file_content(abs_path) |  | ||||||
|                 lang_class = get_language_class(abs_path) |  | ||||||
|                 content = f"<h2>File: /{html.escape(rel_path)}</h2>" |  | ||||||
|                 content += ( |  | ||||||
|                     f"<pre style='background:#f1f1f1; padding:1em; border-radius:4px; overflow-x:auto;'>" |  | ||||||
|                     f"<code class='{lang_class}'>{html.escape(file_content)}</code></pre>" |  | ||||||
|                 ) |  | ||||||
|                 content += "<p><a href='{}'>← Back to directory</a></p>".format( |  | ||||||
|                     f"/browse?path={html.escape(os.path.dirname(rel_path))}" |  | ||||||
|                 ) |  | ||||||
|                 self.send_response(200) |  | ||||||
|                 self.send_header("Content-type", "text/html") |  | ||||||
|                 self.end_headers() |  | ||||||
|                 self.wfile.write(HTML_TEMPLATE(content).encode("utf-8")) |  | ||||||
|             else: |  | ||||||
|                 self.send_error(404, "Not found") |  | ||||||
|         else: |  | ||||||
|             self.send_error(404) |  | ||||||
| 
 |  | ||||||
| if __name__ == "__main__": |  | ||||||
|      |  | ||||||
|     while True: |  | ||||||
|         try: |  | ||||||
|             with socketserver.TCPServer(("", PORT), GitLogHandler) as httpd: |  | ||||||
|                 print(f"Serving at http://localhost:{PORT}") |  | ||||||
|                 httpd.serve_forever() |  | ||||||
|                 break |  | ||||||
|         except Exception as ex: |  | ||||||
|             print(ex) |  | ||||||
|             PORT += 1 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| @ -68,7 +68,7 @@ class ChannelService(BaseService): | |||||||
|         return channel |         return channel | ||||||
| 
 | 
 | ||||||
|     async def get_recent_users(self, channel_uid): |     async def get_recent_users(self, channel_uid): | ||||||
|         async for user in self.query("SELECT user.uid, user.username,user.color,user.last_ping,user.nick FROM channel_member INNER JOIN user ON user.uid = channel_member.user_uid WHERE channel_uid=:channel_uid ORDER BY last_ping DESC LIMIT 30", {"channel_uid": channel_uid}): |         async for user in self.query("SELECT user.uid, user.username,user.color,user.last_ping,user.nick FROM channel_member INNER JOIN user ON user.uid = channel_member.user_uid WHERE channel_uid=:channel_uid AND user.last_ping >= datetime('now', '-3 minutes') ORDER BY last_ping DESC LIMIT 30", {"channel_uid": channel_uid}): | ||||||
|             yield user |             yield user | ||||||
| 
 | 
 | ||||||
|     async def get_users(self, channel_uid): |     async def get_users(self, channel_uid): | ||||||
|  | |||||||
| @ -314,13 +314,9 @@ class RPCView(BaseView): | |||||||
|             self._require_login() |             self._require_login() | ||||||
| 
 | 
 | ||||||
|             results = [ |             results = [ | ||||||
|                     record.record async for record  in self.services.channel.get_online_users(channel_uid) |                     record async for record  in self.services.channel.get_recent_users(channel_uid) | ||||||
|             ] |             ] | ||||||
|             for result in results: |             sorted(results, key=lambda x: x["nick"]) | ||||||
|                 del result['email'] |  | ||||||
|                 del result['password'] |  | ||||||
|                 del result['deleted_at'] |  | ||||||
|                 del result['updated_at'] |  | ||||||
|             return results |             return results | ||||||
| 
 | 
 | ||||||
|         async def echo(self, obj): |         async def echo(self, obj): | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user