Add certificate view

This commit is contained in:
Melon 2023-11-14 18:04:30 +00:00
parent 8ecd8c287a
commit 0c0f2148bc
Signed by: melon
GPG Key ID: 6C9D970C50D26A25
3 changed files with 202 additions and 1 deletions

30
src/stores/certs.ts Normal file
View File

@ -0,0 +1,30 @@
import {writable} from "svelte/store";
export interface Cert {
id: number;
auto_renew: boolean;
active: boolean;
renewing: boolean;
renew_failed: boolean;
not_after: string;
updated_at: string;
domains: string[];
}
export function siteEqual(a: Cert | null, b: Cert | null) {
if (a == null || b == null) return false;
a.domains.sort();
b.domains.sort();
return (
a.id == b.id &&
a.auto_renew == b.auto_renew &&
a.active == b.active &&
a.renewing == b.renewing &&
a.renew_failed == b.renew_failed &&
a.not_after == b.not_after &&
a.updated_at == b.updated_at &&
JSON.stringify(a.domains) == JSON.stringify(b.domains)
);
}
export const certsTable = writable<{[key: string]: Cert}>({});

View File

@ -1 +1,122 @@
<div style="padding:8px;background-color:#bb7900;">Warning: This is currently still under development</div>
<script lang="ts">
import {domainOption} from "../stores/domain-option";
import {getBearer} from "../stores/login";
import {type Cert, certsTable} from "../stores/certs";
const apiOrchid = import.meta.env.VITE_API_ORCHID;
let tableKeys: string[] = [];
$: tableKeys = Object.entries($certsTable)
.map(x => x[1])
.filter(x => x.domains.map(x => domainFilter(x, $domainOption)).reduce((a, b) => a || b))
.sort((a, b) => {
// sort renew failed first
if (a.renew_failed && b.renew_failed) return a.id - b.id;
if (a.renew_failed) return -1;
if (b.renew_failed) return 1;
return a.id - b.id;
})
.map(x => x.id.toString());
function domainFilter(src: string, domain: string) {
if (domain == "*") return true;
let n = src.indexOf("/");
if (n == -1) n = src.length;
let p = src.slice(0, n);
if (p == domain) return true;
return p.endsWith(domain);
}
let promiseForTable: Promise<void> = Object.entries($certsTable).length === 0 ? reloadTable() : Promise.resolve();
function reloadTable(): Promise<void> {
return new Promise<void>((res, rej) => {
fetch(apiOrchid + "/owned", {headers: {Authorization: getBearer()}})
.then(x => {
if (x.status !== 200) throw new Error("Unexpected status code: " + x.status);
return x.json();
})
.then(x => {
let rows = x as Map<number, Cert>;
Object.values(rows).forEach(x => {
$certsTable[Object(x.id).toString()] = x;
});
console.log($certsTable);
res();
})
.catch(x => rej(x));
});
}
</script>
<div class="wrapper">
<div style="padding:8px;background-color:#bb7900;">Warning: This is currently still under development</div>
<div class="scrolling-area">
{#await promiseForTable}
<div class="text-padding">
<div>Loading...</div>
</div>
{:then}
<table class="main-table">
<thead>
<tr>
<th>ID</th>
<th>Auto Renew</th>
<th>Active</th>
<th>Renewing</th>
<th>Renew Failed</th>
<th>Not After</th>
<th>Domains</th>
</tr>
</thead>
<tbody class="invert-rows">
{#each tableKeys as key (key)}
{@const cert = $certsTable[key]}
<tr class:cert-error={cert.renew_failed}>
<td>{cert.id}</td>
<td>{cert.auto_renew}</td>
<td>{cert.active}</td>
<td>{cert.renewing}</td>
<td>{cert.renew_failed}</td>
<td>
<div>{cert.not_after}</div>
<div>{Math.round((new Date(cert.not_after).getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24))} days until expiry</div>
</td>
<td class="branch-cell">
{#each cert.domains as domain}
<div>{domain}</div>
{/each}
</td>
</tr>
{/each}
</tbody>
</table>
{:catch err}
<div class="text-padding">
<div>Administrator... I hardly know her?</div>
<div>{err}</div>
</div>
{/await}
</div>
</div>
<style lang="scss">
.branch-cell {
display: grid;
grid-template-columns: repeat(1, auto);
justify-content: center;
gap: 8px;
}
// css please explain yourself
tr.cert-error.cert-error {
&:nth-child(2n + 1) {
background-color: #510000;
}
&:nth-child(2n) {
background-color: #330000;
}
}
</style>

View File

@ -150,6 +150,56 @@ func apiServer(verify mjwt.Verifier) {
}
json.NewEncoder(rw).Encode(m)
}))
r.Handle("/v1/orchid/owned", hasPerm(verify, "orchid:cert", func(rw http.ResponseWriter, req *http.Request) {
m := make(map[int]any, 41)
for i := 0; i < 20; i++ {
u := uuid.NewString()
m[i] = map[string]any{
"id": i + 1,
"auto_renew": true,
"active": true,
"renewing": false,
"renew_failed": false,
"not_after": "2024-02-06T11:52:05Z",
"updated_at": "2023-11-08T07:32:08Z",
"domains": []string{
u + ".example.com",
"*." + u + ".example.com",
},
}
}
for i := 0; i < 20; i++ {
u := uuid.NewString()
m[i+20] = map[string]any{
"id": i + 21,
"auto_renew": false,
"active": false,
"renewing": false,
"renew_failed": false,
"not_after": "2024-02-06T11:52:05Z",
"updated_at": "2023-11-08T07:32:08Z",
"domains": []string{
u + ".example.org",
"*." + u + ".example.org",
},
}
}
u := uuid.NewString()
m[40] = map[string]any{
"id": 41,
"auto_renew": false,
"active": false,
"renewing": false,
"renew_failed": true,
"not_after": "2024-02-06T11:52:05Z",
"updated_at": "2023-11-08T07:32:08Z",
"domains": []string{
u + ".example.org",
"*." + u + ".example.org",
},
}
json.NewEncoder(rw).Encode(m)
}))
r.Handle("/v1/sites", hasPerm(verify, "sites:manage", func(rw http.ResponseWriter, req *http.Request) {
if req.Method == http.MethodPost {
defer req.Body.Close()