Files
MeshTender/internal/core/templates/org.html
T
2026-07-30 08:46:19 -04:00

209 lines
8.9 KiB
HTML

{{define "title"}}{{.Org.Name}} · MeshTender{{end}}
{{define "header"}}{{template "org-header" .}}{{end}}
{{define "content"}}
{{template "org-tabs" .Nav}}
{{if .Error}}<div class="alert alert-danger">{{.Error}}</div>{{end}}
<!-- Summary stats -->
<div class="row row-cards">
<div class="col-sm-6 col-lg-3">
<div class="card card-sm">
<div class="card-body">
<div class="row align-items-center">
<div class="col-auto"><span class="bg-primary text-white avatar">{{template "icon-users" ""}}</span></div>
<div class="col"><div class="h1 mb-0">{{.MemberCount}}</div><div class="text-secondary">Member{{if ne .MemberCount 1}}s{{end}}</div></div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-lg-3">
<div class="card card-sm">
<div class="card-body">
<div class="row align-items-center">
<div class="col-auto"><span class="bg-green text-white avatar">{{template "icon-user" ""}}</span></div>
<div class="col"><div class="h1 mb-0">{{.AdminCount}}</div><div class="text-secondary">Admin{{if ne .AdminCount 1}}s{{end}}</div></div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-lg-3">
<div class="card card-sm">
<div class="card-body">
<div class="row align-items-center">
<div class="col-auto"><span class="bg-azure text-white avatar">{{template "icon-antenna" ""}}</span></div>
<div class="col"><div class="h1 mb-0">{{.RepeaterCount}}</div><div class="text-secondary">Repeater{{if ne .RepeaterCount 1}}s{{end}}</div></div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-lg-3">
<div class="card card-sm">
<div class="card-body">
<div class="row align-items-center">
<div class="col-auto"><span class="bg-purple text-white avatar">{{template "icon-map-pin" ""}}</span></div>
<div class="col text-truncate">
<div class="h1 mb-0 text-truncate">{{if .Org.Region}}{{.Org.Region}}{{else}}<span class="text-secondary fw-normal">Not set</span>{{end}}</div>
<div class="text-secondary">Region</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row row-cards mt-1">
<!-- Left column: details -->
<div class="col-lg-8">
<div class="card">
<div class="card-header">
<h2 class="card-title">About</h2>
{{if .IsAdmin}}
<div class="card-actions">
<button type="button" class="btn btn-sm" data-testid="edit-profile"
data-bs-toggle="modal" data-bs-target="#org-profile-modal"
hx-get="/orgs/{{.Org.Slug}}/edit" hx-target="#org-profile-modal-content" hx-swap="innerHTML">{{template "icon-pencil" "me-1"}}Edit</button>
</div>
{{end}}
</div>
<div class="card-body">
{{if .Org.Description}}
<div class="markdown">{{markdown .Org.Description}}</div>
{{else}}
<p class="text-secondary mb-0">No description yet.</p>
{{end}}
</div>
</div>
{{if or .Links .IsAdmin}}
<div class="card mt-3">
<div class="card-header">
<h2 class="card-title">Links</h2>
{{if .IsAdmin}}
<div class="card-actions">
<button type="button" class="btn btn-sm" data-testid="edit-links"
data-bs-toggle="modal" data-bs-target="#org-links-modal"
hx-get="/orgs/{{.Org.Slug}}/links" hx-target="#org-links-modal-content" hx-swap="innerHTML">{{template "icon-pencil" "me-1"}}Edit</button>
</div>
{{end}}
</div>
{{if .Links}}
<div class="list-group list-group-flush">
{{range .Links}}
{{if .Href}}
<a class="list-group-item list-group-item-action d-flex align-items-center gap-2" href="{{.Href}}" target="_blank" rel="noopener noreferrer nofollow">
<span class="text-secondary d-flex" title="{{.PlatformName}}">{{template "link-icon" .Platform}}</span>
<span class="flex-fill text-truncate">{{.Display}}</span>
<span class="text-secondary d-flex">{{template "icon-arrow-right" ""}}</span>
</a>
{{else}}
<div class="list-group-item d-flex align-items-center gap-2">
<span class="text-secondary d-flex" title="{{.PlatformName}}">{{template "link-icon" .Platform}}</span>
<span class="flex-fill text-truncate">{{.Display}}</span>
</div>
{{end}}
{{end}}
</div>
{{else}}
<div class="card-body"><p class="text-secondary mb-0">No links yet.</p></div>
{{end}}
</div>
{{end}}
{{if .IsAdmin}}
{{/* Custom domains — hidden for now; the hosting/TLS infrastructure isn't in
place yet. Re-enable this card (plus the domains data in pageOrg and the
/domains routes in web.go) once it is.
<div class="card mt-3">
<div class="card-header"><h2 class="card-title">Custom domains</h2></div>
<div class="card-body">
<p class="text-secondary">
Point your own domain at MeshTender (CNAME it to this app). Once verified, visiting that
domain shows this organization's public page. Sign-in and management always happen on the
main site. TLS certificates for your domain are handled by your hosting/ops setup.
</p>
{{if .Domains}}
<div class="list-group list-group-flush mb-3">
{{range .Domains}}
<div class="list-group-item px-0">
<div class="d-flex align-items-center gap-2 mb-1">
<span class="fw-bold font-monospace">{{.Hostname}}</span>
{{if .Verified}}
<span class="badge bg-success-lt">verified</span>
{{else}}
<span class="badge bg-warning-lt">pending verification</span>
{{end}}
<form method="post" action="/orgs/{{$.Org.Slug}}/domains/delete" class="m-0 ms-auto">
<input type="hidden" name="domain_id" value="{{.ID}}">
<button type="submit" class="btn btn-sm btn-ghost-danger">Remove</button>
</form>
</div>
{{if not .Verified}}
<p class="text-secondary mb-2">Add this DNS TXT record, then click Verify:</p>
<div class="mb-2">
<code class="d-block">{{$.TXTName}}{{.Hostname}}</code>
<code class="d-block">TXT&nbsp;&nbsp;{{.VerificationToken}}</code>
</div>
<form method="post" action="/orgs/{{$.Org.Slug}}/domains/verify" class="m-0">
<input type="hidden" name="domain_id" value="{{.ID}}">
<button type="submit" class="btn btn-sm">Verify</button>
</form>
{{end}}
</div>
{{end}}
</div>
{{end}}
<form method="post" action="/orgs/{{.Org.Slug}}/domains">
<div class="mb-3">
<label class="form-label" for="add_domain">Add a domain</label>
<input type="text" class="form-control font-monospace" id="add_domain" name="hostname" placeholder="mesh.example.org" maxlength="253">
</div>
<button type="submit" class="btn btn-primary">Add domain</button>
</form>
</div>
</div>
*/}}
{{end}}
</div>
<!-- Right column: map -->
<div class="col-lg-4">
{{if .HasMap}}
<div class="card">
<div class="card-header"><h2 class="card-title">Repeaters</h2></div>
<div class="card-body p-2">
<link rel="stylesheet" href="{{ asset "/static/leaflet.css" }}">
<div id="map" style="height:260px" role="region" aria-label="Map of organization repeater locations"></div>
<script src="{{ asset "/static/leaflet.js" }}"></script>
<script src="{{ asset "/static/meshmap.js" }}"></script>
<script nonce="{{.Nonce}}">
meshMap('map', [
{{range .Repeaters}}{{if .HasLocation}}{name: {{.Name}}, lat: {{.Lat}}, lon: {{.Lon}}},{{end}}{{end}}
]);
</script>
</div>
</div>
{{end}}
</div>
</div>
{{if .IsAdmin}}
<!-- Admin edit modals; the About/Links card-header buttons load their fragments here via htmx. -->
<div class="modal modal-blur fade" id="org-profile-modal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-lg" role="document">
<div class="modal-content" id="org-profile-modal-content"></div>
</div>
</div>
<div class="modal modal-blur fade" id="org-links-modal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-lg" role="document">
<div class="modal-content" id="org-links-modal-content"></div>
</div>
</div>
<!-- Per-platform link config for the links editor loaded into the modal above;
link-editor.js re-initializes on htmx:afterSwap. -->
<script nonce="{{.Nonce}}">window.MESHTENDER_LINK_PLATFORMS = {{.PlatformsJS}};</script>
<script src="{{ asset "/static/link-editor.js" }}"></script>
{{end}}
<a class="back-link mt-3" href="/orgs">{{template "icon-arrow-left" "me-1"}}All organizations</a>
{{end}}