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