deployment, frontend updates
This commit is contained in:
12
Dockerfile
Normal file
12
Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt gunicorn
|
||||
|
||||
COPY dmapp/dmweb /app/dmweb
|
||||
|
||||
EXPOSE 10000
|
||||
|
||||
CMD ["gunicorn", "-b", "0.0.0.0:10000", "-w", "2", "dmweb:create_app()"]
|
||||
97
dmapp/dmdb/sync.py
Normal file
97
dmapp/dmdb/sync.py
Normal file
@@ -0,0 +1,97 @@
|
||||
"""
|
||||
dmsync - MongoDB Change Streams sync daemon
|
||||
Watches local deskmeter database and pushes changes to remote MongoDB
|
||||
|
||||
Requires local MongoDB to be configured as a replica set.
|
||||
Uses resume tokens to continue from last position after restart.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from pymongo import MongoClient
|
||||
from pymongo.errors import PyMongoError
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s"
|
||||
)
|
||||
log = logging.getLogger("dmsync")
|
||||
|
||||
RESUME_TOKEN_FILE = Path.home() / ".dmsync-resume-token"
|
||||
REMOTE_HOST = os.environ.get("DMSYNC_REMOTE_HOST", "mcrn.ar")
|
||||
REMOTE_PORT = int(os.environ.get("DMSYNC_REMOTE_PORT", 27017))
|
||||
COLLECTIONS = ("switch", "task", "task_history", "state")
|
||||
|
||||
|
||||
def load_resume_token():
|
||||
"""Load resume token from file if exists."""
|
||||
if RESUME_TOKEN_FILE.exists():
|
||||
try:
|
||||
return json.loads(RESUME_TOKEN_FILE.read_text())
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
log.warning(f"Failed to load resume token: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def save_resume_token(token):
|
||||
"""Persist resume token to file."""
|
||||
try:
|
||||
RESUME_TOKEN_FILE.write_text(json.dumps(token))
|
||||
except IOError as e:
|
||||
log.error(f"Failed to save resume token: {e}")
|
||||
|
||||
|
||||
def sync():
|
||||
"""Main sync loop using Change Streams."""
|
||||
log.info(f"Connecting to local MongoDB...")
|
||||
local = MongoClient()
|
||||
|
||||
log.info(f"Connecting to remote MongoDB at {REMOTE_HOST}:{REMOTE_PORT}...")
|
||||
remote = MongoClient(REMOTE_HOST, REMOTE_PORT)
|
||||
|
||||
local_db = local.deskmeter
|
||||
remote_db = remote.deskmeter
|
||||
|
||||
resume_token = load_resume_token()
|
||||
if resume_token:
|
||||
log.info("Resuming from saved token")
|
||||
|
||||
watch_kwargs = {"resume_after": resume_token} if resume_token else {}
|
||||
|
||||
# Watch for inserts, updates, and replaces on the database
|
||||
pipeline = [{"$match": {"operationType": {"$in": ["insert", "update", "replace"]}}}]
|
||||
|
||||
log.info(f"Watching collections: {', '.join(COLLECTIONS)}")
|
||||
|
||||
try:
|
||||
with local_db.watch(pipeline, **watch_kwargs) as stream:
|
||||
for change in stream:
|
||||
collection = change["ns"]["coll"]
|
||||
|
||||
if collection not in COLLECTIONS:
|
||||
continue
|
||||
|
||||
doc = change.get("fullDocument")
|
||||
if not doc:
|
||||
continue
|
||||
|
||||
# Upsert to remote
|
||||
result = remote_db[collection].replace_one(
|
||||
{"_id": doc["_id"]}, doc, upsert=True
|
||||
)
|
||||
|
||||
action = "inserted" if result.upserted_id else "updated"
|
||||
log.info(f"{collection}: {action} {doc['_id']}")
|
||||
|
||||
save_resume_token(stream.resume_token)
|
||||
|
||||
except PyMongoError as e:
|
||||
log.error(f"MongoDB error: {e}")
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
log.info("Starting dmsync daemon")
|
||||
sync()
|
||||
@@ -1,8 +1,18 @@
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from flask import Blueprint, render_template, jsonify
|
||||
from flask import Blueprint, jsonify, render_template
|
||||
|
||||
from .get_period_times import get_period_totals, task_or_none, timezone, get_work_period_totals, get_current_task_info, convert_seconds, get_task_time_seconds, get_task_blocks_calendar, get_raw_switches
|
||||
from .get_period_times import (
|
||||
convert_seconds,
|
||||
get_current_task_info,
|
||||
get_period_totals,
|
||||
get_raw_switches,
|
||||
get_task_blocks_calendar,
|
||||
get_task_time_seconds,
|
||||
get_work_period_totals,
|
||||
task_or_none,
|
||||
timezone,
|
||||
)
|
||||
|
||||
dmbp = Blueprint("deskmeter", __name__, url_prefix="/", template_folder="templates")
|
||||
|
||||
@@ -22,7 +32,7 @@ def calendar_view(scope="daily", year=None, month=None, day=None):
|
||||
from flask import request
|
||||
|
||||
task = None
|
||||
grid = int(request.args.get('grid', 1)) # Grid hours: 1, 3, or 6
|
||||
grid = int(request.args.get("grid", 1)) # Grid hours: 1, 3, or 6
|
||||
if grid not in [1, 3, 6]:
|
||||
grid = 1
|
||||
|
||||
@@ -33,12 +43,16 @@ def calendar_view(scope="daily", year=None, month=None, day=None):
|
||||
if not day:
|
||||
day = datetime.today().day
|
||||
|
||||
base_date = datetime(year, month, day).replace(hour=0, minute=0, second=0, tzinfo=timezone)
|
||||
base_date = datetime(year, month, day).replace(
|
||||
hour=0, minute=0, second=0, tzinfo=timezone
|
||||
)
|
||||
|
||||
if scope == "daily":
|
||||
start = base_date
|
||||
end = base_date.replace(hour=23, minute=59, second=59)
|
||||
blocks = get_task_blocks_calendar(start, end, task, min_block_seconds=60, grid_hours=grid)
|
||||
blocks = get_task_blocks_calendar(
|
||||
start, end, task, min_block_seconds=60, grid_hours=grid
|
||||
)
|
||||
prev_date = base_date - timedelta(days=1)
|
||||
next_date = base_date + timedelta(days=1)
|
||||
days = [base_date]
|
||||
@@ -46,7 +60,9 @@ def calendar_view(scope="daily", year=None, month=None, day=None):
|
||||
elif scope == "weekly":
|
||||
start = base_date - timedelta(days=base_date.weekday())
|
||||
end = start + timedelta(days=6, hours=23, minutes=59, seconds=59)
|
||||
blocks = get_task_blocks_calendar(start, end, task, min_block_seconds=300, grid_hours=grid)
|
||||
blocks = get_task_blocks_calendar(
|
||||
start, end, task, min_block_seconds=300, grid_hours=grid
|
||||
)
|
||||
prev_date = start - timedelta(days=7)
|
||||
next_date = start + timedelta(days=7)
|
||||
days = [start + timedelta(days=i) for i in range(7)]
|
||||
@@ -57,7 +73,9 @@ def calendar_view(scope="daily", year=None, month=None, day=None):
|
||||
end = datetime(year + 1, 1, 1, tzinfo=timezone) - timedelta(seconds=1)
|
||||
else:
|
||||
end = datetime(year, month + 1, 1, tzinfo=timezone) - timedelta(seconds=1)
|
||||
blocks = get_task_blocks_calendar(start, end, task, min_block_seconds=600, grid_hours=grid)
|
||||
blocks = get_task_blocks_calendar(
|
||||
start, end, task, min_block_seconds=600, grid_hours=grid
|
||||
)
|
||||
if month == 1:
|
||||
prev_date = datetime(year - 1, 12, 1, tzinfo=timezone)
|
||||
else:
|
||||
@@ -75,7 +93,9 @@ def calendar_view(scope="daily", year=None, month=None, day=None):
|
||||
scope = "daily"
|
||||
start = base_date
|
||||
end = base_date.replace(hour=23, minute=59, second=59)
|
||||
blocks = get_task_blocks_calendar(start, end, task, min_block_seconds=60, grid_hours=grid)
|
||||
blocks = get_task_blocks_calendar(
|
||||
start, end, task, min_block_seconds=60, grid_hours=grid
|
||||
)
|
||||
prev_date = base_date - timedelta(days=1)
|
||||
next_date = base_date + timedelta(days=1)
|
||||
days = [base_date]
|
||||
@@ -91,7 +111,7 @@ def calendar_view(scope="daily", year=None, month=None, day=None):
|
||||
next_date=next_date,
|
||||
days=days,
|
||||
grid=grid,
|
||||
auto_refresh=False
|
||||
auto_refresh=False,
|
||||
)
|
||||
|
||||
|
||||
@@ -111,7 +131,9 @@ def switches_view(scope="daily", year=None, month=None, day=None):
|
||||
if not day:
|
||||
day = datetime.today().day
|
||||
|
||||
base_date = datetime(year, month, day).replace(hour=0, minute=0, second=0, tzinfo=timezone)
|
||||
base_date = datetime(year, month, day).replace(
|
||||
hour=0, minute=0, second=0, tzinfo=timezone
|
||||
)
|
||||
|
||||
if scope == "daily":
|
||||
start = base_date
|
||||
@@ -157,7 +179,7 @@ def switches_view(scope="daily", year=None, month=None, day=None):
|
||||
base_date=base_date,
|
||||
prev_date=prev_date,
|
||||
next_date=next_date,
|
||||
auto_refresh=False
|
||||
auto_refresh=False,
|
||||
)
|
||||
|
||||
|
||||
@@ -176,13 +198,17 @@ def index(task=None):
|
||||
|
||||
# Get current task info
|
||||
current_task_id, current_task_path = get_current_task_info()
|
||||
current_task_time = None
|
||||
if current_task_id:
|
||||
total_seconds = get_task_time_seconds(start, end, current_task_id)
|
||||
if total_seconds > 0:
|
||||
current_task_time = convert_seconds(total_seconds)
|
||||
|
||||
return render_template("main.html", rows=rows, current_task_path=current_task_path, current_task_time=current_task_time, auto_refresh=True)
|
||||
# Get all tasks worked on today
|
||||
task_rows = get_work_period_totals(start, end)
|
||||
|
||||
return render_template(
|
||||
"main.html",
|
||||
rows=rows,
|
||||
current_task_path=current_task_path,
|
||||
task_rows=task_rows,
|
||||
auto_refresh=True,
|
||||
)
|
||||
|
||||
|
||||
@dmbp.route("/api/current_task")
|
||||
@@ -191,10 +217,9 @@ def api_current_task():
|
||||
JSON API endpoint returning current task information
|
||||
"""
|
||||
current_task_id, current_task_path = get_current_task_info()
|
||||
return jsonify({
|
||||
"task_id": current_task_id,
|
||||
"task_path": current_task_path or "no task"
|
||||
})
|
||||
return jsonify(
|
||||
{"task_id": current_task_id, "task_path": current_task_path or "no task"}
|
||||
)
|
||||
|
||||
|
||||
@dmbp.route("/api/today")
|
||||
@@ -212,13 +237,16 @@ def api_today(task=None):
|
||||
|
||||
# Get current task info
|
||||
current_task_id, current_task_path = get_current_task_info()
|
||||
current_task_time = None
|
||||
if current_task_id:
|
||||
total_seconds = get_task_time_seconds(start, end, current_task_id)
|
||||
if total_seconds > 0:
|
||||
current_task_time = convert_seconds(total_seconds)
|
||||
|
||||
return render_template("main_content.html", rows=rows, current_task_path=current_task_path, current_task_time=current_task_time)
|
||||
# Get all tasks worked on today
|
||||
task_rows = get_work_period_totals(start, end)
|
||||
|
||||
return render_template(
|
||||
"main_content.html",
|
||||
rows=rows,
|
||||
current_task_path=current_task_path,
|
||||
task_rows=task_rows,
|
||||
)
|
||||
|
||||
|
||||
@dmbp.route("/day/<int:month>/<int:day>")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import os
|
||||
from collections import Counter, defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
@@ -8,7 +9,7 @@ from pymongo import MongoClient
|
||||
timezone = ZoneInfo("America/Argentina/Buenos_Aires")
|
||||
utctz = ZoneInfo("UTC")
|
||||
|
||||
client = MongoClient()
|
||||
client = MongoClient(os.environ.get("MONGODB_HOST", "localhost"))
|
||||
db = client.deskmeter
|
||||
switches = db.switch
|
||||
tasks = db.task
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
<html>
|
||||
<head>
|
||||
<!-- <link rel="stylesheet" href="{{ url_for('static', filename='styles/dm.css') }}"> -->
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
{% block head %} {% endblock %}
|
||||
|
||||
<style>
|
||||
body
|
||||
{
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh; /* This ensures that the container takes the full height of the viewport */
|
||||
margin: 0; /* Remove default margin */
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
background-color: #1a1a1a;
|
||||
color: #e0e0e0;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
@@ -22,24 +23,25 @@
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
gap: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #444;
|
||||
}
|
||||
|
||||
.nav-bar a {
|
||||
text-decoration: none;
|
||||
color: #6b9bd1;
|
||||
font-size: 11pt;
|
||||
font-size: 12pt;
|
||||
padding: 5px 10px;
|
||||
border-radius: 3px;
|
||||
background-color: #2a2a2a;
|
||||
}
|
||||
|
||||
.nav-bar a:hover {
|
||||
background-color: #3a3a3a;
|
||||
background-color: #2a2a2a;
|
||||
}
|
||||
|
||||
.grey {
|
||||
color: #888;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.blue {
|
||||
@@ -47,10 +49,33 @@
|
||||
}
|
||||
|
||||
table {
|
||||
font-size: 84pt
|
||||
font-size: clamp(14pt, 3vw, 28pt);
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
td {
|
||||
padding-right: 100px;
|
||||
padding: 0.1em 0.5em;
|
||||
}
|
||||
|
||||
td:first-child {
|
||||
text-align: right;
|
||||
padding-right: 0.3em;
|
||||
}
|
||||
|
||||
td:last-child {
|
||||
text-align: left;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.workspace-table {
|
||||
font-size: clamp(10pt, 2vw, 18pt);
|
||||
margin-top: 20px;
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid #444;
|
||||
}
|
||||
|
||||
.workspace-table td {
|
||||
padding: 0.05em 0.4em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -58,10 +83,11 @@
|
||||
<script>
|
||||
function refreshData() {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', '/api/today', true);
|
||||
xhr.onreadystatechange = function() {
|
||||
xhr.open("GET", "/api/today", true);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4 && xhr.status === 200) {
|
||||
document.getElementById('content-container').innerHTML = xhr.responseText;
|
||||
document.getElementById("content-container").innerHTML =
|
||||
xhr.responseText;
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
@@ -71,10 +97,8 @@
|
||||
setInterval(refreshData, 5000);
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- agregar función que me diga cuanto tiempo hace que esta activo el escritorio
|
||||
(calcular el delta con el ultimo switch y pasarlo a mm:ss) -->
|
||||
|
||||
@@ -87,9 +111,7 @@
|
||||
</div>
|
||||
|
||||
<div id="content-container">
|
||||
{% block content %}
|
||||
{% include 'main_content.html' %}
|
||||
{% endblock %}
|
||||
{% block content %} {% include 'main_content.html' %} {% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,23 +1,39 @@
|
||||
{% if current_task_path and current_task_time %}
|
||||
<div id="current-task-info" style="font-size: 48pt; margin-bottom: 40px; text-align: center;">
|
||||
<div style="color: #e0e0e0;">{{ current_task_path }}</div>
|
||||
<div style="color: #999; font-size: 36pt;">{{ current_task_time }}</div>
|
||||
{% if current_task_path %}
|
||||
<div
|
||||
id="current-task-info"
|
||||
style="
|
||||
font-size: clamp(10pt, 2vw, 16pt);
|
||||
margin-bottom: 15px;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
"
|
||||
>
|
||||
Current: <span style="color: #6b9bd1">{{ current_task_path }}</span>
|
||||
</div>
|
||||
{% endif %} {% if task_rows %}
|
||||
<div id="task-list" style="margin-bottom: 25px">
|
||||
<table>
|
||||
<tbody>
|
||||
{% for row in task_rows %}
|
||||
<tr>
|
||||
<td>{{ row["ws"] }}</td>
|
||||
<td>{{ row["total"] }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
<table>
|
||||
<tbody>
|
||||
{% for row in rows %}
|
||||
{% if row["ws"] in ['Away', 'Other'] %}
|
||||
{% set my_class = 'grey' %}
|
||||
{% elif row["ws"] in ['Active', 'Idle'] %}
|
||||
{% set my_class = 'blue' %}
|
||||
{% else %}
|
||||
{% set my_class = '' %}
|
||||
{% endif %}
|
||||
|
||||
<table class="workspace-table">
|
||||
<tbody>
|
||||
{% for row in rows %} {% if row["ws"] in ['Away', 'Other'] %} {% set
|
||||
my_class = 'grey' %} {% elif row["ws"] in ['Active', 'Idle'] %} {% set
|
||||
my_class = 'blue' %} {% else %} {% set my_class = '' %} {% endif %}
|
||||
<tr>
|
||||
<td class="{{my_class}}" >{{ row["ws"] }}</td>
|
||||
<td class="{{my_class}}" >{{ row["total"] }}</td>
|
||||
<td class="{{my_class}}">{{ row["ws"] }}</td>
|
||||
<td class="{{my_class}}">{{ row["total"] }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
32
docker-compose.yml
Normal file
32
docker-compose.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
services:
|
||||
dmweb:
|
||||
build: .
|
||||
image: registry.mcrn.ar/dmweb:latest
|
||||
container_name: dmweb
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- mongo
|
||||
environment:
|
||||
- MONGODB_HOST=mongo
|
||||
networks:
|
||||
- gateway
|
||||
- internal
|
||||
|
||||
mongo:
|
||||
image: mongo:7
|
||||
container_name: dmweb-mongo
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- mongo-data:/data/db
|
||||
ports:
|
||||
- "27017:27017"
|
||||
networks:
|
||||
- internal
|
||||
|
||||
volumes:
|
||||
mongo-data:
|
||||
|
||||
networks:
|
||||
gateway:
|
||||
external: true
|
||||
internal:
|
||||
76
docs/architecture/01-system-overview.dot
Normal file
76
docs/architecture/01-system-overview.dot
Normal file
@@ -0,0 +1,76 @@
|
||||
digraph SystemOverview {
|
||||
// Graph settings
|
||||
rankdir=TB;
|
||||
compound=true;
|
||||
fontname="Helvetica";
|
||||
node [fontname="Helvetica", fontsize=11];
|
||||
edge [fontname="Helvetica", fontsize=10];
|
||||
|
||||
// Title
|
||||
labelloc="t";
|
||||
label="Deskmeter - System Architecture";
|
||||
fontsize=16;
|
||||
|
||||
// Styling
|
||||
node [shape=box, style="rounded,filled"];
|
||||
|
||||
// Local Machine
|
||||
subgraph cluster_local {
|
||||
label="Local Machine (Bare Metal)";
|
||||
style=filled;
|
||||
color="#E8F5E9";
|
||||
fillcolor="#E8F5E9";
|
||||
|
||||
dmcore [label="dmcore\n(Workspace Tracker)", fillcolor="#C8E6C9"];
|
||||
dmweb_local [label="dmweb\n(Dev Server)", fillcolor="#C8E6C9"];
|
||||
dmsync [label="dmsync\n(Change Streams)", fillcolor="#DCEDC8"];
|
||||
mongo_local [label="MongoDB\n(Replica Set)", fillcolor="#FFECB3", shape=cylinder];
|
||||
}
|
||||
|
||||
// OS Integration
|
||||
subgraph cluster_os {
|
||||
label="OS Integration";
|
||||
style=dashed;
|
||||
color=gray;
|
||||
|
||||
wmctrl [label="wmctrl\n(X11 Workspaces)", fillcolor="#E3F2FD"];
|
||||
gnome_ext [label="GNOME Extension\n(Panel Indicator)", fillcolor="#E3F2FD"];
|
||||
}
|
||||
|
||||
// Remote (AWS)
|
||||
subgraph cluster_remote {
|
||||
label="AWS EC2 (mcrn.ar)";
|
||||
style=filled;
|
||||
color="#FFF3E0";
|
||||
fillcolor="#FFF3E0";
|
||||
|
||||
subgraph cluster_docker {
|
||||
label="Docker Compose";
|
||||
style=dashed;
|
||||
color="#F57C00";
|
||||
|
||||
dmweb_remote [label="dmweb\n(Flask + Gunicorn)", fillcolor="#FFE0B2"];
|
||||
mongo_remote [label="MongoDB\n(Docker)", fillcolor="#FFECB3", shape=cylinder];
|
||||
}
|
||||
|
||||
nginx [label="Nginx\n(Gateway)", fillcolor="#BBDEFB"];
|
||||
}
|
||||
|
||||
// External
|
||||
browser [label="Browser\n(Portfolio Viewer)", fillcolor="#F3E5F5"];
|
||||
|
||||
// Local connections
|
||||
wmctrl -> dmcore [label="workspace\ndetection", color="#388E3C"];
|
||||
dmcore -> mongo_local [label="write\nswitches", color="#FFA000"];
|
||||
dmweb_local -> mongo_local [label="read", style=dashed, color="#666"];
|
||||
gnome_ext -> dmweb_local [label="API poll", color="#1976D2", style=dashed];
|
||||
|
||||
// Sync connection
|
||||
mongo_local -> dmsync [label="Change\nStreams", color="#7B1FA2"];
|
||||
dmsync -> mongo_remote [label="push\nchanges", color="#7B1FA2", style=bold];
|
||||
|
||||
// Remote connections
|
||||
dmweb_remote -> mongo_remote [label="read", style=dashed, color="#666"];
|
||||
nginx -> dmweb_remote [label="proxy", color="#1976D2"];
|
||||
browser -> nginx [label="HTTPS", color="#1976D2"];
|
||||
}
|
||||
173
docs/architecture/01-system-overview.svg
Normal file
173
docs/architecture/01-system-overview.svg
Normal file
@@ -0,0 +1,173 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.1 (0)
|
||||
-->
|
||||
<!-- Title: SystemOverview Pages: 1 -->
|
||||
<svg width="467pt" height="624pt"
|
||||
viewBox="0.00 0.00 467.00 624.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 619.69)">
|
||||
<title>SystemOverview</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-619.69 463,-619.69 463,4 -4,4"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="229.5" y="-596.49" font-family="Helvetica,sans-Serif" font-size="16.00">Deskmeter - System Architecture</text>
|
||||
<g id="clust1" class="cluster">
|
||||
<title>cluster_local</title>
|
||||
<polygon fill="#e8f5e9" stroke="#e8f5e9" points="185,-125.62 185,-457.19 434,-457.19 434,-125.62 185,-125.62"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="309.5" y="-437.99" font-family="Helvetica,sans-Serif" font-size="16.00">Local Machine (Bare Metal)</text>
|
||||
</g>
|
||||
<g id="clust2" class="cluster">
|
||||
<title>cluster_os</title>
|
||||
<polygon fill="none" stroke="gray" stroke-dasharray="5,2" points="185,-500.69 185,-580.19 451,-580.19 451,-500.69 185,-500.69"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="318" y="-560.99" font-family="Helvetica,sans-Serif" font-size="16.00">OS Integration</text>
|
||||
</g>
|
||||
<g id="clust3" class="cluster">
|
||||
<title>cluster_remote</title>
|
||||
<polygon fill="#fff3e0" stroke="#fff3e0" points="8,-8 8,-334.19 177,-334.19 177,-8 8,-8"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="92.5" y="-314.99" font-family="Helvetica,sans-Serif" font-size="16.00">AWS EC2 (mcrn.ar)</text>
|
||||
</g>
|
||||
<g id="clust4" class="cluster">
|
||||
<title>cluster_docker</title>
|
||||
<polygon fill="none" stroke="#f57c00" stroke-dasharray="5,2" points="16,-16 16,-205.12 169,-205.12 169,-16 16,-16"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="92.5" y="-185.93" font-family="Helvetica,sans-Serif" font-size="16.00">Docker Compose</text>
|
||||
</g>
|
||||
<!-- dmcore -->
|
||||
<g id="node1" class="node">
|
||||
<title>dmcore</title>
|
||||
<path fill="#c8e6c9" stroke="black" d="M309.25,-421.69C309.25,-421.69 204.75,-421.69 204.75,-421.69 198.75,-421.69 192.75,-415.69 192.75,-409.69 192.75,-409.69 192.75,-397.69 192.75,-397.69 192.75,-391.69 198.75,-385.69 204.75,-385.69 204.75,-385.69 309.25,-385.69 309.25,-385.69 315.25,-385.69 321.25,-391.69 321.25,-397.69 321.25,-397.69 321.25,-409.69 321.25,-409.69 321.25,-415.69 315.25,-421.69 309.25,-421.69"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="257" y="-406.74" font-family="Helvetica,sans-Serif" font-size="11.00">dmcore</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="257" y="-393.24" font-family="Helvetica,sans-Serif" font-size="11.00">(Workspace Tracker)</text>
|
||||
</g>
|
||||
<!-- mongo_local -->
|
||||
<g id="node4" class="node">
|
||||
<title>mongo_local</title>
|
||||
<path fill="#ffecb3" stroke="black" d="M300.25,-300.38C300.25,-302.79 280.86,-304.75 257,-304.75 233.14,-304.75 213.75,-302.79 213.75,-300.38 213.75,-300.38 213.75,-261 213.75,-261 213.75,-258.59 233.14,-256.62 257,-256.62 280.86,-256.62 300.25,-258.59 300.25,-261 300.25,-261 300.25,-300.38 300.25,-300.38"/>
|
||||
<path fill="none" stroke="black" d="M300.25,-300.38C300.25,-297.96 280.86,-296 257,-296 233.14,-296 213.75,-297.96 213.75,-300.38"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="257" y="-283.74" font-family="Helvetica,sans-Serif" font-size="11.00">MongoDB</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="257" y="-270.24" font-family="Helvetica,sans-Serif" font-size="11.00">(Replica Set)</text>
|
||||
</g>
|
||||
<!-- dmcore->mongo_local -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>dmcore->mongo_local</title>
|
||||
<path fill="none" stroke="#ffa000" d="M257,-385.35C257,-367.52 257,-339.24 257,-316.72"/>
|
||||
<polygon fill="#ffa000" stroke="#ffa000" points="260.5,-316.74 257,-306.74 253.5,-316.74 260.5,-316.74"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="278.38" y="-358.19" font-family="Helvetica,sans-Serif" font-size="10.00">write</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="278.38" y="-345.44" font-family="Helvetica,sans-Serif" font-size="10.00">switches</text>
|
||||
</g>
|
||||
<!-- dmweb_local -->
|
||||
<g id="node2" class="node">
|
||||
<title>dmweb_local</title>
|
||||
<path fill="#c8e6c9" stroke="black" d="M414.25,-421.69C414.25,-421.69 351.75,-421.69 351.75,-421.69 345.75,-421.69 339.75,-415.69 339.75,-409.69 339.75,-409.69 339.75,-397.69 339.75,-397.69 339.75,-391.69 345.75,-385.69 351.75,-385.69 351.75,-385.69 414.25,-385.69 414.25,-385.69 420.25,-385.69 426.25,-391.69 426.25,-397.69 426.25,-397.69 426.25,-409.69 426.25,-409.69 426.25,-415.69 420.25,-421.69 414.25,-421.69"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="383" y="-406.74" font-family="Helvetica,sans-Serif" font-size="11.00">dmweb</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="383" y="-393.24" font-family="Helvetica,sans-Serif" font-size="11.00">(Dev Server)</text>
|
||||
</g>
|
||||
<!-- dmweb_local->mongo_local -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>dmweb_local->mongo_local</title>
|
||||
<path fill="none" stroke="#666666" stroke-dasharray="5,2" d="M364.95,-385.35C345.23,-366.41 313.24,-335.7 289.2,-312.61"/>
|
||||
<polygon fill="#666666" stroke="#666666" points="291.79,-310.24 282.15,-305.84 286.94,-315.29 291.79,-310.24"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="356.64" y="-351.81" font-family="Helvetica,sans-Serif" font-size="10.00">read</text>
|
||||
</g>
|
||||
<!-- dmsync -->
|
||||
<g id="node3" class="node">
|
||||
<title>dmsync</title>
|
||||
<path fill="#dcedc8" stroke="black" d="M299.88,-169.62C299.88,-169.62 208.12,-169.62 208.12,-169.62 202.12,-169.62 196.12,-163.62 196.12,-157.62 196.12,-157.62 196.12,-145.62 196.12,-145.62 196.12,-139.62 202.12,-133.62 208.12,-133.62 208.12,-133.62 299.88,-133.62 299.88,-133.62 305.88,-133.62 311.88,-139.62 311.88,-145.62 311.88,-145.62 311.88,-157.62 311.88,-157.62 311.88,-163.62 305.88,-169.62 299.88,-169.62"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="254" y="-154.68" font-family="Helvetica,sans-Serif" font-size="11.00">dmsync</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="254" y="-141.18" font-family="Helvetica,sans-Serif" font-size="11.00">(Change Streams)</text>
|
||||
</g>
|
||||
<!-- mongo_remote -->
|
||||
<g id="node8" class="node">
|
||||
<title>mongo_remote</title>
|
||||
<path fill="#ffecb3" stroke="black" d="M154.25,-67.75C154.25,-70.16 138.9,-72.12 120,-72.12 101.1,-72.12 85.75,-70.16 85.75,-67.75 85.75,-67.75 85.75,-28.38 85.75,-28.38 85.75,-25.96 101.1,-24 120,-24 138.9,-24 154.25,-25.96 154.25,-28.38 154.25,-28.38 154.25,-67.75 154.25,-67.75"/>
|
||||
<path fill="none" stroke="black" d="M154.25,-67.75C154.25,-65.34 138.9,-63.38 120,-63.38 101.1,-63.38 85.75,-65.34 85.75,-67.75"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="120" y="-51.11" font-family="Helvetica,sans-Serif" font-size="11.00">MongoDB</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="120" y="-37.61" font-family="Helvetica,sans-Serif" font-size="11.00">(Docker)</text>
|
||||
</g>
|
||||
<!-- dmsync->mongo_remote -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>dmsync->mongo_remote</title>
|
||||
<path fill="none" stroke="#7b1fa2" stroke-width="2" d="M230.96,-133.17C211.01,-118.04 181.75,-95.87 158.29,-78.08"/>
|
||||
<polygon fill="#7b1fa2" stroke="#7b1fa2" stroke-width="2" points="161.78,-76.34 151.7,-73.09 157.55,-81.92 161.78,-76.34"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="227.08" y="-106.12" font-family="Helvetica,sans-Serif" font-size="10.00">push</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="227.08" y="-93.38" font-family="Helvetica,sans-Serif" font-size="10.00">changes</text>
|
||||
</g>
|
||||
<!-- mongo_local->dmsync -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>mongo_local->dmsync</title>
|
||||
<path fill="none" stroke="#7b1fa2" d="M256.45,-256.38C255.95,-235.25 255.21,-204.03 254.68,-181.35"/>
|
||||
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="258.18,-181.5 254.45,-171.58 251.18,-181.66 258.18,-181.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="276.28" y="-229.12" font-family="Helvetica,sans-Serif" font-size="10.00">Change</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="276.28" y="-216.38" font-family="Helvetica,sans-Serif" font-size="10.00">Streams</text>
|
||||
</g>
|
||||
<!-- wmctrl -->
|
||||
<g id="node5" class="node">
|
||||
<title>wmctrl</title>
|
||||
<path fill="#e3f2fd" stroke="black" d="M296.88,-544.69C296.88,-544.69 205.12,-544.69 205.12,-544.69 199.12,-544.69 193.12,-538.69 193.12,-532.69 193.12,-532.69 193.12,-520.69 193.12,-520.69 193.12,-514.69 199.12,-508.69 205.12,-508.69 205.12,-508.69 296.88,-508.69 296.88,-508.69 302.88,-508.69 308.88,-514.69 308.88,-520.69 308.88,-520.69 308.88,-532.69 308.88,-532.69 308.88,-538.69 302.88,-544.69 296.88,-544.69"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="251" y="-529.74" font-family="Helvetica,sans-Serif" font-size="11.00">wmctrl</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="251" y="-516.24" font-family="Helvetica,sans-Serif" font-size="11.00">(X11 Workspaces)</text>
|
||||
</g>
|
||||
<!-- wmctrl->dmcore -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>wmctrl->dmcore</title>
|
||||
<path fill="none" stroke="#388e3c" d="M251.86,-508.35C252.83,-488.76 254.43,-456.58 255.58,-433.26"/>
|
||||
<polygon fill="#388e3c" stroke="#388e3c" points="259.07,-433.66 256.07,-423.5 252.08,-433.32 259.07,-433.66"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="280.25" y="-481.19" font-family="Helvetica,sans-Serif" font-size="10.00">workspace</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="280.25" y="-468.44" font-family="Helvetica,sans-Serif" font-size="10.00">detection</text>
|
||||
</g>
|
||||
<!-- gnome_ext -->
|
||||
<g id="node6" class="node">
|
||||
<title>gnome_ext</title>
|
||||
<path fill="#e3f2fd" stroke="black" d="M430.88,-544.69C430.88,-544.69 339.12,-544.69 339.12,-544.69 333.12,-544.69 327.12,-538.69 327.12,-532.69 327.12,-532.69 327.12,-520.69 327.12,-520.69 327.12,-514.69 333.12,-508.69 339.12,-508.69 339.12,-508.69 430.88,-508.69 430.88,-508.69 436.88,-508.69 442.88,-514.69 442.88,-520.69 442.88,-520.69 442.88,-532.69 442.88,-532.69 442.88,-538.69 436.88,-544.69 430.88,-544.69"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="385" y="-529.74" font-family="Helvetica,sans-Serif" font-size="11.00">GNOME Extension</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="385" y="-516.24" font-family="Helvetica,sans-Serif" font-size="11.00">(Panel Indicator)</text>
|
||||
</g>
|
||||
<!-- gnome_ext->dmweb_local -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>gnome_ext->dmweb_local</title>
|
||||
<path fill="none" stroke="#1976d2" stroke-dasharray="5,2" d="M384.71,-508.35C384.39,-488.76 383.86,-456.58 383.47,-433.26"/>
|
||||
<polygon fill="#1976d2" stroke="#1976d2" points="386.98,-433.45 383.31,-423.51 379.98,-433.56 386.98,-433.45"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="402.78" y="-474.81" font-family="Helvetica,sans-Serif" font-size="10.00">API poll</text>
|
||||
</g>
|
||||
<!-- dmweb_remote -->
|
||||
<g id="node7" class="node">
|
||||
<title>dmweb_remote</title>
|
||||
<path fill="#ffe0b2" stroke="black" d="M149,-169.62C149,-169.62 55,-169.62 55,-169.62 49,-169.62 43,-163.62 43,-157.62 43,-157.62 43,-145.62 43,-145.62 43,-139.62 49,-133.62 55,-133.62 55,-133.62 149,-133.62 149,-133.62 155,-133.62 161,-139.62 161,-145.62 161,-145.62 161,-157.62 161,-157.62 161,-163.62 155,-169.62 149,-169.62"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="102" y="-154.68" font-family="Helvetica,sans-Serif" font-size="11.00">dmweb</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="102" y="-141.18" font-family="Helvetica,sans-Serif" font-size="11.00">(Flask + Gunicorn)</text>
|
||||
</g>
|
||||
<!-- dmweb_remote->mongo_remote -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>dmweb_remote->mongo_remote</title>
|
||||
<path fill="none" stroke="#666666" stroke-dasharray="5,2" d="M105.05,-133.4C107.47,-119.79 110.91,-100.35 113.88,-83.57"/>
|
||||
<polygon fill="#666666" stroke="#666666" points="117.29,-84.43 115.59,-73.97 110.39,-83.21 117.29,-84.43"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="123.49" y="-99.75" font-family="Helvetica,sans-Serif" font-size="10.00">read</text>
|
||||
</g>
|
||||
<!-- nginx -->
|
||||
<g id="node9" class="node">
|
||||
<title>nginx</title>
|
||||
<path fill="#bbdefb" stroke="black" d="M126.88,-298.69C126.88,-298.69 77.12,-298.69 77.12,-298.69 71.12,-298.69 65.12,-292.69 65.12,-286.69 65.12,-286.69 65.12,-274.69 65.12,-274.69 65.12,-268.69 71.12,-262.69 77.12,-262.69 77.12,-262.69 126.88,-262.69 126.88,-262.69 132.88,-262.69 138.88,-268.69 138.88,-274.69 138.88,-274.69 138.88,-286.69 138.88,-286.69 138.88,-292.69 132.88,-298.69 126.88,-298.69"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="102" y="-283.74" font-family="Helvetica,sans-Serif" font-size="11.00">Nginx</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="102" y="-270.24" font-family="Helvetica,sans-Serif" font-size="11.00">(Gateway)</text>
|
||||
</g>
|
||||
<!-- nginx->dmweb_remote -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>nginx->dmweb_remote</title>
|
||||
<path fill="none" stroke="#1976d2" d="M102,-262.29C102,-241.38 102,-206 102,-181.09"/>
|
||||
<polygon fill="#1976d2" stroke="#1976d2" points="105.5,-181.33 102,-171.33 98.5,-181.33 105.5,-181.33"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="115.88" y="-222.75" font-family="Helvetica,sans-Serif" font-size="10.00">proxy</text>
|
||||
</g>
|
||||
<!-- browser -->
|
||||
<g id="node10" class="node">
|
||||
<title>browser</title>
|
||||
<path fill="#f3e5f5" stroke="black" d="M145.62,-421.69C145.62,-421.69 58.38,-421.69 58.38,-421.69 52.38,-421.69 46.38,-415.69 46.38,-409.69 46.38,-409.69 46.38,-397.69 46.38,-397.69 46.38,-391.69 52.38,-385.69 58.38,-385.69 58.38,-385.69 145.62,-385.69 145.62,-385.69 151.62,-385.69 157.62,-391.69 157.62,-397.69 157.62,-397.69 157.62,-409.69 157.62,-409.69 157.62,-415.69 151.62,-421.69 145.62,-421.69"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="102" y="-406.74" font-family="Helvetica,sans-Serif" font-size="11.00">Browser</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="102" y="-393.24" font-family="Helvetica,sans-Serif" font-size="11.00">(Portfolio Viewer)</text>
|
||||
</g>
|
||||
<!-- browser->nginx -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>browser->nginx</title>
|
||||
<path fill="none" stroke="#1976d2" d="M102,-385.35C102,-365.76 102,-333.58 102,-310.26"/>
|
||||
<polygon fill="#1976d2" stroke="#1976d2" points="105.5,-310.51 102,-300.51 98.5,-310.51 105.5,-310.51"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="117.75" y="-351.81" font-family="Helvetica,sans-Serif" font-size="10.00">HTTPS</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 14 KiB |
76
docs/architecture/02-data-sync.dot
Normal file
76
docs/architecture/02-data-sync.dot
Normal file
@@ -0,0 +1,76 @@
|
||||
digraph DataSync {
|
||||
// Graph settings
|
||||
rankdir=LR;
|
||||
compound=true;
|
||||
fontname="Helvetica";
|
||||
node [fontname="Helvetica", fontsize=11];
|
||||
edge [fontname="Helvetica", fontsize=10];
|
||||
|
||||
// Title
|
||||
labelloc="t";
|
||||
label="Deskmeter - Change Streams Data Sync";
|
||||
fontsize=16;
|
||||
|
||||
// Styling
|
||||
node [shape=box, style="rounded,filled"];
|
||||
|
||||
// Local MongoDB
|
||||
subgraph cluster_local_mongo {
|
||||
label="Local MongoDB (Replica Set)";
|
||||
style=filled;
|
||||
color="#E8F5E9";
|
||||
fillcolor="#E8F5E9";
|
||||
|
||||
oplog [label="Oplog", fillcolor="#C8E6C9", shape=cylinder];
|
||||
|
||||
subgraph cluster_collections {
|
||||
label="Collections";
|
||||
style=dashed;
|
||||
color="#388E3C";
|
||||
|
||||
switch_coll [label="switch\n(workspace events)", fillcolor="#DCEDC8"];
|
||||
task_coll [label="task\n(current tasks)", fillcolor="#DCEDC8"];
|
||||
state_coll [label="state\n(current state)", fillcolor="#DCEDC8"];
|
||||
history_coll [label="task_history\n(path cache)", fillcolor="#DCEDC8"];
|
||||
}
|
||||
}
|
||||
|
||||
// dmsync daemon
|
||||
subgraph cluster_dmsync {
|
||||
label="dmsync Daemon";
|
||||
style=filled;
|
||||
color="#F3E5F5";
|
||||
fillcolor="#F3E5F5";
|
||||
|
||||
watcher [label="Change Stream\nWatcher", fillcolor="#E1BEE7"];
|
||||
resume_token [label="Resume Token\n(~/.dmsync-resume-token)", fillcolor="#E1BEE7", shape=note];
|
||||
}
|
||||
|
||||
// Remote MongoDB
|
||||
subgraph cluster_remote_mongo {
|
||||
label="Remote MongoDB (Docker)";
|
||||
style=filled;
|
||||
color="#FFF3E0";
|
||||
fillcolor="#FFF3E0";
|
||||
|
||||
remote_switch [label="switch", fillcolor="#FFE0B2"];
|
||||
remote_task [label="task", fillcolor="#FFE0B2"];
|
||||
remote_state [label="state", fillcolor="#FFE0B2"];
|
||||
remote_history [label="task_history", fillcolor="#FFE0B2"];
|
||||
}
|
||||
|
||||
// Flow
|
||||
switch_coll -> oplog [style=invis];
|
||||
task_coll -> oplog [style=invis];
|
||||
state_coll -> oplog [style=invis];
|
||||
history_coll -> oplog [style=invis];
|
||||
|
||||
oplog -> watcher [label="watch()", color="#7B1FA2", style=bold];
|
||||
watcher -> resume_token [label="persist", color="#666", style=dashed];
|
||||
resume_token -> watcher [label="resume_after", color="#666", style=dashed];
|
||||
|
||||
watcher -> remote_switch [label="upsert", color="#F57C00"];
|
||||
watcher -> remote_task [label="upsert", color="#F57C00"];
|
||||
watcher -> remote_state [label="upsert", color="#F57C00"];
|
||||
watcher -> remote_history [label="upsert", color="#F57C00"];
|
||||
}
|
||||
162
docs/architecture/02-data-sync.svg
Normal file
162
docs/architecture/02-data-sync.svg
Normal file
@@ -0,0 +1,162 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.1 (0)
|
||||
-->
|
||||
<!-- Title: DataSync Pages: 1 -->
|
||||
<svg width="760pt" height="499pt"
|
||||
viewBox="0.00 0.00 760.00 499.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 494.5)">
|
||||
<title>DataSync</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-494.5 755.75,-494.5 755.75,4 -4,4"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="375.88" y="-471.3" font-family="Helvetica,sans-Serif" font-size="16.00">Deskmeter - Change Streams Data Sync</text>
|
||||
<g id="clust1" class="cluster">
|
||||
<title>cluster_local_mongo</title>
|
||||
<polygon fill="#e8f5e9" stroke="#e8f5e9" points="4.5,-169 4.5,-455 252.25,-455 252.25,-169 4.5,-169"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="128.38" y="-435.8" font-family="Helvetica,sans-Serif" font-size="16.00">Local MongoDB (Replica Set)</text>
|
||||
</g>
|
||||
<g id="clust2" class="cluster">
|
||||
<title>cluster_collections</title>
|
||||
<polygon fill="none" stroke="#388e3c" stroke-dasharray="5,2" points="16,-177 16,-419 156.75,-419 156.75,-177 16,-177"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="86.38" y="-399.8" font-family="Helvetica,sans-Serif" font-size="16.00">Collections</text>
|
||||
</g>
|
||||
<g id="clust3" class="cluster">
|
||||
<title>cluster_dmsync</title>
|
||||
<polygon fill="#f3e5f5" stroke="#f3e5f5" points="309,-258 309,-368 724.38,-368 724.38,-258 309,-258"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="516.69" y="-348.8" font-family="Helvetica,sans-Serif" font-size="16.00">dmsync Daemon</text>
|
||||
</g>
|
||||
<g id="clust4" class="cluster">
|
||||
<title>cluster_remote_mongo</title>
|
||||
<polygon fill="#fff3e0" stroke="#fff3e0" points="518.25,-8 518.25,-250 751.75,-250 751.75,-8 518.25,-8"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="635" y="-230.8" font-family="Helvetica,sans-Serif" font-size="16.00">Remote MongoDB (Docker)</text>
|
||||
</g>
|
||||
<!-- oplog -->
|
||||
<g id="node1" class="node">
|
||||
<title>oplog</title>
|
||||
<path fill="#c8e6c9" stroke="black" d="M239.75,-298.73C239.75,-300.53 227.65,-302 212.75,-302 197.85,-302 185.75,-300.53 185.75,-298.73 185.75,-298.73 185.75,-269.27 185.75,-269.27 185.75,-267.47 197.85,-266 212.75,-266 227.65,-266 239.75,-267.47 239.75,-269.27 239.75,-269.27 239.75,-298.73 239.75,-298.73"/>
|
||||
<path fill="none" stroke="black" d="M239.75,-298.73C239.75,-296.92 227.65,-295.45 212.75,-295.45 197.85,-295.45 185.75,-296.92 185.75,-298.73"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="212.75" y="-280.3" font-family="Helvetica,sans-Serif" font-size="11.00">Oplog</text>
|
||||
</g>
|
||||
<!-- watcher -->
|
||||
<g id="node6" class="node">
|
||||
<title>watcher</title>
|
||||
<path fill="#e1bee7" stroke="black" d="M405.75,-302C405.75,-302 329,-302 329,-302 323,-302 317,-296 317,-290 317,-290 317,-278 317,-278 317,-272 323,-266 329,-266 329,-266 405.75,-266 405.75,-266 411.75,-266 417.75,-272 417.75,-278 417.75,-278 417.75,-290 417.75,-290 417.75,-296 411.75,-302 405.75,-302"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="367.38" y="-287.05" font-family="Helvetica,sans-Serif" font-size="11.00">Change Stream</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="367.38" y="-273.55" font-family="Helvetica,sans-Serif" font-size="11.00">Watcher</text>
|
||||
</g>
|
||||
<!-- oplog->watcher -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>oplog->watcher</title>
|
||||
<path fill="none" stroke="#7b1fa2" stroke-width="2" d="M239.99,-284C258,-284 282.67,-284 305.43,-284"/>
|
||||
<polygon fill="#7b1fa2" stroke="#7b1fa2" stroke-width="2" points="303.74,-287.5 313.74,-284 303.74,-280.5 303.74,-287.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="280.62" y="-287.25" font-family="Helvetica,sans-Serif" font-size="10.00">watch()</text>
|
||||
</g>
|
||||
<!-- switch_coll -->
|
||||
<g id="node2" class="node">
|
||||
<title>switch_coll</title>
|
||||
<path fill="#dcedc8" stroke="black" d="M136.75,-221C136.75,-221 36,-221 36,-221 30,-221 24,-215 24,-209 24,-209 24,-197 24,-197 24,-191 30,-185 36,-185 36,-185 136.75,-185 136.75,-185 142.75,-185 148.75,-191 148.75,-197 148.75,-197 148.75,-209 148.75,-209 148.75,-215 142.75,-221 136.75,-221"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="86.38" y="-206.05" font-family="Helvetica,sans-Serif" font-size="11.00">switch</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="86.38" y="-192.55" font-family="Helvetica,sans-Serif" font-size="11.00">(workspace events)</text>
|
||||
</g>
|
||||
<!-- switch_coll->oplog -->
|
||||
<!-- task_coll -->
|
||||
<g id="node3" class="node">
|
||||
<title>task_coll</title>
|
||||
<path fill="#dcedc8" stroke="black" d="M123.25,-275C123.25,-275 49.5,-275 49.5,-275 43.5,-275 37.5,-269 37.5,-263 37.5,-263 37.5,-251 37.5,-251 37.5,-245 43.5,-239 49.5,-239 49.5,-239 123.25,-239 123.25,-239 129.25,-239 135.25,-245 135.25,-251 135.25,-251 135.25,-263 135.25,-263 135.25,-269 129.25,-275 123.25,-275"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="86.38" y="-260.05" font-family="Helvetica,sans-Serif" font-size="11.00">task</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="86.38" y="-246.55" font-family="Helvetica,sans-Serif" font-size="11.00">(current tasks)</text>
|
||||
</g>
|
||||
<!-- task_coll->oplog -->
|
||||
<!-- state_coll -->
|
||||
<g id="node4" class="node">
|
||||
<title>state_coll</title>
|
||||
<path fill="#dcedc8" stroke="black" d="M122.88,-329C122.88,-329 49.88,-329 49.88,-329 43.88,-329 37.88,-323 37.88,-317 37.88,-317 37.88,-305 37.88,-305 37.88,-299 43.88,-293 49.88,-293 49.88,-293 122.88,-293 122.88,-293 128.88,-293 134.88,-299 134.88,-305 134.88,-305 134.88,-317 134.88,-317 134.88,-323 128.88,-329 122.88,-329"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="86.38" y="-314.05" font-family="Helvetica,sans-Serif" font-size="11.00">state</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="86.38" y="-300.55" font-family="Helvetica,sans-Serif" font-size="11.00">(current state)</text>
|
||||
</g>
|
||||
<!-- state_coll->oplog -->
|
||||
<!-- history_coll -->
|
||||
<g id="node5" class="node">
|
||||
<title>history_coll</title>
|
||||
<path fill="#dcedc8" stroke="black" d="M117.25,-383C117.25,-383 55.5,-383 55.5,-383 49.5,-383 43.5,-377 43.5,-371 43.5,-371 43.5,-359 43.5,-359 43.5,-353 49.5,-347 55.5,-347 55.5,-347 117.25,-347 117.25,-347 123.25,-347 129.25,-353 129.25,-359 129.25,-359 129.25,-371 129.25,-371 129.25,-377 123.25,-383 117.25,-383"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="86.38" y="-368.05" font-family="Helvetica,sans-Serif" font-size="11.00">task_history</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="86.38" y="-354.55" font-family="Helvetica,sans-Serif" font-size="11.00">(path cache)</text>
|
||||
</g>
|
||||
<!-- history_coll->oplog -->
|
||||
<!-- resume_token -->
|
||||
<g id="node7" class="node">
|
||||
<title>resume_token</title>
|
||||
<polygon fill="#e1bee7" stroke="black" points="710.38,-317 552.62,-317 552.62,-281 716.38,-281 716.38,-311 710.38,-317"/>
|
||||
<polyline fill="none" stroke="black" points="710.38,-317 710.38,-311"/>
|
||||
<polyline fill="none" stroke="black" points="716.38,-311 710.38,-311"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="634.5" y="-302.05" font-family="Helvetica,sans-Serif" font-size="11.00">Resume Token</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="634.5" y="-288.55" font-family="Helvetica,sans-Serif" font-size="11.00">(~/.dmsync-resume-token)</text>
|
||||
</g>
|
||||
<!-- watcher->resume_token -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>watcher->resume_token</title>
|
||||
<path fill="none" stroke="#666666" stroke-dasharray="5,2" d="M418.2,-283.38C442.89,-283.35 473.16,-283.73 500.25,-285.25 513.4,-285.99 527.26,-287.07 540.86,-288.32"/>
|
||||
<polygon fill="#666666" stroke="#666666" points="540.41,-291.79 550.7,-289.25 541.07,-284.82 540.41,-291.79"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="468" y="-288.5" font-family="Helvetica,sans-Serif" font-size="10.00">persist</text>
|
||||
</g>
|
||||
<!-- remote_switch -->
|
||||
<g id="node8" class="node">
|
||||
<title>remote_switch</title>
|
||||
<path fill="#ffe0b2" stroke="black" d="M649.5,-214C649.5,-214 619.5,-214 619.5,-214 613.5,-214 607.5,-208 607.5,-202 607.5,-202 607.5,-190 607.5,-190 607.5,-184 613.5,-178 619.5,-178 619.5,-178 649.5,-178 649.5,-178 655.5,-178 661.5,-184 661.5,-190 661.5,-190 661.5,-202 661.5,-202 661.5,-208 655.5,-214 649.5,-214"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="634.5" y="-192.3" font-family="Helvetica,sans-Serif" font-size="11.00">switch</text>
|
||||
</g>
|
||||
<!-- watcher->remote_switch -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>watcher->remote_switch</title>
|
||||
<path fill="none" stroke="#f57c00" d="M418.24,-267.45C470.07,-250.25 549.97,-223.72 596.46,-208.29"/>
|
||||
<polygon fill="#f57c00" stroke="#f57c00" points="597.42,-211.66 605.81,-205.19 595.21,-205.02 597.42,-211.66"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="468" y="-263.65" font-family="Helvetica,sans-Serif" font-size="10.00">upsert</text>
|
||||
</g>
|
||||
<!-- remote_task -->
|
||||
<g id="node9" class="node">
|
||||
<title>remote_task</title>
|
||||
<path fill="#ffe0b2" stroke="black" d="M649.5,-160C649.5,-160 619.5,-160 619.5,-160 613.5,-160 607.5,-154 607.5,-148 607.5,-148 607.5,-136 607.5,-136 607.5,-130 613.5,-124 619.5,-124 619.5,-124 649.5,-124 649.5,-124 655.5,-124 661.5,-130 661.5,-136 661.5,-136 661.5,-148 661.5,-148 661.5,-154 655.5,-160 649.5,-160"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="634.5" y="-138.3" font-family="Helvetica,sans-Serif" font-size="11.00">task</text>
|
||||
</g>
|
||||
<!-- watcher->remote_task -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>watcher->remote_task</title>
|
||||
<path fill="none" stroke="#f57c00" d="M386.34,-265.52C412.71,-239.57 464.61,-192.85 518.25,-169 543.15,-157.93 573.17,-151.08 596.34,-147.06"/>
|
||||
<polygon fill="#f57c00" stroke="#f57c00" points="596.63,-150.55 605.93,-145.49 595.5,-143.65 596.63,-150.55"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="468" y="-224.4" font-family="Helvetica,sans-Serif" font-size="10.00">upsert</text>
|
||||
</g>
|
||||
<!-- remote_state -->
|
||||
<g id="node10" class="node">
|
||||
<title>remote_state</title>
|
||||
<path fill="#ffe0b2" stroke="black" d="M649.5,-106C649.5,-106 619.5,-106 619.5,-106 613.5,-106 607.5,-100 607.5,-94 607.5,-94 607.5,-82 607.5,-82 607.5,-76 613.5,-70 619.5,-70 619.5,-70 649.5,-70 649.5,-70 655.5,-70 661.5,-76 661.5,-82 661.5,-82 661.5,-94 661.5,-94 661.5,-100 655.5,-106 649.5,-106"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="634.5" y="-84.3" font-family="Helvetica,sans-Serif" font-size="11.00">state</text>
|
||||
</g>
|
||||
<!-- watcher->remote_state -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>watcher->remote_state</title>
|
||||
<path fill="none" stroke="#f57c00" d="M374.68,-265.75C384.38,-239.86 405.12,-192.16 435.75,-161.25 479.42,-117.18 551.93,-99.36 595.96,-92.35"/>
|
||||
<polygon fill="#f57c00" stroke="#f57c00" points="596.42,-95.81 605.81,-90.9 595.41,-88.89 596.42,-95.81"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="468" y="-164.5" font-family="Helvetica,sans-Serif" font-size="10.00">upsert</text>
|
||||
</g>
|
||||
<!-- remote_history -->
|
||||
<g id="node11" class="node">
|
||||
<title>remote_history</title>
|
||||
<path fill="#ffe0b2" stroke="black" d="M663.88,-52C663.88,-52 605.12,-52 605.12,-52 599.12,-52 593.12,-46 593.12,-40 593.12,-40 593.12,-28 593.12,-28 593.12,-22 599.12,-16 605.12,-16 605.12,-16 663.88,-16 663.88,-16 669.88,-16 675.88,-22 675.88,-28 675.88,-28 675.88,-40 675.88,-40 675.88,-46 669.88,-52 663.88,-52"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="634.5" y="-30.3" font-family="Helvetica,sans-Serif" font-size="11.00">task_history</text>
|
||||
</g>
|
||||
<!-- watcher->remote_history -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>watcher->remote_history</title>
|
||||
<path fill="none" stroke="#f57c00" d="M370.83,-265.73C376.42,-230.55 393.07,-152.59 435.75,-104.25 472.79,-62.29 536.59,-45.39 581.69,-38.59"/>
|
||||
<polygon fill="#f57c00" stroke="#f57c00" points="582.09,-42.07 591.52,-37.24 581.13,-35.13 582.09,-42.07"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="468" y="-107.5" font-family="Helvetica,sans-Serif" font-size="10.00">upsert</text>
|
||||
</g>
|
||||
<!-- resume_token->watcher -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>resume_token->watcher</title>
|
||||
<path fill="none" stroke="#666666" stroke-dasharray="5,2" d="M552.48,-302.23C516.62,-302.72 473.99,-302.03 435.75,-298 433.7,-297.78 431.62,-297.53 429.53,-297.25"/>
|
||||
<polygon fill="#666666" stroke="#666666" points="430.15,-293.81 419.73,-295.75 429.09,-300.73 430.15,-293.81"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="468" y="-305.07" font-family="Helvetica,sans-Serif" font-size="10.00">resume_after</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
83
docs/architecture/03-deployment.dot
Normal file
83
docs/architecture/03-deployment.dot
Normal file
@@ -0,0 +1,83 @@
|
||||
digraph Deployment {
|
||||
// Graph settings
|
||||
rankdir=TB;
|
||||
compound=true;
|
||||
fontname="Helvetica";
|
||||
node [fontname="Helvetica", fontsize=11];
|
||||
edge [fontname="Helvetica", fontsize=10];
|
||||
|
||||
// Title
|
||||
labelloc="t";
|
||||
label="Deskmeter - Deployment Architecture";
|
||||
fontsize=16;
|
||||
|
||||
// Styling
|
||||
node [shape=box, style="rounded,filled"];
|
||||
|
||||
// Development
|
||||
subgraph cluster_dev {
|
||||
label="Development (Local)";
|
||||
style=filled;
|
||||
color="#E3F2FD";
|
||||
fillcolor="#E3F2FD";
|
||||
|
||||
source [label="Source Code\n(/home/mariano/wdir/dm)", fillcolor="#BBDEFB"];
|
||||
docker_build [label="docker build", fillcolor="#BBDEFB", shape=parallelogram];
|
||||
}
|
||||
|
||||
// Registry
|
||||
subgraph cluster_registry {
|
||||
label="Private Registry";
|
||||
style=filled;
|
||||
color="#F3E5F5";
|
||||
fillcolor="#F3E5F5";
|
||||
|
||||
registry [label="registry.mcrn.ar:5000\n(Docker Registry)", fillcolor="#E1BEE7", shape=cylinder];
|
||||
}
|
||||
|
||||
// AWS EC2
|
||||
subgraph cluster_aws {
|
||||
label="AWS EC2 (mcrn.ar)";
|
||||
style=filled;
|
||||
color="#FFF3E0";
|
||||
fillcolor="#FFF3E0";
|
||||
|
||||
// Gateway Network
|
||||
subgraph cluster_gateway {
|
||||
label="gateway network";
|
||||
style=dashed;
|
||||
color="#F57C00";
|
||||
|
||||
nginx [label="Nginx\n(SSL termination)", fillcolor="#FFE0B2"];
|
||||
dmweb_container [label="dmweb:latest\n(port 10000)", fillcolor="#FFE0B2"];
|
||||
}
|
||||
|
||||
// Internal Network
|
||||
subgraph cluster_internal {
|
||||
label="internal network";
|
||||
style=dashed;
|
||||
color="#388E3C";
|
||||
|
||||
mongo_container [label="mongo:7\n(port 27017)", fillcolor="#C8E6C9", shape=cylinder];
|
||||
mongo_volume [label="mongo-data\n(volume)", fillcolor="#DCEDC8", shape=folder];
|
||||
}
|
||||
}
|
||||
|
||||
// DNS
|
||||
dns [label="deskmeter.mcrn.ar\n(DNS)", fillcolor="#FFCDD2", shape=diamond];
|
||||
|
||||
// Internet
|
||||
internet [label="Internet\n(Portfolio Visitors)", fillcolor="#F5F5F5"];
|
||||
|
||||
// Flow
|
||||
source -> docker_build [label="Dockerfile"];
|
||||
docker_build -> registry [label="docker push", color="#7B1FA2"];
|
||||
registry -> dmweb_container [label="docker pull", color="#7B1FA2"];
|
||||
|
||||
dmweb_container -> mongo_container [label="MONGODB_HOST=mongo", color="#388E3C"];
|
||||
mongo_container -> mongo_volume [label="persist", style=dashed];
|
||||
|
||||
internet -> dns [color="#1976D2"];
|
||||
dns -> nginx [label="HTTPS", color="#1976D2"];
|
||||
nginx -> dmweb_container [label="proxy_pass", color="#F57C00"];
|
||||
}
|
||||
158
docs/architecture/03-deployment.svg
Normal file
158
docs/architecture/03-deployment.svg
Normal file
@@ -0,0 +1,158 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Generated by graphviz version 14.1.1 (0)
|
||||
-->
|
||||
<!-- Title: Deployment Pages: 1 -->
|
||||
<svg width="447pt" height="701pt"
|
||||
viewBox="0.00 0.00 447.00 701.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 697.44)">
|
||||
<title>Deployment</title>
|
||||
<polygon fill="white" stroke="none" points="-4,4 -4,-697.44 443.25,-697.44 443.25,4 -4,4"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="219.62" y="-674.24" font-family="Helvetica,sans-Serif" font-size="16.00">Deskmeter - Deployment Architecture</text>
|
||||
<g id="clust1" class="cluster">
|
||||
<title>cluster_dev</title>
|
||||
<polygon fill="#e3f2fd" stroke="#e3f2fd" points="8,-476.69 8,-657.94 191,-657.94 191,-476.69 8,-476.69"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="99.5" y="-638.74" font-family="Helvetica,sans-Serif" font-size="16.00">Development (Local)</text>
|
||||
</g>
|
||||
<g id="clust2" class="cluster">
|
||||
<title>cluster_registry</title>
|
||||
<polygon fill="#f3e5f5" stroke="#f3e5f5" points="40,-307.88 40,-399.5 190,-399.5 190,-307.88 40,-307.88"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="115" y="-380.3" font-family="Helvetica,sans-Serif" font-size="16.00">Private Registry</text>
|
||||
</g>
|
||||
<g id="clust3" class="cluster">
|
||||
<title>cluster_aws</title>
|
||||
<polygon fill="#fff3e0" stroke="#fff3e0" points="227,-8 227,-428.94 397,-428.94 397,-8 227,-8"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="312" y="-409.74" font-family="Helvetica,sans-Serif" font-size="16.00">AWS EC2 (mcrn.ar)</text>
|
||||
</g>
|
||||
<g id="clust4" class="cluster">
|
||||
<title>cluster_gateway</title>
|
||||
<polygon fill="none" stroke="#f57c00" stroke-dasharray="5,2" points="235,-223.12 235,-393.44 389,-393.44 389,-223.12 235,-223.12"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="312" y="-374.24" font-family="Helvetica,sans-Serif" font-size="16.00">gateway network</text>
|
||||
</g>
|
||||
<g id="clust5" class="cluster">
|
||||
<title>cluster_internal</title>
|
||||
<polygon fill="none" stroke="#388e3c" stroke-dasharray="5,2" points="235,-16 235,-192.38 382,-192.38 382,-16 235,-16"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="308.5" y="-173.18" font-family="Helvetica,sans-Serif" font-size="16.00">internal network</text>
|
||||
</g>
|
||||
<!-- source -->
|
||||
<g id="node1" class="node">
|
||||
<title>source</title>
|
||||
<path fill="#bbdefb" stroke="black" d="M171.12,-622.44C171.12,-622.44 38.88,-622.44 38.88,-622.44 32.88,-622.44 26.88,-616.44 26.88,-610.44 26.88,-610.44 26.88,-598.44 26.88,-598.44 26.88,-592.44 32.88,-586.44 38.88,-586.44 38.88,-586.44 171.12,-586.44 171.12,-586.44 177.12,-586.44 183.12,-592.44 183.12,-598.44 183.12,-598.44 183.12,-610.44 183.12,-610.44 183.12,-616.44 177.12,-622.44 171.12,-622.44"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="105" y="-607.49" font-family="Helvetica,sans-Serif" font-size="11.00">Source Code</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="105" y="-593.99" font-family="Helvetica,sans-Serif" font-size="11.00">(/home/mariano/wdir/dm)</text>
|
||||
</g>
|
||||
<!-- docker_build -->
|
||||
<g id="node2" class="node">
|
||||
<title>docker_build</title>
|
||||
<path fill="#bbdefb" stroke="black" d="M168.63,-520.69C168.63,-520.69 72.32,-520.69 72.32,-520.69 66.32,-520.69 56.41,-516.14 52.49,-511.59 52.49,-511.59 37.19,-493.79 37.19,-493.79 33.28,-489.24 35.37,-484.69 41.37,-484.69 41.37,-484.69 137.68,-484.69 137.68,-484.69 143.68,-484.69 153.59,-489.24 157.51,-493.79 157.51,-493.79 172.81,-511.59 172.81,-511.59 176.72,-516.14 174.63,-520.69 168.63,-520.69"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="105" y="-498.99" font-family="Helvetica,sans-Serif" font-size="11.00">docker build</text>
|
||||
</g>
|
||||
<!-- source->docker_build -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>source->docker_build</title>
|
||||
<path fill="none" stroke="black" d="M105,-586.06C105,-571.26 105,-549.69 105,-532.33"/>
|
||||
<polygon fill="black" stroke="black" points="108.5,-532.38 105,-522.38 101.5,-532.38 108.5,-532.38"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="129.38" y="-558.94" font-family="Helvetica,sans-Serif" font-size="10.00">Dockerfile</text>
|
||||
</g>
|
||||
<!-- registry -->
|
||||
<g id="node3" class="node">
|
||||
<title>registry</title>
|
||||
<path fill="#e1bee7" stroke="black" d="M182.25,-359.62C182.25,-362.04 152.11,-364 115,-364 77.89,-364 47.75,-362.04 47.75,-359.62 47.75,-359.62 47.75,-320.25 47.75,-320.25 47.75,-317.84 77.89,-315.88 115,-315.88 152.11,-315.88 182.25,-317.84 182.25,-320.25 182.25,-320.25 182.25,-359.62 182.25,-359.62"/>
|
||||
<path fill="none" stroke="black" d="M182.25,-359.62C182.25,-357.21 152.11,-355.25 115,-355.25 77.89,-355.25 47.75,-357.21 47.75,-359.62"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="115" y="-342.99" font-family="Helvetica,sans-Serif" font-size="11.00">registry.mcrn.ar:5000</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="115" y="-329.49" font-family="Helvetica,sans-Serif" font-size="11.00">(Docker Registry)</text>
|
||||
</g>
|
||||
<!-- docker_build->registry -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>docker_build->registry</title>
|
||||
<path fill="none" stroke="#7b1fa2" d="M106.07,-484.48C107.68,-458.62 110.76,-409.17 112.84,-375.63"/>
|
||||
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="116.31,-376.2 113.44,-366 109.33,-375.76 116.31,-376.2"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="138.06" y="-440.19" font-family="Helvetica,sans-Serif" font-size="10.00">docker push</text>
|
||||
</g>
|
||||
<!-- dmweb_container -->
|
||||
<g id="node5" class="node">
|
||||
<title>dmweb_container</title>
|
||||
<path fill="#ffe0b2" stroke="black" d="M322.5,-267.12C322.5,-267.12 255.5,-267.12 255.5,-267.12 249.5,-267.12 243.5,-261.12 243.5,-255.12 243.5,-255.12 243.5,-243.12 243.5,-243.12 243.5,-237.12 249.5,-231.12 255.5,-231.12 255.5,-231.12 322.5,-231.12 322.5,-231.12 328.5,-231.12 334.5,-237.12 334.5,-243.12 334.5,-243.12 334.5,-255.12 334.5,-255.12 334.5,-261.12 328.5,-267.12 322.5,-267.12"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="289" y="-252.18" font-family="Helvetica,sans-Serif" font-size="11.00">dmweb:latest</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="289" y="-238.68" font-family="Helvetica,sans-Serif" font-size="11.00">(port 10000)</text>
|
||||
</g>
|
||||
<!-- registry->dmweb_container -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>registry->dmweb_container</title>
|
||||
<path fill="none" stroke="#7b1fa2" d="M159.81,-316.07C185.94,-302.73 218.78,-285.97 244.81,-272.68"/>
|
||||
<polygon fill="#7b1fa2" stroke="#7b1fa2" points="246.12,-275.94 253.43,-268.28 242.94,-269.71 246.12,-275.94"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="244.21" y="-288.38" font-family="Helvetica,sans-Serif" font-size="10.00">docker pull</text>
|
||||
</g>
|
||||
<!-- nginx -->
|
||||
<g id="node4" class="node">
|
||||
<title>nginx</title>
|
||||
<path fill="#ffe0b2" stroke="black" d="M356,-357.94C356,-357.94 268,-357.94 268,-357.94 262,-357.94 256,-351.94 256,-345.94 256,-345.94 256,-333.94 256,-333.94 256,-327.94 262,-321.94 268,-321.94 268,-321.94 356,-321.94 356,-321.94 362,-321.94 368,-327.94 368,-333.94 368,-333.94 368,-345.94 368,-345.94 368,-351.94 362,-357.94 356,-357.94"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="312" y="-342.99" font-family="Helvetica,sans-Serif" font-size="11.00">Nginx</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="312" y="-329.49" font-family="Helvetica,sans-Serif" font-size="11.00">(SSL termination)</text>
|
||||
</g>
|
||||
<!-- nginx->dmweb_container -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>nginx->dmweb_container</title>
|
||||
<path fill="none" stroke="#f57c00" d="M307.57,-321.81C304.37,-309.48 299.99,-292.57 296.29,-278.27"/>
|
||||
<polygon fill="#f57c00" stroke="#f57c00" points="299.77,-277.77 293.88,-268.96 293,-279.52 299.77,-277.77"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="328.94" y="-288.38" font-family="Helvetica,sans-Serif" font-size="10.00">proxy_pass</text>
|
||||
</g>
|
||||
<!-- mongo_container -->
|
||||
<g id="node6" class="node">
|
||||
<title>mongo_container</title>
|
||||
<path fill="#c8e6c9" stroke="black" d="M328.5,-152.5C328.5,-154.91 309.45,-156.88 286,-156.88 262.55,-156.88 243.5,-154.91 243.5,-152.5 243.5,-152.5 243.5,-113.12 243.5,-113.12 243.5,-110.71 262.55,-108.75 286,-108.75 309.45,-108.75 328.5,-110.71 328.5,-113.12 328.5,-113.12 328.5,-152.5 328.5,-152.5"/>
|
||||
<path fill="none" stroke="black" d="M328.5,-152.5C328.5,-150.09 309.45,-148.12 286,-148.12 262.55,-148.12 243.5,-150.09 243.5,-152.5"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="286" y="-135.86" font-family="Helvetica,sans-Serif" font-size="11.00">mongo:7</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="286" y="-122.36" font-family="Helvetica,sans-Serif" font-size="11.00">(port 27017)</text>
|
||||
</g>
|
||||
<!-- dmweb_container->mongo_container -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>dmweb_container->mongo_container</title>
|
||||
<path fill="none" stroke="#388e3c" d="M288.54,-230.76C288.11,-214.37 287.46,-189.3 286.92,-168.77"/>
|
||||
<polygon fill="#388e3c" stroke="#388e3c" points="290.42,-168.71 286.66,-158.81 283.42,-168.9 290.42,-168.71"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="351.78" y="-203.62" font-family="Helvetica,sans-Serif" font-size="10.00">MONGODB_HOST=mongo</text>
|
||||
</g>
|
||||
<!-- mongo_volume -->
|
||||
<g id="node7" class="node">
|
||||
<title>mongo_volume</title>
|
||||
<polygon fill="#dcedc8" stroke="black" points="327,-60 324,-64 303,-64 300,-60 245,-60 245,-24 327,-24 327,-60"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="286" y="-45.05" font-family="Helvetica,sans-Serif" font-size="11.00">mongo-data</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="286" y="-31.55" font-family="Helvetica,sans-Serif" font-size="11.00">(volume)</text>
|
||||
</g>
|
||||
<!-- mongo_container->mongo_volume -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>mongo_container->mongo_volume</title>
|
||||
<path fill="none" stroke="black" stroke-dasharray="5,2" d="M286,-108.48C286,-97.15 286,-83.43 286,-71.49"/>
|
||||
<polygon fill="black" stroke="black" points="289.5,-71.8 286,-61.8 282.5,-71.8 289.5,-71.8"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="302.5" y="-81.25" font-family="Helvetica,sans-Serif" font-size="10.00">persist</text>
|
||||
</g>
|
||||
<!-- dns -->
|
||||
<g id="node8" class="node">
|
||||
<title>dns</title>
|
||||
<path fill="#ffcdd2" stroke="black" d="M307.48,-534.33C307.48,-534.33 210.27,-506.04 210.27,-506.04 204.51,-504.36 204.51,-501.01 210.27,-499.33 210.27,-499.33 307.48,-471.04 307.48,-471.04 313.24,-469.36 324.76,-469.36 330.52,-471.04 330.52,-471.04 427.73,-499.33 427.73,-499.33 433.49,-501.01 433.49,-504.36 427.73,-506.04 427.73,-506.04 330.52,-534.33 330.52,-534.33 324.76,-536.01 313.24,-536.01 307.48,-534.33"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="319" y="-505.74" font-family="Helvetica,sans-Serif" font-size="11.00">deskmeter.mcrn.ar</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="319" y="-492.24" font-family="Helvetica,sans-Serif" font-size="11.00">(DNS)</text>
|
||||
</g>
|
||||
<!-- dns->nginx -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>dns->nginx</title>
|
||||
<path fill="none" stroke="#1976d2" d="M317.53,-467.99C316.26,-438.8 314.44,-397.03 313.24,-369.47"/>
|
||||
<polygon fill="#1976d2" stroke="#1976d2" points="316.75,-369.6 312.82,-359.76 309.76,-369.9 316.75,-369.6"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="332.29" y="-440.19" font-family="Helvetica,sans-Serif" font-size="10.00">HTTPS</text>
|
||||
</g>
|
||||
<!-- internet -->
|
||||
<g id="node9" class="node">
|
||||
<title>internet</title>
|
||||
<path fill="#f5f5f5" stroke="black" d="M364.5,-622.44C364.5,-622.44 273.5,-622.44 273.5,-622.44 267.5,-622.44 261.5,-616.44 261.5,-610.44 261.5,-610.44 261.5,-598.44 261.5,-598.44 261.5,-592.44 267.5,-586.44 273.5,-586.44 273.5,-586.44 364.5,-586.44 364.5,-586.44 370.5,-586.44 376.5,-592.44 376.5,-598.44 376.5,-598.44 376.5,-610.44 376.5,-610.44 376.5,-616.44 370.5,-622.44 364.5,-622.44"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="319" y="-607.49" font-family="Helvetica,sans-Serif" font-size="11.00">Internet</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="319" y="-593.99" font-family="Helvetica,sans-Serif" font-size="11.00">(Portfolio Visitors)</text>
|
||||
</g>
|
||||
<!-- internet->dns -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>internet->dns</title>
|
||||
<path fill="none" stroke="#1976d2" d="M319,-586.06C319,-575.87 319,-562.48 319,-549.46"/>
|
||||
<polygon fill="#1976d2" stroke="#1976d2" points="322.5,-549.72 319,-539.72 315.5,-549.72 322.5,-549.72"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
115
docs/architecture/graph.html
Normal file
115
docs/architecture/graph.html
Normal file
@@ -0,0 +1,115 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Graph Viewer - Deskmeter</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body class="graph-viewer">
|
||||
<header class="graph-header">
|
||||
<a href="index.html" class="back-link">← Index</a>
|
||||
<div class="nav-controls">
|
||||
<button onclick="navigate(-1)" id="btn-prev" title="Previous (←)">◀</button>
|
||||
<span id="nav-position">1 / 3</span>
|
||||
<button onclick="navigate(1)" id="btn-next" title="Next (→)">▶</button>
|
||||
</div>
|
||||
<h1 id="graph-title">Loading...</h1>
|
||||
<div class="graph-controls">
|
||||
<button onclick="setMode('fit')">Fit</button>
|
||||
<button onclick="setMode('fit-width')">Width</button>
|
||||
<button onclick="setMode('fit-height')">Height</button>
|
||||
<button onclick="setMode('actual-size')">100%</button>
|
||||
<button onclick="downloadSvg()">↓ SVG</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="graph-container" id="graph-container">
|
||||
<img id="graph-img" src="" alt="Graph">
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const graphOrder = [
|
||||
'01-system-overview',
|
||||
'02-data-sync',
|
||||
'03-deployment'
|
||||
];
|
||||
|
||||
const graphs = {
|
||||
'01-system-overview': {
|
||||
title: 'System Overview',
|
||||
file: '01-system-overview.svg'
|
||||
},
|
||||
'02-data-sync': {
|
||||
title: 'Data Sync Architecture',
|
||||
file: '02-data-sync.svg'
|
||||
},
|
||||
'03-deployment': {
|
||||
title: 'Deployment Architecture',
|
||||
file: '03-deployment.svg'
|
||||
}
|
||||
};
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
let graphKey = params.get('g') || '01-system-overview';
|
||||
let currentIndex = graphOrder.indexOf(graphKey);
|
||||
if (currentIndex === -1) currentIndex = 0;
|
||||
|
||||
function loadGraph(key) {
|
||||
const graph = graphs[key];
|
||||
document.getElementById('graph-title').textContent = graph.title;
|
||||
document.getElementById('graph-img').src = graph.file;
|
||||
document.title = graph.title + ' - Deskmeter';
|
||||
history.replaceState(null, '', '?g=' + key);
|
||||
graphKey = key;
|
||||
updateNavHints();
|
||||
}
|
||||
|
||||
function updateNavHints() {
|
||||
const idx = graphOrder.indexOf(graphKey);
|
||||
const prevBtn = document.getElementById('btn-prev');
|
||||
const nextBtn = document.getElementById('btn-next');
|
||||
prevBtn.disabled = idx === 0;
|
||||
nextBtn.disabled = idx === graphOrder.length - 1;
|
||||
document.getElementById('nav-position').textContent = (idx + 1) + ' / ' + graphOrder.length;
|
||||
}
|
||||
|
||||
function navigate(direction) {
|
||||
const idx = graphOrder.indexOf(graphKey);
|
||||
const newIdx = idx + direction;
|
||||
if (newIdx >= 0 && newIdx < graphOrder.length) {
|
||||
currentIndex = newIdx;
|
||||
loadGraph(graphOrder[newIdx]);
|
||||
}
|
||||
}
|
||||
|
||||
function setMode(mode) {
|
||||
const container = document.getElementById('graph-container');
|
||||
container.className = 'graph-container ' + mode;
|
||||
}
|
||||
|
||||
function downloadSvg() {
|
||||
const graph = graphs[graphKey];
|
||||
const link = document.createElement('a');
|
||||
link.href = graph.file;
|
||||
link.download = graph.file;
|
||||
link.click();
|
||||
}
|
||||
|
||||
// Keyboard navigation
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'ArrowLeft') {
|
||||
navigate(-1);
|
||||
} else if (e.key === 'ArrowRight') {
|
||||
navigate(1);
|
||||
} else if (e.key === 'Escape') {
|
||||
window.location.href = 'index.html';
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize
|
||||
loadGraph(graphOrder[currentIndex]);
|
||||
setMode('fit');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
180
docs/architecture/index.html
Normal file
180
docs/architecture/index.html
Normal file
@@ -0,0 +1,180 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Deskmeter - Architecture & Design</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Deskmeter</h1>
|
||||
<p class="subtitle">Productivity Tracking - Architecture Documentation</p>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section class="graph-section" id="overview">
|
||||
<div class="graph-header-row">
|
||||
<h2>System Overview</h2>
|
||||
<a href="graph.html?g=01-system-overview" class="view-btn">View Full</a>
|
||||
</div>
|
||||
<a href="graph.html?g=01-system-overview" class="graph-preview">
|
||||
<img src="01-system-overview.svg" alt="System Overview">
|
||||
</a>
|
||||
<div class="graph-details">
|
||||
<p>High-level architecture showing local tracking, data sync, and remote portfolio deployment.</p>
|
||||
<h4>Key Components</h4>
|
||||
<ul>
|
||||
<li><strong>dmcore</strong>: Workspace tracking daemon using wmctrl (X11)</li>
|
||||
<li><strong>dmweb</strong>: Flask web dashboard with calendar and switches views</li>
|
||||
<li><strong>dmsync</strong>: Change Streams daemon pushing data to remote MongoDB</li>
|
||||
<li><strong>GNOME Extension</strong>: Panel indicator showing current task</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="graph-section" id="data-sync">
|
||||
<div class="graph-header-row">
|
||||
<h2>Data Sync Architecture</h2>
|
||||
<a href="graph.html?g=02-data-sync" class="view-btn">View Full</a>
|
||||
</div>
|
||||
<a href="graph.html?g=02-data-sync" class="graph-preview">
|
||||
<img src="02-data-sync.svg" alt="Data Sync">
|
||||
</a>
|
||||
<div class="graph-details">
|
||||
<p>Real-time sync using MongoDB Change Streams with resume token persistence.</p>
|
||||
<h4>Collections Synced</h4>
|
||||
<table class="details-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Collection</th>
|
||||
<th>Purpose</th>
|
||||
<th>Size</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>switch</code></td>
|
||||
<td>Workspace switch events with timestamps and durations</td>
|
||||
<td>~33k docs</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>task</code></td>
|
||||
<td>Current task definitions from task file</td>
|
||||
<td>~50 docs</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>task_history</code></td>
|
||||
<td>Cached historic task path lookups</td>
|
||||
<td>~200 docs</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>state</code></td>
|
||||
<td>Current workspace and task state</td>
|
||||
<td>1 doc</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Why Change Streams?</h4>
|
||||
<ul>
|
||||
<li>Reacts to actual changes (not polling)</li>
|
||||
<li>Resume token ensures no data loss on restart</li>
|
||||
<li>Native MongoDB feature (requires replica set)</li>
|
||||
<li>Clean, intentional architecture for portfolio</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="graph-section" id="deployment">
|
||||
<div class="graph-header-row">
|
||||
<h2>Deployment Architecture</h2>
|
||||
<a href="graph.html?g=03-deployment" class="view-btn">View Full</a>
|
||||
</div>
|
||||
<a href="graph.html?g=03-deployment" class="graph-preview">
|
||||
<img src="03-deployment.svg" alt="Deployment">
|
||||
</a>
|
||||
<div class="graph-details">
|
||||
<p>Docker Compose deployment on AWS EC2 with Nginx reverse proxy.</p>
|
||||
<h4>Networks</h4>
|
||||
<ul>
|
||||
<li><strong>gateway</strong>: External network connecting Nginx to dmweb</li>
|
||||
<li><strong>internal</strong>: Private network for dmweb ↔ MongoDB communication</li>
|
||||
</ul>
|
||||
<h4>Deployment Flow</h4>
|
||||
<ul>
|
||||
<li>Build Docker image locally</li>
|
||||
<li>Push to private registry (registry.mcrn.ar)</li>
|
||||
<li>Pull and deploy on AWS EC2</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="findings-section">
|
||||
<h2>Design Decisions</h2>
|
||||
<div class="findings-grid">
|
||||
<article class="finding-card">
|
||||
<h3>Bare Metal Core</h3>
|
||||
<p>dmcore runs on bare metal (not Docker) because it requires direct OS access for workspace detection via wmctrl and X11.</p>
|
||||
</article>
|
||||
<article class="finding-card">
|
||||
<h3>Single-Node Replica Set</h3>
|
||||
<p>Local MongoDB configured as replica set (rs0) to enable Change Streams. No actual replication, just API requirement.</p>
|
||||
</article>
|
||||
<article class="finding-card">
|
||||
<h3>Push Sync Model</h3>
|
||||
<p>Local machine pushes to remote (not pull). MongoDB on remote is not exposed to internet - only accessible from the sync daemon.</p>
|
||||
</article>
|
||||
<article class="finding-card">
|
||||
<h3>Portfolio-Ready</h3>
|
||||
<p>Remote instance shows historical data. "Current task" reflects last-synced state, not real-time.</p>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tech-section">
|
||||
<h2>Technology Stack</h2>
|
||||
<div class="tech-grid">
|
||||
<div class="tech-column">
|
||||
<h3>Core</h3>
|
||||
<ul>
|
||||
<li>Python 3.11</li>
|
||||
<li>Flask</li>
|
||||
<li>PyMongo</li>
|
||||
<li>Jinja2 Templates</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tech-column">
|
||||
<h3>Data</h3>
|
||||
<ul>
|
||||
<li>MongoDB 7</li>
|
||||
<li>Change Streams</li>
|
||||
<li>Replica Set</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tech-column">
|
||||
<h3>Infrastructure</h3>
|
||||
<ul>
|
||||
<li>Docker</li>
|
||||
<li>Docker Compose</li>
|
||||
<li>Nginx</li>
|
||||
<li>AWS EC2</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tech-column">
|
||||
<h3>OS Integration</h3>
|
||||
<ul>
|
||||
<li>wmctrl (X11)</li>
|
||||
<li>GNOME Shell Extension</li>
|
||||
<li>systemd</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>Deskmeter - Architecture Documentation</p>
|
||||
<p class="date">Generated: <time datetime="2025-01">January 2025</time></p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
348
docs/architecture/styles.css
Normal file
348
docs/architecture/styles.css
Normal file
@@ -0,0 +1,348 @@
|
||||
:root {
|
||||
--bg-primary: #1a1a2e;
|
||||
--bg-secondary: #16213e;
|
||||
--bg-card: #0f3460;
|
||||
--text-primary: #eee;
|
||||
--text-secondary: #a0a0a0;
|
||||
--accent: #e94560;
|
||||
--accent-secondary: #533483;
|
||||
--border: #2a2a4a;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
header {
|
||||
background: linear-gradient(135deg, var(--bg-secondary), var(--accent-secondary));
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
header .subtitle {
|
||||
color: var(--text-secondary);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
/* Graph sections */
|
||||
.graph-section {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.graph-header-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.graph-header-row h2 {
|
||||
font-size: 1.25rem;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.view-btn {
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
font-size: 0.875rem;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.view-btn:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.graph-preview {
|
||||
display: block;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
.graph-preview img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.graph-details {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.graph-details h4 {
|
||||
color: var(--text-primary);
|
||||
margin: 1rem 0 0.5rem;
|
||||
}
|
||||
|
||||
.graph-details ul {
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
|
||||
.graph-details li {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
/* Tech section */
|
||||
.tech-section {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.tech-section h2 {
|
||||
color: var(--accent);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.tech-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.tech-column h3 {
|
||||
color: var(--text-primary);
|
||||
font-size: 1rem;
|
||||
margin-bottom: 0.75rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.tech-column ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.tech-column li {
|
||||
padding: 0.25rem 0;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* Findings */
|
||||
.findings-section {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.findings-section h2 {
|
||||
color: var(--accent);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.findings-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.finding-card {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 8px;
|
||||
padding: 1.25rem;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.finding-card h3 {
|
||||
color: var(--accent);
|
||||
font-size: 1rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.finding-card p {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.finding-card ul {
|
||||
margin-left: 1rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.finding-card code {
|
||||
background: var(--bg-primary);
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 3px;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: var(--text-secondary);
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
footer .date {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
/* Graph viewer page */
|
||||
body.graph-viewer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.graph-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: var(--bg-secondary);
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.nav-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.nav-controls button {
|
||||
background: var(--bg-card);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border);
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nav-controls button:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
#nav-position {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.graph-header h1 {
|
||||
flex: 1;
|
||||
font-size: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.graph-controls {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.graph-controls button {
|
||||
background: var(--bg-card);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border);
|
||||
padding: 0.375rem 0.75rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.graph-controls button:hover {
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
.graph-container {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
background: white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.graph-container.fit img {
|
||||
max-width: 100%;
|
||||
max-height: calc(100vh - 60px);
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.graph-container.fit-width img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.graph-container.fit-height img {
|
||||
height: calc(100vh - 60px);
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.graph-container.actual-size img {
|
||||
/* No constraints */
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
.details-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1rem 0;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.details-table th,
|
||||
.details-table td {
|
||||
padding: 0.5rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.details-table th {
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.details-table td {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.details-table code {
|
||||
background: var(--bg-primary);
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.note {
|
||||
font-style: italic;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
Reference in New Issue
Block a user