chore(webui): iterate on the states page

This commit is contained in:
Julien Dessaux 2025-04-07 00:17:17 +02:00
parent ee11821ed1
commit 59b3d49a4f
Signed by: adyxax
GPG key ID: F92E51B86E07177E
2 changed files with 113 additions and 36 deletions

View file

@ -1,10 +1,10 @@
{{ define "main" }} {{ define "main" }}
<h1>States</h1> <h1>States</h1>
<div class="flex-row" style="justify-content: space-between;"> <div class="flex-row" style="justify-content:space-between;">
<div style="min-width: 240px;"> <div style="min-width:240px;">
<p>TfStated is currently managing {{ len .States }} states.</p> <p>TfStated is currently managing <span class=button>{{ len .States }}</span> states.</p>
<p>Use this page to inspect the existing states.</p> <p>Use this page to inspect the existing states.</p>
<p>You also have the option to upload a state file in order to create a new one. This is equivalent to using the <code>state push</code> command of OpenTofu/Terraform on a brand new state.</p> <p>You also have the option to upload a JSON state file in order to create a new state in TfStated. This is equivalent to using the <code>state push</code> command of OpenTofu/Terraform on a brand new state.</p>
</div> </div>
<form action="/states" enctype="multipart/form-data" method="post"> <form action="/states" enctype="multipart/form-data" method="post">
<fieldset> <fieldset>
@ -12,35 +12,74 @@
<div class="grid-2"> <div class="grid-2">
<label for="path">Path</label> <label for="path">Path</label>
<input autofocus <input autofocus
class="flex-stretch" {{ if or .PathDuplicate .PathError }}class="error"{{ end }}
id="path" id="path"
name="path" name="path"
required required
type="text" type="text"
value="{{ .Path }}"> value="{{ .Path }}">
{{ if .PathDuplicate }} <label for="file" style="min-width:120px;">JSON state file</label>
<span class="error">This path already exist</span>
{{ else if .PathError }}
<span class="error">A valid URL path beginning with a / is expected.</span>
{{ end }}
<label for="file" style="min-width: 120px">JSON state file</label>
<input id="file" <input id="file"
name="file" name="file"
required required
type="file"> type="file">
</div> </div>
{{ if .PathDuplicate }}
<span class="error">This path already exist.</span>
{{ else if .PathError }}
<span class="error">
Path needs to be a valid
<span class="tooltip">
absolute
<span class="tooltip-text">
URL path is considered absolute when it starts with a <code>/</code> character.
</span>
</span>
and
<span class="tooltip">
clean
<span class="tooltip-text">
A URL path is considered clean when it has no relative path elements like <code>../</code>, repeated <code>//</code> and when it does not end with a <code>/</code>.
</span>
</span>
URL path.
</span>
{{ end }}
<div style="align-self:stretch; display:flex; justify-content:flex-end;">
<button class="primary" type="submit" value="submit">Upload and Create State</button> <button class="primary" type="submit" value="submit">Upload and Create State</button>
</div>
</fieldset> </fieldset>
</form> </form>
</div> </div>
<article aria-role="table" class="grid-3" style="margin-top: 16px; justify-items: stretch;"> <article>
<span>Path</span> <table style="width:100%;">
<span>Updated</span> <thead>
<span>Locked</span> <tr>
<th>Path</th>
<th>Updated</th>
<th>Locked</th>
</tr>
</thead>
<tbody>
{{ range .States }} {{ range .States }}
<a href="/states/{{ .Id }}">{{ .Path }}</a> <tr>
<a href="/states/{{ .Id }}">{{ .Updated }}</a> <td><a href="/states/{{ .Id }}">{{ .Path }}</a></td>
<a href="/states/{{ .Id }}" style="text-align: center;">{{ if eq .Lock nil }}no{{ else }}yes{{ end }}</a> <td>{{ .Updated }}</td>
<td style="text-align:center;">
{{ if eq .Lock nil }}
unlocked
{{ else }}
<span class="tooltip">
locked
<span class="tooltip-text">
{{ .State.Lock }}
</span>
</span>
{{ end }} {{ end }}
</td>
</tr>
{{ end }}
</tbody>
</table>
</article> </article>
{{ end }} {{ end }}

View file

@ -67,18 +67,23 @@ article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav,
display: block; display: block;
} }
* { * {
box-sizing: border-box;
-webkit-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent;
} }
*, *::before, *::after { *, *::before, *::after {
box-sizing: border-box; box-sizing: border-box;
} }
textarea, label,
select,
input, input,
progress { progress,
width: fit-content; select,
textarea {
min-width: fit-content;
overflow: hidden;
text-overflow: ellipsis;
}
input:not([type=image i], [type=range i], [type=checkbox i], [type=radio i]) {
overflow: clip !important;
overflow-clip-margin: 0 !important;
} }
html { html {
@ -242,7 +247,7 @@ fieldset {
margin: 0px 0px 8px 0px; margin: 0px 0px 8px 0px;
padding: 8px; padding: 8px;
} }
button { button, .button {
background-color: var(--bg-2); background-color: var(--bg-2);
border: 1px solid var(--orange); border: 1px solid var(--orange);
color: var(--orange); color: var(--orange);
@ -266,8 +271,8 @@ button:hover {
color: var(--red); color: var(--red);
} }
.primary { .primary {
background-color: var(--orange); background-color: var(--orange) !important;
color: var(--fg-1); color: var(--fg-1) !important;
} }
.flex-column { .flex-column {
display: flex; display: flex;
@ -376,19 +381,52 @@ footer a {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.flex-row {
flex-wrap: wrap;
}
} }
th {
border-bottom: 1px solid var(--fg-0);
table tbody a {
display: block;
text-decoration: none;
transition: all 0.25s ease-out;
} }
.clickable-rows tbody tr:hover a { .clickable-rows tbody tr:hover a {
background-color: var(--secondary-container); background-color: var(--secondary-container);
} }
.tooltip {
border-bottom: 1px dotted var(--fg-0);
display: inline-block;
position: relative;
}
.tooltip .tooltip-text {
background-color: var(--bg-1);
border: 1px solid var(--fg-0);
border-radius: 4px;
color: var(--fg-0);
left: 50%;
margin-left: -60px;
padding: 5px 0;
position: absolute;
text-align: center;
top: 100%;
visibility: hidden;
width: 120px;
z-index: 1;
}
.tooltip .tooltip-text::after {
border-color: transparent transparent var(--fg-0) transparent;
border-style: solid;
border-width: 5px;
bottom: 100%; /* At the top of the tooltip */
content: " ";
left: 50%;
margin-left: -5px;
position: absolute;
}
.tooltip:hover .tooltip-text {
visibility: visible;
}
.material-icons { .material-icons {
font-size: 16px; font-size: 16px;
} }