deployment, frontend updates

This commit is contained in:
buenosairesam
2026-01-26 15:11:03 -03:00
parent bf7bcbc37a
commit 3122facaba
16 changed files with 1692 additions and 113 deletions

12
Dockerfile Normal file
View 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
View 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()

View File

@@ -1,8 +1,18 @@
from datetime import datetime, timedelta 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") 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 from flask import request
task = None 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]: if grid not in [1, 3, 6]:
grid = 1 grid = 1
@@ -33,12 +43,16 @@ def calendar_view(scope="daily", year=None, month=None, day=None):
if not day: if not day:
day = datetime.today().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": if scope == "daily":
start = base_date start = base_date
end = base_date.replace(hour=23, minute=59, second=59) 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) prev_date = base_date - timedelta(days=1)
next_date = base_date + timedelta(days=1) next_date = base_date + timedelta(days=1)
days = [base_date] days = [base_date]
@@ -46,7 +60,9 @@ def calendar_view(scope="daily", year=None, month=None, day=None):
elif scope == "weekly": elif scope == "weekly":
start = base_date - timedelta(days=base_date.weekday()) start = base_date - timedelta(days=base_date.weekday())
end = start + timedelta(days=6, hours=23, minutes=59, seconds=59) 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) prev_date = start - timedelta(days=7)
next_date = start + timedelta(days=7) next_date = start + timedelta(days=7)
days = [start + timedelta(days=i) for i in range(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) end = datetime(year + 1, 1, 1, tzinfo=timezone) - timedelta(seconds=1)
else: else:
end = datetime(year, month + 1, 1, tzinfo=timezone) - timedelta(seconds=1) 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: if month == 1:
prev_date = datetime(year - 1, 12, 1, tzinfo=timezone) prev_date = datetime(year - 1, 12, 1, tzinfo=timezone)
else: else:
@@ -75,7 +93,9 @@ def calendar_view(scope="daily", year=None, month=None, day=None):
scope = "daily" scope = "daily"
start = base_date start = base_date
end = base_date.replace(hour=23, minute=59, second=59) 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) prev_date = base_date - timedelta(days=1)
next_date = base_date + timedelta(days=1) next_date = base_date + timedelta(days=1)
days = [base_date] days = [base_date]
@@ -91,7 +111,7 @@ def calendar_view(scope="daily", year=None, month=None, day=None):
next_date=next_date, next_date=next_date,
days=days, days=days,
grid=grid, 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: if not day:
day = datetime.today().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": if scope == "daily":
start = base_date start = base_date
@@ -157,7 +179,7 @@ def switches_view(scope="daily", year=None, month=None, day=None):
base_date=base_date, base_date=base_date,
prev_date=prev_date, prev_date=prev_date,
next_date=next_date, next_date=next_date,
auto_refresh=False auto_refresh=False,
) )
@@ -176,13 +198,17 @@ def index(task=None):
# Get current task info # Get current task info
current_task_id, current_task_path = 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") @dmbp.route("/api/current_task")
@@ -191,10 +217,9 @@ def api_current_task():
JSON API endpoint returning current task information JSON API endpoint returning current task information
""" """
current_task_id, current_task_path = get_current_task_info() current_task_id, current_task_path = get_current_task_info()
return jsonify({ return jsonify(
"task_id": current_task_id, {"task_id": current_task_id, "task_path": current_task_path or "no task"}
"task_path": current_task_path or "no task" )
})
@dmbp.route("/api/today") @dmbp.route("/api/today")
@@ -212,13 +237,16 @@ def api_today(task=None):
# Get current task info # Get current task info
current_task_id, current_task_path = 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>") @dmbp.route("/day/<int:month>/<int:day>")

View File

@@ -1,3 +1,4 @@
import os
from collections import Counter, defaultdict from collections import Counter, defaultdict
from datetime import datetime, timedelta from datetime import datetime, timedelta
from pathlib import Path from pathlib import Path
@@ -8,7 +9,7 @@ from pymongo import MongoClient
timezone = ZoneInfo("America/Argentina/Buenos_Aires") timezone = ZoneInfo("America/Argentina/Buenos_Aires")
utctz = ZoneInfo("UTC") utctz = ZoneInfo("UTC")
client = MongoClient() client = MongoClient(os.environ.get("MONGODB_HOST", "localhost"))
db = client.deskmeter db = client.deskmeter
switches = db.switch switches = db.switch
tasks = db.task tasks = db.task

