initial commit

This commit is contained in:
Ludwig Lehnert
2026-02-03 16:39:37 +01:00
commit 70fe6076a4
30 changed files with 2128 additions and 0 deletions

88
webui/views/admin.ejs Normal file
View File

@@ -0,0 +1,88 @@
<%- include('partials/header', { title: 'Admin', user }) %>
<section class="panel">
<h1>Access logs</h1>
<p class="muted">Every authenticated request and share operation is recorded.</p>
<div class="table-wrap">
<table class="table">
<thead>
<tr>
<th>Time (UTC)</th>
<th>User</th>
<th>Action</th>
<th>Method</th>
<th>Path</th>
<th>Status</th>
<th>Share</th>
</tr>
</thead>
<tbody>
<% logs.forEach((log) => { %>
<tr>
<td><%= log.occurred_at %></td>
<td><%= log.user_upn %></td>
<td><%= log.action %></td>
<td><%= log.method %></td>
<td><%= log.path %></td>
<td><%= log.status_code || '' %></td>
<td><%= log.share_id || '' %></td>
</tr>
<% }) %>
</tbody>
</table>
</div>
</section>
<section class="grid">
<div class="panel">
<h2>Daily activity</h2>
<canvas id="dailyChart" height="200"></canvas>
</div>
<div class="panel">
<h2>Actions breakdown</h2>
<canvas id="actionChart" height="200"></canvas>
</div>
</section>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.2/dist/chart.umd.min.js"></script>
<script>
const dailyLabels = <%- JSON.stringify(daily.map(item => item.day)) %>;
const dailyCounts = <%- JSON.stringify(daily.map(item => item.count)) %>;
const actionLabels = <%- JSON.stringify(actions.map(item => item.action)) %>;
const actionCounts = <%- JSON.stringify(actions.map(item => item.count)) %>;
new Chart(document.getElementById('dailyChart'), {
type: 'line',
data: {
labels: dailyLabels,
datasets: [{
label: 'Requests',
data: dailyCounts,
borderColor: '#d26b2f',
backgroundColor: 'rgba(210, 107, 47, 0.2)',
tension: 0.3,
fill: true
}]
},
options: {
responsive: true,
plugins: { legend: { display: false } }
}
});
new Chart(document.getElementById('actionChart'), {
type: 'bar',
data: {
labels: actionLabels,
datasets: [{
label: 'Count',
data: actionCounts,
backgroundColor: '#b75522'
}]
},
options: {
responsive: true,
plugins: { legend: { display: false } }
}
});
</script>
<%- include('partials/footer') %>

47
webui/views/index.ejs Normal file
View File

@@ -0,0 +1,47 @@
<%- include('partials/header', { title: 'Shares', user }) %>
<section class="panel">
<h1>Create share</h1>
<% if (error) { %>
<div class="alert"><%= error %></div>
<% } %>
<form action="/shares" method="post" class="form-row">
<input name="name" placeholder="share-name" required />
<button class="primary" type="submit">Create</button>
</form>
<div class="hint">Share names are 1-31 chars and must avoid reserved names like private or IPC$.</div>
</section>
<section class="grid">
<div class="panel">
<h2>My shares</h2>
<% if (!myShares.length) { %>
<p class="muted">You do not own any shares yet.</p>
<% } %>
<ul class="list">
<% myShares.forEach((share) => { %>
<li>
<a href="/shares/<%= share.id %>"><%= share.name %></a>
<span class="badge">Owner</span>
</li>
<% }) %>
</ul>
</div>
<div class="panel">
<h2>Shares you can access</h2>
<% if (!shares.length) { %>
<p class="muted">No shares are available to you yet.</p>
<% } %>
<ul class="list">
<% shares.forEach((share) => { %>
<li>
<a href="/shares/<%= share.id %>"><%= share.name %></a>
<% if (share.owner_upn === user.upn) { %>
<span class="badge">Owner</span>
<% } %>
</li>
<% }) %>
</ul>
</div>
</section>
<%- include('partials/footer') %>

7
webui/views/login.ejs Normal file
View File

@@ -0,0 +1,7 @@
<%- include('partials/header', { title: 'Sign in', user: null }) %>
<section class="panel">
<h1>Sign in</h1>
<p>Use Entra ID to access your file shares.</p>
<a class="primary" href="/auth/login">Continue with Entra ID</a>
</section>
<%- include('partials/footer') %>

View File

@@ -0,0 +1,3 @@
</main>
</body>
</html>

View File

