feat: fix migrate_to_wmt playbook; restructure sidebar nav; add Client Name column to WMT dashboard
This commit is contained in:
@@ -97,13 +97,39 @@
|
|||||||
debug:
|
debug:
|
||||||
msg: "work_place will be set to: '{{ work_place_value }}'"
|
msg: "work_place will be set to: '{{ work_place_value }}'"
|
||||||
|
|
||||||
# ── 5. Replace work_place value in WMT/data/config.txt ───────────────
|
# ── 5. Write work_place into WMT/data/config.txt ──────────────────────
|
||||||
- name: Replace work_place in WMT config.txt
|
# Uses Python (always available on Raspberry Pi) to correctly
|
||||||
lineinfile:
|
# read/write the INI file and set work_place inside [device].
|
||||||
path: /home/pi/Desktop/WMT/data/config.txt
|
# Also resets last_synced to epoch so first startup does NOT
|
||||||
regexp: '^work_place\s*=.*'
|
# overwrite work_place with a potentially empty server value.
|
||||||
line: "work_place={{ work_place_value }}"
|
- name: Set work_place in WMT config.txt via Python
|
||||||
backup: true
|
ansible.builtin.shell:
|
||||||
|
cmd: |
|
||||||
|
python3 - <<'PYEOF'
|
||||||
|
import configparser, os
|
||||||
|
path = '/home/pi/Desktop/WMT/data/config.txt'
|
||||||
|
p = configparser.ConfigParser()
|
||||||
|
p.read(path)
|
||||||
|
if not p.has_section('chrome'):
|
||||||
|
p.add_section('chrome')
|
||||||
|
if not p.has_section('card_api'):
|
||||||
|
p.add_section('card_api')
|
||||||
|
if not p.has_section('server'):
|
||||||
|
p.add_section('server')
|
||||||
|
if not p.has_section('device'):
|
||||||
|
p.add_section('device')
|
||||||
|
if not p.has_section('meta'):
|
||||||
|
p.add_section('meta')
|
||||||
|
p.set('device', 'work_place', '{{ work_place_value }}')
|
||||||
|
# Reset last_synced so first startup pull from server does not
|
||||||
|
# overwrite the work_place we just set (server will return the
|
||||||
|
# correct device_name once this device checks in).
|
||||||
|
p.set('meta', 'last_synced', '1970-01-01T00:00:00')
|
||||||
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||||
|
with open(path, 'w') as f:
|
||||||
|
p.write(f)
|
||||||
|
print('work_place set to: {{ work_place_value }}')
|
||||||
|
PYEOF
|
||||||
|
|
||||||
- name: Confirm work_place change
|
- name: Confirm work_place change
|
||||||
command: grep 'work_place' /home/pi/Desktop/WMT/data/config.txt
|
command: grep 'work_place' /home/pi/Desktop/WMT/data/config.txt
|
||||||
|
|||||||
+12
-1
@@ -129,10 +129,20 @@ def execute():
|
|||||||
seen.add(h['hostname'])
|
seen.add(h['hostname'])
|
||||||
|
|
||||||
settings = ansible_service.load_settings()
|
settings = ansible_service.load_settings()
|
||||||
|
|
||||||
|
# Discover custom playbooks (exclude built-ins that have dedicated buttons)
|
||||||
|
_builtin_names = {'update_devices', 'restart_service', 'distribute_ssh_keys', 'system_health'}
|
||||||
|
custom_playbooks = []
|
||||||
|
if ansible_service.playbook_dir.exists():
|
||||||
|
for _f in sorted(ansible_service.playbook_dir.glob('*.yml')):
|
||||||
|
if _f.stem.lower() not in _builtin_names:
|
||||||
|
custom_playbooks.append({'name': _f.stem, 'filename': _f.name})
|
||||||
|
|
||||||
return render_template('ansible/execute.html',
|
return render_template('ansible/execute.html',
|
||||||
inventory=inventory_data,
|
inventory=inventory_data,
|
||||||
all_inv_hosts=all_inv_hosts,
|
all_inv_hosts=all_inv_hosts,
|
||||||
preselect_playbook=preselect,
|
preselect_playbook=preselect,
|
||||||
|
custom_playbooks=custom_playbooks,
|
||||||
use_password_auth=settings.get('use_password_auth', False))
|
use_password_auth=settings.get('use_password_auth', False))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error loading execute form: {e}")
|
logging.error(f"Error loading execute form: {e}")
|
||||||
@@ -141,6 +151,7 @@ def execute():
|
|||||||
inventory={'groups': {}},
|
inventory={'groups': {}},
|
||||||
all_inv_hosts=[],
|
all_inv_hosts=[],
|
||||||
preselect_playbook='',
|
preselect_playbook='',
|
||||||
|
custom_playbooks=[],
|
||||||
use_password_auth=False)
|
use_password_auth=False)
|
||||||
|
|
||||||
elif request.method == 'POST':
|
elif request.method == 'POST':
|
||||||
@@ -462,7 +473,7 @@ def playbook_content():
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
requested_path = Path(playbook_path)
|
requested_path = Path(playbook_path)
|
||||||
if not requested_path.is_absolute():
|
if not requested_path.is_absolute():
|
||||||
requested_path = ansible_service.playbook_dir / requested_path
|
requested_path = Path.cwd() / requested_path
|
||||||
|
|
||||||
# Ensure path is within playbook directory
|
# Ensure path is within playbook directory
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -91,6 +91,12 @@
|
|||||||
<h6 class="text-muted fw-bold mb-2 small text-uppercase">Custom Playbooks</h6>
|
<h6 class="text-muted fw-bold mb-2 small text-uppercase">Custom Playbooks</h6>
|
||||||
<select class="form-select" id="customPlaybook">
|
<select class="form-select" id="customPlaybook">
|
||||||
<option value="">— select custom playbook —</option>
|
<option value="">— select custom playbook —</option>
|
||||||
|
{% for pb in custom_playbooks %}
|
||||||
|
<option value="{{ pb.name }}"
|
||||||
|
{% if preselect_playbook == pb.filename or preselect_playbook == pb.name %}selected{% endif %}>
|
||||||
|
{{ pb.name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<input type="hidden" name="playbook" id="selectedPlaybook">
|
<input type="hidden" name="playbook" id="selectedPlaybook">
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,12 +21,12 @@
|
|||||||
background-color: #f8f9ff;
|
background-color: #f8f9ff;
|
||||||
}
|
}
|
||||||
.code-editor-area {
|
.code-editor-area {
|
||||||
min-height: 400px;
|
min-height: 600px;
|
||||||
border: 1px solid #dee2e6;
|
border: 1px solid #dee2e6;
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
}
|
}
|
||||||
.CodeMirror {
|
.CodeMirror {
|
||||||
height: 400px;
|
height: 600px;
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
}
|
}
|
||||||
.playbook-actions {
|
.playbook-actions {
|
||||||
@@ -244,7 +244,7 @@
|
|||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<pre id="playbookContent" style="max-height: 400px; overflow-y: auto; background-color: #f8f9fa; padding: 15px; border-radius: 5px;"></pre>
|
<pre id="playbookContent" style="max-height: 600px; overflow-y: auto; background-color: #f8f9fa; padding: 15px; border-radius: 5px;"></pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
|||||||
+7
-9
@@ -235,14 +235,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="nav-menu">
|
<ul class="nav-menu">
|
||||||
<div class="nav-section">Device Management</div>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a href="{{ url_for('main.devices') }}" class="nav-link {% if request.endpoint in ['main.devices','main.device_edit','main.device_detail'] %}active{% endif %}">
|
|
||||||
<i class="fas fa-desktop"></i>
|
|
||||||
Devices
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<div class="nav-section">WMT</div>
|
<div class="nav-section">WMT</div>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="{{ url_for('wmt_web.index') }}" class="nav-link {% if request.endpoint == 'wmt_web.index' %}active{% endif %}">
|
<a href="{{ url_for('wmt_web.index') }}" class="nav-link {% if request.endpoint == 'wmt_web.index' %}active{% endif %}">
|
||||||
@@ -266,7 +258,13 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="{{ url_for('wmt_web.devices') }}" class="nav-link {% if request.endpoint in ['wmt_web.devices','wmt_web.device_new','wmt_web.device_edit'] %}active{% endif %}">
|
<a href="{{ url_for('wmt_web.devices') }}" class="nav-link {% if request.endpoint in ['wmt_web.devices','wmt_web.device_new','wmt_web.device_edit'] %}active{% endif %}">
|
||||||
<i class="fas fa-desktop"></i>
|
<i class="fas fa-desktop"></i>
|
||||||
WMT Devices
|
Devices
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="{{ url_for('main.devices') }}" class="nav-link {% if request.endpoint in ['main.devices','main.device_edit','main.device_detail'] %}active{% endif %}">
|
||||||
|
<i class="fas fa-heartbeat"></i>
|
||||||
|
Devices Monitoring
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,7 @@
|
|||||||
<thead class="table-light">
|
<thead class="table-light">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Work Place</th>
|
<th>Work Place</th>
|
||||||
|
<th>Client Name</th>
|
||||||
<th>MAC</th>
|
<th>MAC</th>
|
||||||
<th>IP</th>
|
<th>IP</th>
|
||||||
<th>Last Seen</th>
|
<th>Last Seen</th>
|
||||||
@@ -126,6 +127,7 @@
|
|||||||
{% for d in devices %}
|
{% for d in devices %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>{{ d.device_name or '—' }}</strong></td>
|
<td><strong>{{ d.device_name or '—' }}</strong></td>
|
||||||
|
<td>{{ d.hostname or '—' }}</td>
|
||||||
<td><code>{{ d.mac_address }}</code></td>
|
<td><code>{{ d.mac_address }}</code></td>
|
||||||
<td>{{ d.device_ip or '—' }}</td>
|
<td>{{ d.device_ip or '—' }}</td>
|
||||||
<td class="text-muted small">
|
<td class="text-muted small">
|
||||||
|
|||||||
Reference in New Issue
Block a user