initial commit
This commit is contained in:
88
webui/views/admin.ejs
Normal file
88
webui/views/admin.ejs
Normal 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
47
webui/views/index.ejs
Normal 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
7
webui/views/login.ejs
Normal 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') %>
|
||||
3
webui/views/partials/footer.ejs
Normal file
3
webui/views/partials/footer.ejs
Normal file
@@ -0,0 +1,3 @@
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
29
webui/views/partials/header.ejs
Normal file
29
webui/views/partials/header.ejs
Normal 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
117
webui/views/share.ejs
Normal 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') %>
|
||||
Reference in New Issue
Block a user