View File

@@ -1,80 +1,104 @@
<html> <html>
<head> <head>
<!-- <link rel="stylesheet" href="{{ url_for('static', filename='styles/dm.css') }}"> --> <!-- <link rel="stylesheet" href="{{ url_for('static', filename='styles/dm.css') }}"> -->
{% block head %} {% block head %} {% endblock %}
{% endblock %}
<style> <style>
body body {
{ display: flex;
display: flex; flex-direction: column;
flex-direction: column; align-items: center;
align-items: center; justify-content: center;
justify-content: center; min-height: 100vh;
height: 100vh; /* This ensures that the container takes the full height of the viewport */ margin: 0;
margin: 0; /* Remove default margin */ padding: 20px;
background-color: #1a1a1a; box-sizing: border-box;
color: #e0e0e0; background-color: #1a1a1a;
} color: #e0e0e0;
font-family: monospace;
}
.nav-bar { .nav-bar {
position: absolute; position: absolute;
top: 20px; top: 20px;
left: 20px; left: 20px;
display: flex; display: flex;
gap: 15px; gap: 20px;
} padding-bottom: 10px;
border-bottom: 2px solid #444;
}
.nav-bar a { .nav-bar a {
text-decoration: none; text-decoration: none;
color: #6b9bd1; color: #6b9bd1;
font-size: 11pt; font-size: 12pt;
padding: 5px 10px; padding: 5px 10px;
border-radius: 3px; border-radius: 3px;
background-color: #2a2a2a; }
}
.nav-bar a:hover { .nav-bar a:hover {
background-color: #3a3a3a; background-color: #2a2a2a;
} }
.grey { .grey {
color: #888; color: #666;
} }
.blue { .blue {
color: #6b9bd1; color: #6b9bd1;
} }
table { table {
font-size: 84pt font-size: clamp(14pt, 3vw, 28pt);
} border-collapse: collapse;
td { }
padding-right: 100px;
}
</style>
{% if auto_refresh %} td {
<script> padding: 0.1em 0.5em;
function refreshData() { }
var xhr = new XMLHttpRequest();
xhr.open('GET', '/api/today', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
document.getElementById('content-container').innerHTML = xhr.responseText;
}
};
xhr.send();
}
// Auto-refresh every 5 seconds using AJAX td:first-child {
setInterval(refreshData, 5000); text-align: right;
</script> padding-right: 0.3em;
{% endif %} }
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>
{% if auto_refresh %}
<script>
function refreshData() {
var xhr = new XMLHttpRequest();
xhr.open("GET", "/api/today", true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
document.getElementById("content-container").innerHTML =
xhr.responseText;
}
};
xhr.send();
}
// Auto-refresh every 5 seconds using AJAX
setInterval(refreshData, 5000);
</script>
{% endif %}
</head> </head>
<body> <body>
<!-- agregar función que me diga cuanto tiempo hace que esta activo el escritorio <!-- 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) --> (calcular el delta con el ultimo switch y pasarlo a mm:ss) -->
@@ -87,9 +111,7 @@
</div> </div>
<div id="content-container"> <div id="content-container">
{% block content %} {% block content %} {% include 'main_content.html' %} {% endblock %}
{% include 'main_content.html' %}
{% endblock %}
</div> </div>
</body> </body>
</html> </html>

View File

