@axium/server 0.28.3 → 0.28.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axium/server",
3
- "version": "0.28.3",
3
+ "version": "0.28.5",
4
4
  "author": "James Prevett <axium@jamespre.dev>",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -47,7 +47,7 @@
47
47
  "clean": "rm -rf build .svelte-kit node_modules/{.vite,.vite-temp}"
48
48
  },
49
49
  "peerDependencies": {
50
- "@axium/client": ">=0.9.0",
50
+ "@axium/client": ">=0.9.5",
51
51
  "@axium/core": ">=0.12.0",
52
52
  "kysely": "^0.28.0",
53
53
  "utilium": "^2.3.8",
@@ -77,9 +77,9 @@
77
77
  <p>{user.id}</p>
78
78
  <ClipboardCopy value={user.id} --size="16px" />
79
79
  </div>
80
- <span>
81
- <button class="signout" command="show-modal" commandfor="logout">Sign Out</button>
82
- <button style:cursor="pointer" command="show-modal" commandfor="delete" class="danger">Delete Account</button>
80
+ <div class="inline-button-container">
81
+ <button command="show-modal" commandfor="logout" class="inline-button signout">Sign Out</button>
82
+ <button command="show-modal" commandfor="delete" class="inline-button danger">Delete Account</button>
83
83
  <Logout />
84
84
  <FormDialog
85
85
  id="delete"
@@ -89,30 +89,38 @@
89
89
  >
90
90
  <p>Are you sure you want to delete your account?<br />This action can't be undone.</p>
91
91
  </FormDialog>
92
- </span>
92
+ </div>
93
93
  </div>
94
94
 
95
95
  <div class="section main">
96
96
  <h3>Passkeys</h3>
97
97
  {#each passkeys as passkey}
98
98
  <div class="item passkey">
99
- <dfn title={passkey.deviceType == 'multiDevice' ? 'Multiple devices' : 'Single device'}>
100
- <Icon i={passkey.deviceType == 'multiDevice' ? 'laptop-mobile' : 'mobile'} --size="16px" />
101
- </dfn>
102
- <dfn title="This passkey is {passkey.backedUp ? '' : 'not '}backed up">
103
- <Icon i={passkey.backedUp ? 'circle-check' : 'circle-xmark'} --size="16px" />
104
- </dfn>
105
- {#if passkey.name}
106
- <p>{passkey.name}</p>
107
- {:else}
108
- <p class="subtle"><i>Unnamed</i></p>
109
- {/if}
99
+ <p>
100
+ <dfn title={passkey.deviceType == 'multiDevice' ? 'Multiple devices' : 'Single device'}>
101
+ <Icon i={passkey.deviceType == 'multiDevice' ? 'laptop-mobile' : 'mobile'} --size="16px" />
102
+ </dfn>
103
+ <dfn title="This passkey is {passkey.backedUp ? '' : 'not '}backed up">
104
+ <Icon i={passkey.backedUp ? 'circle-check' : 'circle-xmark'} --size="16px" />
105
+ </dfn>
106
+ {#if passkey.name}
107
+ <p>{passkey.name}</p>
108
+ {:else}
109
+ <p class="subtle"><i>Unnamed</i></p>
110
+ {/if}
111
+ </p>
110
112
  <p>Created {passkey.createdAt.toLocaleString()}</p>
111
- {@render action('edit_passkey:' + passkey.id)}
113
+ <button commandfor="edit_passkey:{passkey.id}" command="show-modal" class="icon-text">
114
+ <Icon i="pen" --size="16px" />
115
+ <span class="mobile-only">Rename</span>
116
+ </button>
112
117
  {#if passkeys.length > 1}
113
- {@render action('delete_passkey:' + passkey.id, 'trash')}
118
+ <button commandfor="delete_passkey:{passkey.id}" command="show-modal" class="icon-text">
119
+ <Icon i="trash" --size="16px" />
120
+ <span class="mobile-only">Delete</span>
121
+ </button>
114
122
  {:else}
115
- <dfn title="You must have at least one passkey" class="disabled">
123
+ <dfn title="You must have at least one passkey" class="disabled icon-text mobile-hide">
116
124
  <Icon i="trash-slash" --fill="#888" --size="16px" />
117
125
  </dfn>
118
126
  {/if}
@@ -140,11 +148,10 @@
140
148
  <p>Are you sure you want to delete this passkey?<br />This action can't be undone.</p>
141
149
  </FormDialog>
142
150
  {/each}
143
- <span>
144
- <button onclick={() => createPasskey(user.id).then(passkeys.push.bind(passkeys))} class="icon-text">
145
- <Icon i="plus" /> Create
146
- </button>
147
- </span>
151
+
152
+ <button onclick={() => createPasskey(user.id).then(passkeys.push.bind(passkeys))} class="inline-button icon-text">
153
+ <Icon i="plus" /> Create
154
+ </button>
148
155
  </div>
149
156
 
150
157
  <div class="section main">
@@ -181,8 +188,4 @@
181
188
  .greeting {
182
189
  font-size: 2em;
183
190
  }
184
-
185
- .signout {
186
- margin-top: 2em;
187
- }
188
191
  </style>
@@ -65,6 +65,7 @@
65
65
 
66
66
  #admin-content {
67
67
  padding-bottom: 4em;
68
+ grid-column: 1;
68
69
  }
69
70
 
70
71
  #admin-sidebar {
@@ -77,6 +78,7 @@
77
78
  justify-content: space-around;
78
79
  gap: 1em;
79
80
  padding: 0.5em;
81
+ z-index: 6;
80
82
  }
81
83
 
82
84
  .sidebar-text {
@@ -25,82 +25,103 @@
25
25
 
26
26
  <form id="filter" method="dialog">
27
27
  <h4>Filters</h4>
28
- <span>Minimum Severity:</span>
29
- <select name="severity" value={data.filter.severity}>
30
- {#each severityNames as value}
31
- <option {value} selected={value == 'info'}>{capitalize(value)}</option>
32
- {/each}
33
- </select>
34
- <span>Since:</span>
35
- <input type="date" name="since" value={data.filter.since} />
36
-
37
- <span>Until:</span>
38
- <input type="date" name="until" value={data.filter.until} />
39
-
40
- <span>Tags:</span>
41
- <input type="text" name="tags" value={data.filter.tags} />
42
-
43
- <span>Event Name:</span>
44
- {#if data.configured}
45
- <select name="event">
46
- <option value="">Any</option>
47
- {#each data.configured.name as name}
48
- <option value={name} selected={data.filter.event == name}>{name}</option>
49
- {/each}
50
- </select>
51
- {:else}
52
- <input type="text" name="event" value={data.filter.event} />
53
- {/if}
54
-
55
- <span>Source:</span>
56
- {#if data.configured}
57
- <select name="source">
58
- <option value="">Any</option>
59
- {#each data.configured.source as source}
60
- <option value={source} selected={data.filter.source == source}>{source}</option>
28
+
29
+ <div class="filter-field">
30
+ <span>Minimum Severity:</span>
31
+ <select name="severity" value={data.filter.severity}>
32
+ {#each severityNames as value}
33
+ <option {value} selected={value == 'info'}>{capitalize(value)}</option>
61
34
  {/each}
62
35
  </select>
63
- {:else}
64
- <input type="text" name="source" value={data.filter.source} />
65
- {/if}
66
-
67
- <span>User UUID:</span>
68
- <input type="text" name="user" size="36" value={data.filter.user} />
69
- <button
70
- onclick={e => {
71
- e.preventDefault();
72
- const fd = new FormData(e.currentTarget.form!);
73
- const params = new URLSearchParams();
74
- for (let [key, value] of fd.entries()) {
75
- if (!value) continue;
76
- switch (key) {
77
- case 'severity':
78
- if (value != 'info') params.set(key, value as string);
79
- break;
80
- case 'since':
81
- case 'until':
82
- params.set(key, new Date(value as string).toISOString());
83
- break;
84
- case 'tags':
85
- for (const tag of value
86
- .toString()
87
- .split(',')
88
- .map(t => t.trim()))
89
- params.append(key, tag);
90
- break;
91
- default:
92
- params.set(key, value as string);
36
+ </div>
37
+
38
+ <div class="filter-field">
39
+ <span>Since:</span>
40
+ <input type="date" name="since" value={data.filter.since} />
41
+ </div>
42
+
43
+ <div class="filter-field">
44
+ <span>Until:</span>
45
+ <input type="date" name="until" value={data.filter.until} />
46
+ </div>
47
+
48
+ <div class="filter-field">
49
+ <span>Tags:</span>
50
+ <input type="text" name="tags" value={data.filter.tags} />
51
+ </div>
52
+
53
+ <div class="filter-field">
54
+ <span>Event Name:</span>
55
+ {#if data.configured}
56
+ <select name="event">
57
+ <option value="">Any</option>
58
+ {#each data.configured.name as name}
59
+ <option value={name} selected={data.filter.event == name}>{name}</option>
60
+ {/each}
61
+ </select>
62
+ {:else}
63
+ <input type="text" name="event" value={data.filter.event} />
64
+ {/if}
65
+ </div>
66
+
67
+ <div class="filter-field">
68
+ <span>Source:</span>
69
+ {#if data.configured}
70
+ <select name="source">
71
+ <option value="">Any</option>
72
+ {#each data.configured.source as source}
73
+ <option value={source} selected={data.filter.source == source}>{source}</option>
74
+ {/each}
75
+ </select>
76
+ {:else}
77
+ <input type="text" name="source" value={data.filter.source} />
78
+ {/if}
79
+ </div>
80
+
81
+ <div class="filter-field">
82
+ <span>User UUID:</span>
83
+ <input type="text" name="user" size="36" value={data.filter.user} />
84
+ </div>
85
+
86
+ <div class="inline-button-container">
87
+ <button
88
+ class="inline-button"
89
+ onclick={e => {
90
+ e.preventDefault();
91
+ const fd = new FormData(e.currentTarget.form!);
92
+ const params = new URLSearchParams();
93
+ for (let [key, value] of fd.entries()) {
94
+ if (!value) continue;
95
+ switch (key) {
96
+ case 'severity':
97
+ if (value != 'info') params.set(key, value as string);
98
+ break;
99
+ case 'since':
100
+ case 'until':
101
+ params.set(key, new Date(value as string).toISOString());
102
+ break;
103
+ case 'tags':
104
+ for (const tag of value
105
+ .toString()
106
+ .split(',')
107
+ .map(t => t.trim()))
108
+ params.append(key, tag);
109
+ break;
110
+ default:
111
+ params.set(key, value as string);
112
+ }
93
113
  }
94
- }
95
- location.search = params ? '?' + params.toString() : '';
96
- }}>Apply</button
97
- >
98
- <button
99
- onclick={e => {
100
- e.preventDefault();
101
- location.search = '';
102
- }}>Reset</button
103
- >
114
+ location.search = params ? '?' + params.toString() : '';
115
+ }}>Apply</button
116
+ >
117
+ <button
118
+ class="inline-button"
119
+ onclick={e => {
120
+ e.preventDefault();
121
+ location.search = '';
122
+ }}>Reset</button
123
+ >
124
+ </div>
104
125
  </form>
105
126
 
106
127
  <div class="list-container">
@@ -146,13 +167,22 @@
146
167
  background-color: var(--bg-menu);
147
168
  padding: 1em;
148
169
  border-radius: 0.5em;
149
- display: grid;
150
- grid-template-columns: repeat(2, 1fr);
170
+ display: flex;
171
+ flex-wrap: wrap;
172
+ flex-direction: column;
151
173
  gap: 0.25em;
152
174
  width: fit-content;
175
+ }
176
+
177
+ .filter-field {
178
+ display: grid;
179
+ grid-template-columns: 1fr 1fr;
180
+ gap: 0.25em;
181
+ }
153
182
 
154
- h4 {
155
- grid-column: 1 / span 2;
183
+ @media (width < 700px) {
184
+ #filter {
185
+ width: calc(100%);
156
186
  }
157
187
  }
158
188
  </style>
@@ -26,7 +26,7 @@
26
26
  <div class="user list-item" onclick={e => e.currentTarget === e.target && (location.href = '/admin/users/' + user.id)}>
27
27
  <span>{user.name}</span>
28
28
  <span>{user.email}</span>
29
- <span>
29
+ <span class="mobile-hide">
30
30
  {#if user.isAdmin}
31
31
  {@render attr('crown', 'Admin', '#710')}
32
32
  {/if}
@@ -37,8 +37,14 @@
37
37
  {@render attr('at', role)}
38
38
  {/each}
39
39
  </span>
40
- <a href="/admin/audit?user={user.id}"><Icon i="file-shield" /></a>
41
- <a href="/admin/users/{user.id}"><Icon i="chevron-right" /></a>
40
+ <a class="icon-text mobile-button" href="/admin/audit?user={user.id}">
41
+ <Icon i="file-shield" />
42
+ <span class="mobile-only">Audit</span>
43
+ </a>
44
+ <a class="icon-text mobile-button" href="/admin/users/{user.id}">
45
+ <Icon i="chevron-right" />
46
+ <span class="mobile-only">Manage</span>
47
+ </a>
42
48
  </div>
43
49
  {:else}
44
50
  <div class="error">No users!</div>
@@ -46,10 +52,6 @@
46
52
  </div>
47
53
 
48
54
  <style>
49
- .list-item {
50
- grid-template-columns: 1fr 1fr 3fr repeat(2, 2em);
51
- }
52
-
53
55
  .attribute {
54
56
  border-radius: 1em;
55
57
  padding: 0.25em 0.75em;
@@ -57,4 +59,14 @@
57
59
  align-items: center;
58
60
  gap: 0.25em;
59
61
  }
62
+
63
+ .list-item {
64
+ grid-template-columns: 1fr 1fr 3fr repeat(2, 2em);
65
+ }
66
+
67
+ @media (width < 700px) {
68
+ .list-item {
69
+ grid-template-columns: 1fr 1fr;
70
+ }
71
+ }
60
72
  </style>