@@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title><%= title %></title>
<link rel="stylesheet" href="/public/styles.css" />
</head>
<body>
<header class="site-header">
<div class="brand">
<span class="brand-mark">AAF</span>
<div>
<div class="brand-title">AAD File Shares</div>
<div class="brand-subtitle">AD-authenticated SMB shares</div>
</div>
</div>
<% if (user) { %>
<div class="user">
<div class="user-name"><%= user.name %></div>
<div class="user-upn"><%= user.upn %></div>
<a class="secondary" href="/admin">Admin</a>
<form action="/auth/logout" method="post">
<button class="secondary" type="submit">Sign out</button>
</form>
</div>
<% } %>
</header>
<main class="main">

117
webui/views/share.ejs Normal file
View File

@@ -0,0 +1,117 @@
<%- include('partials/header', { title: 'Share detail', user }) %>
<% const isOwner = share.owner_upn === user.upn; %>
<section class="panel">
<div class="panel-head">
<div>
<h1><%= share.name %></h1>
<div class="muted">Owner: <%= share.owner_upn %></div>
<div class="muted">State: <%= share.state %></div>
</div>
<% if (isOwner) { %>
<form action="/shares/<%= share.id %>/delete" method="post">
<button class="danger" type="submit">Disable share</button>
</form>
<% } %>
</div>
</section>
<section class="panel">
<h2>Members</h2>
<% if (error) { %>
<div class="alert"><%= error %></div>
<% } %>
<% if (isOwner) { %>
<form action="/shares/<%= share.id %>/members" method="post" class="form-grid">
<input name="principal" placeholder="user@domain" required />
<input type="hidden" name="principalType" value="user" />
<select name="role" required>
<option value="rw">RW</option>
<option value="ro">RO</option>
<option value="owner">Owner</option>
</select>
<button class="primary" type="submit">Add/Update</button>
</form>
<form action="/shares/<%= share.id %>/members" method="post" class="form-grid">
<input name="principal" placeholder="group-name" required />
<input type="hidden" name="principalType" value="group" />
<select name="role" required>
<option value="rw">RW</option>
<option value="ro">RO</option>
<option value="owner">Owner</option>
</select>
<button class="secondary" type="submit">Add Group</button>
</form>
<% } %>
<ul class="list">
<% members.forEach((member) => { %>
<li>
<div>
<div class="member"><%= member.upn || member.name %></div>
<div class="muted"><%= member.type %></div>
</div>
<div class="member-actions">
<span class="badge"><%= member.role.toUpperCase() %></span>
<% if (isOwner) { %>
<form action="/shares/<%= share.id %>/members" method="post">
<input type="hidden" name="principal" value="<%= member.upn || member.name %>" />
<input type="hidden" name="principalType" value="<%= member.type %>" />
<input type="hidden" name="action" value="remove" />
<button class="secondary" type="submit">Remove</button>
</form>
<% } %>
</div>
</li>
<% }) %>
</ul>
</section>
<section class="panel">
<h2>Local groups</h2>
<% if (isOwner) { %>
<form action="/groups" method="post" class="form-row">
<input name="name" placeholder="group-name" required />
<input type="hidden" name="shareId" value="<%= share.id %>" />
<button class="primary" type="submit">Create group</button>
</form>
<% } %>
<div class="hint">Groups are stored in SQLite and can be assigned roles per share.</div>
<div class="group-grid">
<% if (!groups.length) { %>
<div class="muted">No local groups yet.</div>
<% } %>
<% groups.forEach((group) => { %>
<div class="group-card">
<div class="group-head">
<div>
<div class="group-title"><%= group.name %></div>
<div class="muted"><%= group.member_count %> members</div>
</div>
</div>
<% if (isOwner) { %>
<form action="/groups/<%= group.id %>/members" method="post" class="form-row">
<input name="member" placeholder="user@domain" required />
<input type="hidden" name="shareId" value="<%= share.id %>" />
<button class="secondary" type="submit">Add member</button>
</form>
<% } %>
<ul class="list compact">
<% group.members.forEach((member) => { %>
<li>
<span><%= member.user_upn %></span>
<% if (isOwner) { %>
<form action="/groups/<%= group.id %>/members" method="post">
<input type="hidden" name="member" value="<%= member.user_upn %>" />
<input type="hidden" name="action" value="remove" />
<input type="hidden" name="shareId" value="<%= share.id %>" />
<button class="secondary" type="submit">Remove</button>
</form>
<% } %>
</li>
<% }) %>
</ul>
</div>
<% }) %>
</div>
</section>
<%- include('partials/footer') %>