Compare commits

...

2 Commits

Author SHA1 Message Date
ske087 cb12a8f1cf sync: add base.html improvements, update_wmt_code playbook, wmt_releases v2.9, run script 2026-05-25 16:37:10 +03:00
ske087 c1255bdb81 feat: add per-device custom Chrome production URL override
- Device model: add custom_chrome_url nullable column
- API: GET /api/wmt/config/<mac> returns device custom_chrome_url when set,
  falls back to WMTGlobalConfig.chrome_url for all other devices
- Web: device_edit POST saves/clears custom_chrome_url from form
- Template: wmt/device_form.html adds Chrome URL override section with
  clear status indicator (using global vs custom URL)
- DB: ALTER TABLE devices ADD COLUMN custom_chrome_url VARCHAR(500) applied
2026-05-25 16:23:29 +03:00
9 changed files with 112 additions and 183 deletions
+8 -179
View File
@@ -124,183 +124,12 @@
- name: Show data/ contents after update (should match before)
debug:
msg: "{{ data_files_after.stdout_lines }}"
# ── 7. Restart WMT service ────────────────────────────────────────────
- name: Restart WMT systemd service
# ── 9. Reboot ─────────────────────────────────────────────────────────
- name: Reboot host to apply all changes
become: true
systemd:
name: wmt
state: restarted
enabled: true
register: service_result
ignore_errors: true
- name: Show service state
debug:
msg: "WMT service state: {{ service_result.status.ActiveState | default('unknown') }}"
when: service_result is not failed
- name: Warn if service restart failed
debug:
msg: "WARNING: wmt service restart failed the device may need a manual reboot."
when: service_result is failed
vars:
wmt_dir: /home/pi/Desktop/WMT
tmp_zip: /tmp/wmt_update.zip
# Controller address override on CLI with -e "server_url=http://..."
server_url: "http://{{ hostvars[inventory_hostname]['ansible_host'] | default(ansible_host) | regex_replace('\\d+\\.\\d+$', '10.76.157.1') }}"
tasks:
# ── 0. Resolve server URL ─────────────────────────────────────────────
# The monitoring server address is read from the device's own config.txt
# so we don't have to hard-code it here.
- name: Read server_host from WMT config.txt
shell: |
grep -E '^\s*server_host\s*=' {{ wmt_dir }}/data/config.txt 2>/dev/null \
| head -1 | awk -F'=' '{print $2}' | tr -d ' \r\n'
register: cfg_server_host
changed_when: false
ignore_errors: true
- name: Read server_port from WMT config.txt
shell: |
grep -E '^\s*server_port\s*=' {{ wmt_dir }}/data/config.txt 2>/dev/null \
| head -1 | awk -F'=' '{print $2}' | tr -d ' \r\n'
register: cfg_server_port
changed_when: false
ignore_errors: true
- name: Set monitoring server base URL
set_fact:
monitoring_base: "http://{{ cfg_server_host.stdout | default('rpi-ansible') }}:{{ cfg_server_port.stdout | default('5000') }}"
- name: Show resolved server URL
debug:
msg: "Monitoring server: {{ monitoring_base }}"
# ── 1. Check latest version on server ────────────────────────────────
- name: Query latest WMT version from monitoring server
uri:
url: "{{ monitoring_base }}/api/wmt/client/version"
method: GET
return_content: true
timeout: 15
register: version_response
ignore_errors: true
- name: Show server version info
debug:
msg: "Server release: v{{ version_response.json.version | default('unknown') }} ({{ version_response.json.filename | default('n/a') }})"
when: version_response is not failed
- name: Fail if server version endpoint unreachable
fail:
msg: "Cannot reach {{ monitoring_base }}/api/wmt/client/version is the server running?"
when: version_response is failed
# ── 2. Get current local version ─────────────────────────────────────
- name: Read first line of local app.py
shell: head -1 {{ wmt_dir }}/app.py 2>/dev/null || echo "unknown"
register: local_first_line
changed_when: false
- name: Extract local version number
set_fact:
local_version: "{{ local_first_line.stdout | regex_search('version\\s+([\\d.]+)', '\\1') | first | default('0') }}"
- name: Show local version
debug:
msg: "Local version: {{ local_version }} | Server version: {{ version_response.json.version }}"
# ── 3. Ensure WMT directory exists ───────────────────────────────────
- name: Ensure WMT directory exists
file:
path: "{{ wmt_dir }}"
state: directory
owner: pi
group: pi
mode: '0755'
# ── 4. Download release zip ───────────────────────────────────────────
- name: Download WMT release zip from monitoring server
get_url:
url: "{{ monitoring_base }}/api/wmt/client/download"
dest: "{{ tmp_zip }}"
force: true
timeout: 120
mode: '0644'
# ── 5. Back up current app.py ─────────────────────────────────────────
- name: Back up current app.py
copy:
src: "{{ wmt_dir }}/app.py"
dest: "{{ wmt_dir }}/app.py.bak.{{ local_version }}"
remote_src: true
owner: pi
group: pi
mode: preserve
ignore_errors: true
# ── 6. Extract zip skip data/ directory ─────────────────────────────
- name: Extract WMT release zip (preserving data/ directory)
shell: |
cd {{ wmt_dir }}
python3 - <<'EOF'
import zipfile, os, sys
zip_path = "{{ tmp_zip }}"
dest = "{{ wmt_dir }}"
skipped = 0
extracted = 0
with zipfile.ZipFile(zip_path, 'r') as zf:
for member in zf.infolist():
p = member.filename.replace('\\', '/')
if p.startswith('data/') or p == 'data':
skipped += 1
continue
zf.extract(member, dest)
extracted += 1
print(f"Extracted {extracted} files, skipped {skipped} data/ entries")
EOF
register: extract_result
changed_when: true
- name: Show extraction result
debug:
msg: "{{ extract_result.stdout }}"
# ── 7. Fix ownership ──────────────────────────────────────────────────
- name: Set correct ownership on WMT directory
become: true
file:
path: "{{ wmt_dir }}"
owner: pi
group: pi
recurse: true
# ── 8. Clean up temp zip ──────────────────────────────────────────────
- name: Remove temporary zip file
file:
path: "{{ tmp_zip }}"
state: absent
# ── 9. Restart WMT service ────────────────────────────────────────────
- name: Restart WMT systemd service
become: true
systemd:
name: wmt
state: restarted
enabled: true
register: service_result
ignore_errors: true
- name: Show service restart result
debug:
msg: "Service state: {{ service_result.status.ActiveState | default('unknown') }}"
when: service_result is not failed
- name: Warn if service restart failed
debug:
msg: "WARNING: wmt service restart failed the device may need a manual reboot."
when: service_result is failed
reboot:
msg: "Rebooting after WMT code update "
reboot_timeout: 180
pre_reboot_delay: 3
post_reboot_delay: 15
+2 -2
View File
@@ -98,8 +98,8 @@ def get_device_config(mac_address):
_, device_ts, latest_ts = _latest_config_ts(session, mac)
payload = {
# Global settings
'chrome_url': global_cfg.chrome_url,
# Global settings (device custom_chrome_url overrides global chrome_url if set)
'chrome_url': (device.custom_chrome_url if device and device.custom_chrome_url else global_cfg.chrome_url),
'chrome_local_url': global_cfg.chrome_local_url or '',
'chrome_insecure_origin': global_cfg.chrome_insecure_origin,
'card_api_base_url': global_cfg.card_api_base_url,
+1
View File
@@ -52,6 +52,7 @@ class Device(Base):
config_synced_at = Column(DateTime) # set by server when client confirms in-sync
info_reviewed_at = Column(DateTime, default=lambda: datetime(1970, 1, 1))
card_presence = Column(String(10), default='enable')
custom_chrome_url = Column(String(500), nullable=True) # per-device production URL override (overrides WMTGlobalConfig.chrome_url)
# Relationships
logs = relationship("LogEntry", back_populates="device")
+2
View File
@@ -299,6 +299,8 @@ def device_edit(device_id):
device.location = request.form.get('location', '').strip() or None
device.card_presence = request.form.get('card_presence', 'enable')
device.description = request.form.get('notes', '').strip() or None
custom_url = request.form.get('custom_chrome_url', '').strip()
device.custom_chrome_url = custom_url if custom_url else None
device.config_updated_at = datetime.utcnow()
device.info_reviewed_at = datetime.utcnow()
flash('Device updated.', 'success')
+6
View File
@@ -0,0 +1,6 @@
{
"version": "2.9",
"notes": "",
"uploaded_at": "2026-05-13T13:17:18",
"filename": "wmt_v2.9.zip"
}
Binary file not shown.
+64 -2
View File
@@ -781,11 +781,73 @@
}
body.dark-mode .nav-group-header:hover {
background-color: rgba(255,255,255,0.08);
background-color: rgba(255,255,255,0.07);
}
body.dark-mode .nav-group-header.open {
background-color: rgba(255,255,255,0.1);
background-color: rgba(255,255,255,0.09);
}
/* ── Dark mode sidebar ── */
body.dark-mode .sidebar {
background: linear-gradient(160deg, #0d0d0d 0%, #1a1a2e 50%, #16213e 100%);
box-shadow: 2px 0 16px rgba(0,0,0,0.6);
}
body.dark-mode .sidebar .logo {
border-bottom-color: rgba(255,255,255,0.08);
}
body.dark-mode .sidebar .logo small {
color: #7f8c9a;
}
body.dark-mode .sidebar .nav-link {
color: #b0bec5;
}
body.dark-mode .sidebar .nav-link:hover {
background-color: rgba(100,181,246,0.12);
color: #e0f0ff;
}
body.dark-mode .sidebar .nav-link.active {
background-color: rgba(100,181,246,0.2);
color: #e0f0ff;
border-left: 3px solid #64b5f6;
padding-left: 12px;
}
body.dark-mode .sidebar .nav-link i {
color: #64b5f6;
}
body.dark-mode .sidebar .nav-link.active i {
color: #90caf9;
}
body.dark-mode .sidebar .nav-link.admin-link {
color: #ef9a9a;
border-top-color: rgba(255,255,255,0.07);
}
body.dark-mode .sidebar .nav-link.admin-link:hover {
background-color: rgba(239,154,154,0.15);
color: #ffcdd2;
}
body.dark-mode .sidebar .nav-link.admin-link.active {
background-color: rgba(239,154,154,0.22);
color: #ffcdd2;
}
body.dark-mode .sidebar .nav-link.admin-link i {
color: #ef9a9a;
}
body.dark-mode .dark-mode-btn {
border-top-color: rgba(255,255,255,0.07);
color: #b0bec5;
}
</style>
{% block extra_css %}{% endblock %}
+28
View File
@@ -72,6 +72,34 @@
</select>
</div>
<hr>
<h6 class="text-muted mb-1">Chrome Launch Production URL Override</h6>
<p class="text-muted small mb-3">
Leave blank to use the global default URL configured in
<a href="{{ url_for('wmt_web.settings') }}" target="_blank">WMT Settings</a>.
Fill in only if this device needs a different production page.
</p>
<div class="mb-3">
<label class="form-label fw-semibold">Custom Production URL
<small class="text-muted fw-normal">(device-specific override)</small>
</label>
<input type="url" name="custom_chrome_url" class="form-control"
value="{{ device.custom_chrome_url or '' if device else '' }}"
placeholder="Leave blank to use global default">
{% if device and device.custom_chrome_url %}
<div class="form-text text-warning">
<i class="fas fa-exclamation-triangle me-1"></i>
This device uses a custom URL instead of the global default.
Clear the field to revert to the global setting.
</div>
{% else %}
<div class="form-text text-success">
<i class="fas fa-check-circle me-1"></i>
Using global default production URL.
</div>
{% endif %}
</div>
{% if device %}
<div class="alert alert-light border small mb-4">
<strong>Last seen:</strong>
+1
View File
@@ -0,0 +1 @@
source venv/bin/activate && python3 main.py