@@ -1,23 +1,39 @@
{% if current_task_path and current_task_time %} {% if current_task_path %}
<div id="current-task-info" style="font-size: 48pt; margin-bottom: 40px; text-align: center;"> <div
<div style="color: #e0e0e0;">{{ current_task_path }}</div> id="current-task-info"
<div style="color: #999; font-size: 36pt;">{{ current_task_time }}</div> 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> </div>
{% endif %} {% endif %}
<table>
<tbody> <table class="workspace-table">
{% for row in rows %} <tbody>
{% if row["ws"] in ['Away', 'Other'] %} {% for row in rows %} {% if row["ws"] in ['Away', 'Other'] %} {% set
{% set my_class = 'grey' %} my_class = 'grey' %} {% elif row["ws"] in ['Active', 'Idle'] %} {% set
{% elif row["ws"] in ['Active', 'Idle'] %} my_class = 'blue' %} {% else %} {% set my_class = '' %} {% endif %}
{% set my_class = 'blue' %}
{% else %}
{% set my_class = '' %}
{% endif %}
<tr> <tr>
<td class="{{my_class}}" >{{ row["ws"] }}</td> <td class="{{my_class}}">{{ row["ws"] }}</td>
<td class="{{my_class}}" >{{ row["total"] }}</td> <td class="{{my_class}}">{{ row["total"] }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>

32
docker-compose.yml Normal file
View 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:

View 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"];
}

View 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 &#45; 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&#45;&gt;mongo_local -->
<g id="edge2" class="edge">
<title>dmcore&#45;&gt;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&#45;&gt;mongo_local -->
<g id="edge3" class="edge">
<title>dmweb_local&#45;&gt;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&#45;&gt;mongo_remote -->
<g id="edge6" class="edge">
<title>dmsync&#45;&gt;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&#45;&gt;dmsync -->
<g id="edge5" class="edge">
<title>mongo_local&#45;&gt;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&#45;&gt;dmcore -->
<g id="edge1" class="edge">
<title>wmctrl&#45;&gt;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&#45;&gt;dmweb_local -->
<g id="edge4" class="edge">
<title>gnome_ext&#45;&gt;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&#45;&gt;mongo_remote -->
<g id="edge7" class="edge">
<title>dmweb_remote&#45;&gt;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&#45;&gt;dmweb_remote -->
<g id="edge8" class="edge">
<title>nginx&#45;&gt;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&#45;&gt;nginx -->
<g id="edge9" class="edge">
<title>browser&#45;&gt;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

View 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"];
}

View 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 &#45; 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&#45;&gt;watcher -->
<g id="edge5" class="edge">
<title>oplog&#45;&gt;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&#45;&gt;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&#45;&gt;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&#45;&gt;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&#45;&gt;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&#45;resume&#45;token)</text>
</g>
<!-- watcher&#45;&gt;resume_token -->
<g id="edge6" class="edge">
<title>watcher&#45;&gt;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&#45;&gt;remote_switch -->
<g id="edge8" class="edge">
<title>watcher&#45;&gt;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&#45;&gt;remote_task -->
<g id="edge9" class="edge">
<title>watcher&#45;&gt;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&#45;&gt;remote_state -->
<g id="edge10" class="edge">
<title>watcher&#45;&gt;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&#45;&gt;remote_history -->
<g id="edge11" class="edge">
<title>watcher&#45;&gt;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&#45;&gt;watcher -->
<g id="edge7" class="edge">
<title>resume_token&#45;&gt;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

View 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"];
}

View 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 &#45; 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&#45;&gt;docker_build -->
<g id="edge1" class="edge">
<title>source&#45;&gt;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&#45;&gt;registry -->
<g id="edge2" class="edge">
<title>docker_build&#45;&gt;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&#45;&gt;dmweb_container -->
<g id="edge3" class="edge">
<title>registry&#45;&gt;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&#45;&gt;dmweb_container -->
<g id="edge8" class="edge">
<title>nginx&#45;&gt;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&#45;&gt;mongo_container -->
<g id="edge4" class="edge">
<title>dmweb_container&#45;&gt;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&#45;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&#45;&gt;mongo_volume -->
<g id="edge5" class="edge">
<title>mongo_container&#45;&gt;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&#45;&gt;nginx -->
<g id="edge7" class="edge">
<title>dns&#45;&gt;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&#45;&gt;dns -->
<g id="edge6" class="edge">
<title>internet&#45;&gt;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

View 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>

View 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>

View 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;
}