Compare commits
2 Commits
e38bf07ef2
...
cb12a8f1cf
| Author | SHA1 | Date | |
|---|---|---|---|
| cb12a8f1cf | |||
| c1255bdb81 |
@@ -125,182 +125,11 @@
|
|||||||
debug:
|
debug:
|
||||||
msg: "{{ data_files_after.stdout_lines }}"
|
msg: "{{ data_files_after.stdout_lines }}"
|
||||||
|
|
||||||
# ── 7. Restart WMT service ────────────────────────────────────────────
|
# ── 9. Reboot ─────────────────────────────────────────────────────────
|
||||||
- name: Restart WMT systemd service
|
- name: Reboot host to apply all changes
|
||||||
become: true
|
become: true
|
||||||
systemd:
|
reboot:
|
||||||
name: wmt
|
msg: "Rebooting after WMT code update "
|
||||||
state: restarted
|
reboot_timeout: 180
|
||||||
enabled: true
|
pre_reboot_delay: 3
|
||||||
register: service_result
|
post_reboot_delay: 15
|
||||||
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
|
|
||||||
+2
-2
@@ -98,8 +98,8 @@ def get_device_config(mac_address):
|
|||||||
_, device_ts, latest_ts = _latest_config_ts(session, mac)
|
_, device_ts, latest_ts = _latest_config_ts(session, mac)
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
# Global settings
|
# Global settings (device custom_chrome_url overrides global chrome_url if set)
|
||||||
'chrome_url': global_cfg.chrome_url,
|
'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_local_url': global_cfg.chrome_local_url or '',
|
||||||
'chrome_insecure_origin': global_cfg.chrome_insecure_origin,
|
'chrome_insecure_origin': global_cfg.chrome_insecure_origin,
|
||||||
'card_api_base_url': global_cfg.card_api_base_url,
|
'card_api_base_url': global_cfg.card_api_base_url,
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ class Device(Base):
|
|||||||
config_synced_at = Column(DateTime) # set by server when client confirms in-sync
|
config_synced_at = Column(DateTime) # set by server when client confirms in-sync
|
||||||
info_reviewed_at = Column(DateTime, default=lambda: datetime(1970, 1, 1))
|
info_reviewed_at = Column(DateTime, default=lambda: datetime(1970, 1, 1))
|
||||||
card_presence = Column(String(10), default='enable')
|
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
|
# Relationships
|
||||||
logs = relationship("LogEntry", back_populates="device")
|
logs = relationship("LogEntry", back_populates="device")
|
||||||
|
|||||||
@@ -299,6 +299,8 @@ def device_edit(device_id):
|
|||||||
device.location = request.form.get('location', '').strip() or None
|
device.location = request.form.get('location', '').strip() or None
|
||||||
device.card_presence = request.form.get('card_presence', 'enable')
|
device.card_presence = request.form.get('card_presence', 'enable')
|
||||||
device.description = request.form.get('notes', '').strip() or None
|
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.config_updated_at = datetime.utcnow()
|
||||||
device.info_reviewed_at = datetime.utcnow()
|
device.info_reviewed_at = datetime.utcnow()
|
||||||
flash('Device updated.', 'success')
|
flash('Device updated.', 'success')
|
||||||
|
|||||||
@@ -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
@@ -781,11 +781,73 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
body.dark-mode .nav-group-header:hover {
|
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 {
|
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>
|
</style>
|
||||||
{% block extra_css %}{% endblock %}
|
{% block extra_css %}{% endblock %}
|
||||||
|
|||||||
@@ -72,6 +72,34 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</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 %}
|
{% if device %}
|
||||||
<div class="alert alert-light border small mb-4">
|
<div class="alert alert-light border small mb-4">
|
||||||
<strong>Last seen:</strong>
|
<strong>Last seen:</strong>
|
||||||
|
|||||||
Reference in New Issue
Block a user