html_templates
src
static
···
48
48
p256 = { version = "0.13", features = ["ecdsa", "jwk"] }
49
49
jose-jwk = { version = "0.1", features = ["p256"] }
50
50
base64 = "0.22"
51
51
-
url = "2"
51
51
+
···
5
5
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, viewport-fit=cover"/>
6
6
<meta name="referrer" content="origin-when-cross-origin"/>
7
7
<title>{{account.handle}} - {{pds_hostname}}</title>
8
8
-
<style>
9
9
-
{{> admin/partials/base_css.hbs}}
10
10
-
11
11
-
.page-title {
12
12
-
margin-bottom: 4px;
13
13
-
}
14
14
-
15
15
-
.page-subtitle {
16
16
-
font-size: 0.8125rem;
17
17
-
color: var(--secondary-color);
18
18
-
font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
19
19
-
margin-bottom: 24px;
20
20
-
word-break: break-all;
21
21
-
}
22
22
-
23
23
-
.detail-section {
24
24
-
background: var(--bg-primary-color);
25
25
-
border: 1px solid var(--border-color);
26
26
-
border-radius: 10px;
27
27
-
padding: 20px;
28
28
-
margin-bottom: 16px;
29
29
-
}
30
30
-
31
31
-
.detail-section h3 {
32
32
-
font-size: 0.875rem;
33
33
-
font-weight: 600;
34
34
-
margin-bottom: 12px;
35
35
-
}
36
36
-
37
37
-
.detail-row {
38
38
-
display: flex;
39
39
-
justify-content: space-between;
40
40
-
align-items: center;
41
41
-
padding: 8px 0;
42
42
-
font-size: 0.8125rem;
43
43
-
border-bottom: 1px solid var(--border-color);
44
44
-
}
45
45
-
46
46
-
.detail-row:last-child {
47
47
-
border-bottom: none;
48
48
-
}
49
49
-
50
50
-
.detail-row .label {
51
51
-
color: var(--secondary-color);
52
52
-
flex-shrink: 0;
53
53
-
}
54
54
-
55
55
-
.detail-row .value {
56
56
-
font-weight: 500;
57
57
-
word-break: break-all;
58
58
-
text-align: right;
59
59
-
max-width: 65%;
60
60
-
}
61
61
-
62
62
-
.badge {
63
63
-
display: inline-block;
64
64
-
padding: 2px 8px;
65
65
-
border-radius: 4px;
66
66
-
font-size: 0.75rem;
67
67
-
font-weight: 500;
68
68
-
}
69
69
-
70
70
-
.badge-success {
71
71
-
background: rgba(22, 163, 74, 0.1);
72
72
-
color: var(--success-color);
73
73
-
}
74
74
-
75
75
-
.badge-danger {
76
76
-
background: rgba(220, 38, 38, 0.1);
77
77
-
color: var(--danger-color);
78
78
-
}
79
79
-
80
80
-
.badge-warning {
81
81
-
background: rgba(234, 179, 8, 0.1);
82
82
-
color: var(--warning-color);
83
83
-
}
84
84
-
85
85
-
.actions {
86
86
-
display: flex;
87
87
-
flex-wrap: wrap;
88
88
-
gap: 8px;
89
89
-
margin-top: 8px;
90
90
-
}
91
91
-
92
92
-
.actions form {
93
93
-
display: inline;
94
94
-
}
95
95
-
96
96
-
.btn {
97
97
-
padding: 8px 16px;
98
98
-
font-size: 0.8125rem;
99
99
-
border: 1px solid var(--border-color);
100
100
-
background: var(--bg-primary-color);
101
101
-
color: var(--primary-color);
102
102
-
}
103
103
-
104
104
-
.btn-primary {
105
105
-
border-color: var(--brand-color);
106
106
-
}
107
107
-
108
108
-
.btn-danger {
109
109
-
border-color: var(--danger-color);
110
110
-
}
111
111
-
112
112
-
.btn-warning {
113
113
-
border-color: var(--warning-color);
114
114
-
}
115
115
-
116
116
-
.password-box {
117
117
-
background: rgba(22, 163, 74, 0.08);
118
118
-
border: 1px solid rgba(22, 163, 74, 0.2);
119
119
-
border-radius: 10px;
120
120
-
padding: 16px 20px;
121
121
-
margin-bottom: 16px;
122
122
-
}
123
123
-
124
124
-
.password-box .pw-label {
125
125
-
font-size: 0.75rem;
126
126
-
font-weight: 600;
127
127
-
color: var(--success-color);
128
128
-
margin-bottom: 6px;
129
129
-
}
130
130
-
131
131
-
.password-box .pw-value {
132
132
-
font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
133
133
-
font-size: 1rem;
134
134
-
font-weight: 600;
135
135
-
user-select: all;
136
136
-
}
137
137
-
138
138
-
.back-link {
139
139
-
display: inline-block;
140
140
-
color: var(--brand-color);
141
141
-
text-decoration: none;
142
142
-
font-size: 0.8125rem;
143
143
-
margin-bottom: 16px;
144
144
-
}
145
145
-
146
146
-
.back-link:hover {
147
147
-
text-decoration: underline;
148
148
-
}
149
149
-
150
150
-
.collection-list {
151
151
-
max-height: 200px;
152
152
-
overflow-y: auto;
153
153
-
padding: 8px 0;
154
154
-
}
155
155
-
156
156
-
.collection-item {
157
157
-
font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
158
158
-
font-size: 0.75rem;
159
159
-
padding: 4px 0;
160
160
-
color: var(--secondary-color);
161
161
-
border-bottom: 1px solid var(--border-color);
162
162
-
}
163
163
-
164
164
-
.collection-item:last-child {
165
165
-
border-bottom: none;
166
166
-
}
167
167
-
168
168
-
.threat-sig {
169
169
-
font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
170
170
-
font-size: 0.75rem;
171
171
-
padding: 4px 0;
172
172
-
color: var(--secondary-color);
173
173
-
}
174
174
-
</style>
8
8
+
<link rel="stylesheet" href="/admin/static/css/admin.css">
175
9
</head>
176
10
<body>
177
11
<div class="layout">
···
288
122
{{#if repo_rev}}
289
123
<div class="detail-row">
290
124
<span class="label">Revision</span>
291
291
-
<span class="value"
292
292
-
style="font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.75rem;">{{repo_rev}}</span>
125
125
+
<span class="value mono-text">{{repo_rev}}</span>
293
126
</div>
294
127
{{/if}}
295
128
</div>
···
5
5
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, viewport-fit=cover"/>
6
6
<meta name="referrer" content="origin-when-cross-origin"/>
7
7
<title>Accounts - {{pds_hostname}}</title>
8
8
-
<style>
9
9
-
{{> admin/partials/base_css.hbs}}
10
10
-
11
11
-
.search-form {
12
12
-
display: flex;
13
13
-
gap: 8px;
14
14
-
margin-bottom: 24px;
15
15
-
}
16
16
-
17
17
-
.search-form input {
18
18
-
flex: 1;
19
19
-
padding: 10px 12px;
20
20
-
font-size: 0.875rem;
21
21
-
border: 1px solid var(--border-color);
22
22
-
border-radius: 8px;
23
23
-
background: var(--bg-primary-color);
24
24
-
color: var(--primary-color);
25
25
-
outline: none;
26
26
-
}
27
27
-
28
28
-
.search-form input:focus {
29
29
-
border-color: var(--brand-color);
30
30
-
}
31
31
-
32
32
-
.table-container {
33
33
-
background: var(--bg-primary-color);
34
34
-
border: 1px solid var(--border-color);
35
35
-
border-radius: 10px;
36
36
-
overflow: hidden;
37
37
-
}
38
38
-
39
39
-
table {
40
40
-
width: 100%;
41
41
-
border-collapse: collapse;
42
42
-
}
43
43
-
44
44
-
thead th {
45
45
-
text-align: left;
46
46
-
padding: 12px 16px;
47
47
-
font-size: 0.75rem;
48
48
-
font-weight: 600;
49
49
-
color: var(--secondary-color);
50
50
-
text-transform: uppercase;
51
51
-
letter-spacing: 0.5px;
52
52
-
border-bottom: 1px solid var(--border-color);
53
53
-
}
54
54
-
55
55
-
tbody tr {
56
56
-
border-bottom: 1px solid var(--border-color);
57
57
-
}
58
58
-
59
59
-
tbody tr:last-child {
60
60
-
border-bottom: none;
61
61
-
}
62
62
-
63
63
-
tbody tr:nth-child(even) {
64
64
-
background: var(--table-stripe);
65
65
-
}
66
66
-
67
67
-
tbody td {
68
68
-
padding: 10px 16px;
69
69
-
font-size: 0.8125rem;
70
70
-
}
71
71
-
72
72
-
tbody td a {
73
73
-
color: var(--brand-color);
74
74
-
text-decoration: none;
75
75
-
}
76
76
-
77
77
-
tbody td a:hover {
78
78
-
text-decoration: underline;
79
79
-
}
80
80
-
81
81
-
.empty-state {
82
82
-
text-align: center;
83
83
-
padding: 40px 20px;
84
84
-
color: var(--secondary-color);
85
85
-
font-size: 0.875rem;
86
86
-
}
87
87
-
88
88
-
.did-cell {
89
89
-
font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
90
90
-
font-size: 0.75rem;
91
91
-
color: var(--secondary-color);
92
92
-
}
93
93
-
</style>
8
8
+
<link rel="stylesheet" href="/admin/static/css/admin.css">
94
9
</head>
95
10
<body>
96
11
<div class="layout">
···
5
5
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, viewport-fit=cover"/>
6
6
<meta name="referrer" content="origin-when-cross-origin"/>
7
7
<title>Create Account - {{pds_hostname}}</title>
8
8
-
<style>
9
9
-
{{> admin/partials/base_css.hbs}}
10
10
-
11
11
-
.form-card {
12
12
-
background: var(--bg-primary-color);
13
13
-
border: 1px solid var(--border-color);
14
14
-
border-radius: 10px;
15
15
-
padding: 24px;
16
16
-
max-width: 480px;
17
17
-
}
18
18
-
19
19
-
.form-group {
20
20
-
margin-bottom: 16px;
21
21
-
}
22
22
-
23
23
-
.form-group label {
24
24
-
display: block;
25
25
-
font-size: 0.8125rem;
26
26
-
font-weight: 500;
27
27
-
margin-bottom: 6px;
28
28
-
color: var(--primary-color);
29
29
-
}
30
30
-
31
31
-
.form-group input {
32
32
-
width: 100%;
33
33
-
padding: 10px 12px;
34
34
-
font-size: 0.875rem;
35
35
-
border: 1px solid var(--border-color);
36
36
-
border-radius: 8px;
37
37
-
background: var(--bg-primary-color);
38
38
-
color: var(--primary-color);
39
39
-
outline: none;
40
40
-
transition: border-color 0.15s;
41
41
-
}
42
42
-
43
43
-
.form-group input:focus {
44
44
-
border-color: var(--brand-color);
45
45
-
}
46
46
-
47
47
-
.form-group .hint {
48
48
-
font-size: 0.75rem;
49
49
-
color: var(--secondary-color);
50
50
-
margin-top: 4px;
51
51
-
}
52
52
-
53
53
-
.success-card {
54
54
-
background: var(--bg-primary-color);
55
55
-
border: 1px solid var(--border-color);
56
56
-
border-radius: 10px;
57
57
-
padding: 24px;
58
58
-
max-width: 480px;
59
59
-
}
60
60
-
61
61
-
.success-card h3 {
62
62
-
font-size: 1rem;
63
63
-
font-weight: 600;
64
64
-
color: var(--success-color);
65
65
-
margin-bottom: 16px;
66
66
-
}
67
67
-
68
68
-
.detail-row {
69
69
-
display: flex;
70
70
-
justify-content: space-between;
71
71
-
align-items: center;
72
72
-
padding: 8px 0;
73
73
-
font-size: 0.8125rem;
74
74
-
border-bottom: 1px solid var(--border-color);
75
75
-
}
76
76
-
77
77
-
.detail-row:last-child {
78
78
-
border-bottom: none;
79
79
-
}
80
80
-
81
81
-
.detail-row .label {
82
82
-
color: var(--secondary-color);
83
83
-
}
84
84
-
85
85
-
.detail-row .value {
86
86
-
font-weight: 500;
87
87
-
word-break: break-all;
88
88
-
text-align: right;
89
89
-
max-width: 65%;
90
90
-
display: flex;
91
91
-
align-items: center;
92
92
-
gap: 6px;
93
93
-
}
94
94
-
95
95
-
.password-highlight {
96
96
-
font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
97
97
-
background: rgba(22, 163, 74, 0.08);
98
98
-
padding: 2px 6px;
99
99
-
border-radius: 4px;
100
100
-
user-select: all;
101
101
-
}
102
102
-
103
103
-
.copy-btn {
104
104
-
background: none;
105
105
-
border: 1px solid var(--border-color);
106
106
-
border-radius: 4px;
107
107
-
padding: 2px 6px;
108
108
-
font-size: 0.6875rem;
109
109
-
cursor: pointer;
110
110
-
color: var(--secondary-color);
111
111
-
transition: color 0.15s, border-color 0.15s;
112
112
-
white-space: nowrap;
113
113
-
}
114
114
-
115
115
-
.copy-btn:hover {
116
116
-
color: var(--primary-color);
117
117
-
border-color: var(--primary-color);
118
118
-
}
119
119
-
</style>
8
8
+
<link rel="stylesheet" href="/admin/static/css/admin.css">
120
9
</head>
121
10
<body>
122
11
<div class="layout">
···
5
5
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, viewport-fit=cover"/>
6
6
<meta name="referrer" content="origin-when-cross-origin"/>
7
7
<title>Dashboard - {{pds_hostname}}</title>
8
8
-
<style>
9
9
-
{{> admin/partials/base_css.hbs}}
10
10
-
11
11
-
.cards {
12
12
-
display: grid;
13
13
-
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
14
14
-
gap: 16px;
15
15
-
margin-bottom: 32px;
16
16
-
}
17
17
-
18
18
-
.card {
19
19
-
background: var(--bg-primary-color);
20
20
-
border: 1px solid var(--border-color);
21
21
-
border-radius: 10px;
22
22
-
padding: 20px;
23
23
-
}
24
24
-
25
25
-
.card-label {
26
26
-
font-size: 0.75rem;
27
27
-
font-weight: 500;
28
28
-
color: var(--secondary-color);
29
29
-
text-transform: uppercase;
30
30
-
letter-spacing: 0.5px;
31
31
-
margin-bottom: 6px;
32
32
-
}
33
33
-
34
34
-
.card-value {
35
35
-
font-size: 1.25rem;
36
36
-
font-weight: 600;
37
37
-
}
38
38
-
39
39
-
.card-value.success {
40
40
-
color: var(--success-color);
41
41
-
}
42
42
-
43
43
-
.card-value.danger {
44
44
-
color: var(--danger-color);
45
45
-
}
46
46
-
47
47
-
.detail-section {
48
48
-
background: var(--bg-primary-color);
49
49
-
border: 1px solid var(--border-color);
50
50
-
border-radius: 10px;
51
51
-
padding: 20px;
52
52
-
margin-bottom: 16px;
53
53
-
}
54
54
-
55
55
-
.detail-section h3 {
56
56
-
font-size: 0.875rem;
57
57
-
font-weight: 600;
58
58
-
margin-bottom: 12px;
59
59
-
}
60
60
-
61
61
-
.detail-row {
62
62
-
display: flex;
63
63
-
justify-content: space-between;
64
64
-
padding: 6px 0;
65
65
-
font-size: 0.8125rem;
66
66
-
border-bottom: 1px solid var(--border-color);
67
67
-
}
68
68
-
69
69
-
.detail-row:last-child {
70
70
-
border-bottom: none;
71
71
-
}
72
72
-
73
73
-
.detail-row .label {
74
74
-
color: var(--secondary-color);
75
75
-
}
76
76
-
77
77
-
.detail-row .value {
78
78
-
font-weight: 500;
79
79
-
word-break: break-all;
80
80
-
text-align: right;
81
81
-
max-width: 60%;
82
82
-
}
83
83
-
84
84
-
.detail-row .value a {
85
85
-
color: var(--brand-color);
86
86
-
text-decoration: none;
87
87
-
}
88
88
-
89
89
-
.detail-row .value a:hover {
90
90
-
text-decoration: underline;
91
91
-
}
92
92
-
</style>
8
8
+
<link rel="stylesheet" href="/admin/static/css/admin.css">
93
9
</head>
94
10
<body>
95
11
<div class="layout">
···
5
5
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, viewport-fit=cover"/>
6
6
<meta name="referrer" content="origin-when-cross-origin"/>
7
7
<title>Error - {{pds_hostname}}</title>
8
8
-
<style>
9
9
-
{{> admin/partials/base_css.hbs}}
10
10
-
11
11
-
body {
12
12
-
min-height: 100vh;
13
13
-
}
14
14
-
15
15
-
/* Standalone centered layout (when not logged in) */
16
16
-
.centered {
17
17
-
display: flex;
18
18
-
align-items: center;
19
19
-
justify-content: center;
20
20
-
min-height: 100vh;
21
21
-
}
22
22
-
23
23
-
.error-card {
24
24
-
background: var(--bg-primary-color);
25
25
-
border: 1px solid var(--border-color);
26
26
-
border-radius: 12px;
27
27
-
padding: 40px;
28
28
-
text-align: center;
29
29
-
max-width: 480px;
30
30
-
width: 100%;
31
31
-
margin: 20px;
32
32
-
}
33
33
-
34
34
-
.error-icon {
35
35
-
font-size: 2.5rem;
36
36
-
margin-bottom: 16px;
37
37
-
color: var(--danger-color);
38
38
-
}
39
39
-
40
40
-
.error-title {
41
41
-
font-size: 1.25rem;
42
42
-
font-weight: 700;
43
43
-
margin-bottom: 8px;
44
44
-
}
45
45
-
46
46
-
.error-message {
47
47
-
font-size: 0.875rem;
48
48
-
color: var(--secondary-color);
49
49
-
margin-bottom: 24px;
50
50
-
line-height: 1.5;
51
51
-
}
52
52
-
53
53
-
.error-link {
54
54
-
display: inline-flex;
55
55
-
align-items: center;
56
56
-
justify-content: center;
57
57
-
padding: 10px 20px;
58
58
-
font-size: 0.875rem;
59
59
-
font-weight: 500;
60
60
-
border: none;
61
61
-
border-radius: 8px;
62
62
-
cursor: pointer;
63
63
-
text-decoration: none;
64
64
-
background: var(--brand-color);
65
65
-
color: #fff;
66
66
-
transition: opacity 0.15s;
67
67
-
}
68
68
-
69
69
-
.error-link:hover {
70
70
-
opacity: 0.85;
71
71
-
}
72
72
-
</style>
8
8
+
<link rel="stylesheet" href="/admin/static/css/admin.css">
73
9
</head>
74
10
<body>
75
11
{{#if handle}}
···
78
14
{{> admin/partials/sidebar.hbs}}
79
15
80
16
<main class="main">
81
81
-
<div class="error-card" style="text-align:center; margin: 60px auto;">
17
17
+
<div class="error-card error-card--inset">
82
18
<div class="error-icon">!</div>
83
19
<div class="error-title">{{error_title}}</div>
84
20
<div class="error-message">{{error_message}}</div>
···
5
5
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, viewport-fit=cover"/>
6
6
<meta name="referrer" content="origin-when-cross-origin"/>
7
7
<title>Invite Codes - {{pds_hostname}}</title>
8
8
-
<style>
9
9
-
{{> admin/partials/base_css.hbs}}
10
10
-
11
11
-
.create-form {
12
12
-
display: flex;
13
13
-
gap: 8px;
14
14
-
align-items: flex-end;
15
15
-
margin-bottom: 24px;
16
16
-
}
17
17
-
18
18
-
.create-form .form-group {
19
19
-
display: flex;
20
20
-
flex-direction: column;
21
21
-
gap: 4px;
22
22
-
}
23
23
-
24
24
-
.create-form label {
25
25
-
font-size: 0.75rem;
26
26
-
font-weight: 500;
27
27
-
color: var(--secondary-color);
28
28
-
}
29
29
-
30
30
-
.create-form input[type="number"] {
31
31
-
padding: 10px 12px;
32
32
-
font-size: 0.875rem;
33
33
-
border: 1px solid var(--border-color);
34
34
-
border-radius: 8px;
35
35
-
background: var(--bg-primary-color);
36
36
-
color: var(--primary-color);
37
37
-
outline: none;
38
38
-
width: 120px;
39
39
-
}
40
40
-
41
41
-
.create-form input[type="number"]:focus {
42
42
-
border-color: var(--brand-color);
43
43
-
}
44
44
-
45
45
-
.btn-small {
46
46
-
padding: 6px 12px;
47
47
-
font-size: 0.75rem;
48
48
-
}
49
49
-
50
50
-
.btn-outline-danger {
51
51
-
background: transparent;
52
52
-
color: var(--danger-color);
53
53
-
border: 1px solid var(--danger-color);
54
54
-
}
55
55
-
56
56
-
.code-box {
57
57
-
background: rgba(22, 163, 74, 0.08);
58
58
-
border: 1px solid rgba(22, 163, 74, 0.2);
59
59
-
border-radius: 10px;
60
60
-
padding: 16px 20px;
61
61
-
margin-bottom: 24px;
62
62
-
}
63
63
-
64
64
-
.code-box .code-label {
65
65
-
font-size: 0.75rem;
66
66
-
font-weight: 600;
67
67
-
color: var(--success-color);
68
68
-
margin-bottom: 6px;
69
69
-
}
70
70
-
71
71
-
.code-box .code-value {
72
72
-
font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
73
73
-
font-size: 1rem;
74
74
-
font-weight: 600;
75
75
-
user-select: all;
76
76
-
}
77
77
-
78
78
-
.table-container {
79
79
-
background: var(--bg-primary-color);
80
80
-
border: 1px solid var(--border-color);
81
81
-
border-radius: 10px;
82
82
-
overflow: hidden;
83
83
-
}
84
84
-
85
85
-
table {
86
86
-
width: 100%;
87
87
-
border-collapse: collapse;
88
88
-
}
89
89
-
90
90
-
thead th {
91
91
-
text-align: left;
92
92
-
padding: 12px 16px;
93
93
-
font-size: 0.75rem;
94
94
-
font-weight: 600;
95
95
-
color: var(--secondary-color);
96
96
-
text-transform: uppercase;
97
97
-
letter-spacing: 0.5px;
98
98
-
border-bottom: 1px solid var(--border-color);
99
99
-
}
100
100
-
101
101
-
tbody tr {
102
102
-
border-bottom: 1px solid var(--border-color);
103
103
-
}
104
104
-
105
105
-
tbody tr:last-child {
106
106
-
border-bottom: none;
107
107
-
}
108
108
-
109
109
-
tbody tr:nth-child(even) {
110
110
-
background: var(--table-stripe);
111
111
-
}
112
112
-
113
113
-
tbody td {
114
114
-
padding: 10px 16px;
115
115
-
font-size: 0.8125rem;
116
116
-
}
117
117
-
118
118
-
.code-cell {
119
119
-
font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
120
120
-
font-size: 0.75rem;
121
121
-
}
122
122
-
123
123
-
.badge {
124
124
-
display: inline-block;
125
125
-
padding: 2px 8px;
126
126
-
border-radius: 4px;
127
127
-
font-size: 0.75rem;
128
128
-
font-weight: 500;
129
129
-
}
130
130
-
131
131
-
.badge-success {
132
132
-
background: rgba(22, 163, 74, 0.1);
133
133
-
color: var(--success-color);
134
134
-
}
135
135
-
136
136
-
.badge-danger {
137
137
-
background: rgba(220, 38, 38, 0.1);
138
138
-
color: var(--danger-color);
139
139
-
}
140
140
-
141
141
-
.empty-state {
142
142
-
text-align: center;
143
143
-
padding: 40px 20px;
144
144
-
color: var(--secondary-color);
145
145
-
font-size: 0.875rem;
146
146
-
}
147
147
-
148
148
-
.load-more {
149
149
-
text-align: center;
150
150
-
padding: 16px;
151
151
-
}
152
152
-
153
153
-
.load-more a {
154
154
-
color: var(--brand-color);
155
155
-
text-decoration: none;
156
156
-
font-size: 0.875rem;
157
157
-
font-weight: 500;
158
158
-
}
159
159
-
160
160
-
.load-more a:hover {
161
161
-
text-decoration: underline;
162
162
-
}
163
163
-
</style>
8
8
+
<link rel="stylesheet" href="/admin/static/css/admin.css">
164
9
</head>
165
10
<body>
166
11
<div class="layout">
···
221
66
<td>
222
67
{{#unless this.disabled}}
223
68
<form method="POST" action="/admin/invite-codes/disable"
224
224
-
style="display:inline;">
69
69
+
class="inline-form">
225
70
<input type="hidden" name="codes" value="{{this.code}}"/>
226
71
<button type="submit" class="btn btn-small btn-outline-danger">Disable
227
72
</button>
···
5
5
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, viewport-fit=cover"/>
6
6
<meta name="referrer" content="origin-when-cross-origin"/>
7
7
<title>Admin Login - {{pds_hostname}}</title>
8
8
+
<link rel="stylesheet" href="/admin/static/css/admin.css">
8
9
<style>
9
9
-
{{> admin/partials/base_css.hbs}}
10
10
-
11
10
body {
12
11
min-height: 100vh;
13
12
display: flex;
14
13
align-items: center;
15
14
justify-content: center;
16
16
-
}
17
17
-
18
18
-
.login-card {
19
19
-
background: var(--bg-primary-color);
20
20
-
border: 1px solid var(--border-color);
21
21
-
border-radius: 12px;
22
22
-
padding: 40px;
23
23
-
width: 100%;
24
24
-
max-width: 400px;
25
25
-
margin: 20px;
26
26
-
}
27
27
-
28
28
-
.login-title {
29
29
-
font-size: 1.5rem;
30
30
-
font-weight: 700;
31
31
-
text-align: center;
32
32
-
margin-bottom: 4px;
33
33
-
}
34
34
-
35
35
-
.login-subtitle {
36
36
-
font-size: 0.875rem;
37
37
-
color: var(--secondary-color);
38
38
-
text-align: center;
39
39
-
margin-bottom: 32px;
40
40
-
}
41
41
-
42
42
-
.form-group {
43
43
-
margin-bottom: 20px;
44
44
-
}
45
45
-
46
46
-
.form-group label {
47
47
-
display: block;
48
48
-
font-size: 0.875rem;
49
49
-
font-weight: 500;
50
50
-
margin-bottom: 6px;
51
51
-
color: var(--primary-color);
52
52
-
}
53
53
-
54
54
-
.form-group input {
55
55
-
width: 100%;
56
56
-
padding: 10px 12px;
57
57
-
font-size: 0.875rem;
58
58
-
border: 1px solid var(--border-color);
59
59
-
border-radius: 8px;
60
60
-
background: var(--bg-primary-color);
61
61
-
color: var(--primary-color);
62
62
-
outline: none;
63
63
-
transition: border-color 0.15s;
64
64
-
}
65
65
-
66
66
-
.form-group input:focus {
67
67
-
border-color: var(--brand-color);
68
68
-
}
69
69
-
70
70
-
.btn-primary {
71
71
-
width: 100%;
72
72
-
}
73
73
-
74
74
-
.error-msg {
75
75
-
background: rgba(220, 38, 38, 0.1);
76
76
-
color: var(--danger-color);
77
77
-
border: 1px solid rgba(220, 38, 38, 0.2);
78
78
-
border-radius: 8px;
79
79
-
padding: 10px 14px;
80
80
-
font-size: 0.875rem;
81
81
-
margin-bottom: 20px;
82
15
}
83
16
</style>
84
17
</head>
···
1
1
-
:root,
2
2
-
:root.light-mode {
3
3
-
--brand-color: rgb(16, 131, 254);
4
4
-
--primary-color: rgb(7, 10, 13);
5
5
-
--secondary-color: rgb(66, 86, 108);
6
6
-
--bg-primary-color: rgb(255, 255, 255);
7
7
-
--bg-secondary-color: rgb(240, 242, 245);
8
8
-
--border-color: rgb(220, 225, 230);
9
9
-
--danger-color: rgb(220, 38, 38);
10
10
-
--success-color: rgb(22, 163, 74);
11
11
-
--warning-color: rgb(234, 179, 8);
12
12
-
--table-stripe: rgba(0, 0, 0, 0.02);
13
13
-
}
14
14
-
15
15
-
@media (prefers-color-scheme: dark) {
16
16
-
:root {
17
17
-
--brand-color: rgb(16, 131, 254);
18
18
-
--primary-color: rgb(255, 255, 255);
19
19
-
--secondary-color: rgb(133, 152, 173);
20
20
-
--bg-primary-color: rgb(7, 10, 13);
21
21
-
--bg-secondary-color: rgb(13, 18, 23);
22
22
-
--border-color: rgb(40, 45, 55);
23
23
-
--table-stripe: rgba(255, 255, 255, 0.02);
24
24
-
}
25
25
-
}
26
26
-
27
27
-
:root.dark-mode {
28
28
-
--brand-color: rgb(16, 131, 254);
29
29
-
--primary-color: rgb(255, 255, 255);
30
30
-
--secondary-color: rgb(133, 152, 173);
31
31
-
--bg-primary-color: rgb(7, 10, 13);
32
32
-
--bg-secondary-color: rgb(13, 18, 23);
33
33
-
--border-color: rgb(40, 45, 55);
34
34
-
--table-stripe: rgba(255, 255, 255, 0.02);
35
35
-
}
36
36
-
37
37
-
* {
38
38
-
margin: 0;
39
39
-
padding: 0;
40
40
-
box-sizing: border-box;
41
41
-
}
42
42
-
43
43
-
body {
44
44
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
45
45
-
background: var(--bg-secondary-color);
46
46
-
color: var(--primary-color);
47
47
-
text-rendering: optimizeLegibility;
48
48
-
-webkit-font-smoothing: antialiased;
49
49
-
}
50
50
-
51
51
-
.layout {
52
52
-
display: flex;
53
53
-
min-height: 100vh;
54
54
-
}
55
55
-
56
56
-
.sidebar {
57
57
-
width: 220px;
58
58
-
background: var(--bg-primary-color);
59
59
-
border-right: 1px solid var(--border-color);
60
60
-
padding: 20px 0;
61
61
-
position: fixed;
62
62
-
top: 0;
63
63
-
left: 0;
64
64
-
bottom: 0;
65
65
-
overflow-y: auto;
66
66
-
display: flex;
67
67
-
flex-direction: column;
68
68
-
}
69
69
-
70
70
-
.sidebar-title {
71
71
-
font-size: 0.8125rem;
72
72
-
font-weight: 700;
73
73
-
padding: 0 20px;
74
74
-
margin-bottom: 4px;
75
75
-
white-space: nowrap;
76
76
-
overflow: hidden;
77
77
-
text-overflow: ellipsis;
78
78
-
}
79
79
-
80
80
-
.sidebar-subtitle {
81
81
-
font-size: 0.6875rem;
82
82
-
color: var(--secondary-color);
83
83
-
padding: 0 20px;
84
84
-
margin-bottom: 20px;
85
85
-
}
86
86
-
87
87
-
.sidebar nav {
88
88
-
flex: 1;
89
89
-
}
90
90
-
91
91
-
.sidebar nav a {
92
92
-
display: block;
93
93
-
padding: 8px 20px;
94
94
-
font-size: 0.8125rem;
95
95
-
color: var(--secondary-color);
96
96
-
text-decoration: none;
97
97
-
transition: background 0.1s, color 0.1s;
98
98
-
}
99
99
-
100
100
-
.sidebar nav a:hover {
101
101
-
background: var(--bg-secondary-color);
102
102
-
color: var(--primary-color);
103
103
-
}
104
104
-
105
105
-
.sidebar nav a.active {
106
106
-
color: var(--brand-color);
107
107
-
font-weight: 500;
108
108
-
}
109
109
-
110
110
-
.sidebar-footer {
111
111
-
padding: 16px 20px 0;
112
112
-
border-top: 1px solid var(--border-color);
113
113
-
margin-top: 16px;
114
114
-
}
115
115
-
116
116
-
.sidebar-footer .session-info {
117
117
-
font-size: 0.75rem;
118
118
-
color: var(--secondary-color);
119
119
-
margin-bottom: 8px;
120
120
-
}
121
121
-
122
122
-
.sidebar-footer form {
123
123
-
display: inline;
124
124
-
}
125
125
-
126
126
-
.sidebar-footer button {
127
127
-
background: none;
128
128
-
border: none;
129
129
-
font-size: 0.75rem;
130
130
-
color: var(--secondary-color);
131
131
-
cursor: pointer;
132
132
-
padding: 0;
133
133
-
text-decoration: underline;
134
134
-
}
135
135
-
136
136
-
.sidebar-footer button:hover {
137
137
-
color: var(--primary-color);
138
138
-
}
139
139
-
140
140
-
.main {
141
141
-
margin-left: 220px;
142
142
-
flex: 1;
143
143
-
padding: 32px;
144
144
-
max-width: 960px;
145
145
-
}
146
146
-
147
147
-
.page-title {
148
148
-
font-size: 1.5rem;
149
149
-
font-weight: 700;
150
150
-
margin-bottom: 24px;
151
151
-
}
152
152
-
153
153
-
.flash-success {
154
154
-
background: rgba(22, 163, 74, 0.1);
155
155
-
color: var(--success-color);
156
156
-
border: 1px solid rgba(22, 163, 74, 0.2);
157
157
-
border-radius: 8px;
158
158
-
padding: 10px 14px;
159
159
-
font-size: 0.875rem;
160
160
-
margin-bottom: 20px;
161
161
-
}
162
162
-
163
163
-
.flash-error {
164
164
-
background: rgba(220, 38, 38, 0.1);
165
165
-
color: var(--danger-color);
166
166
-
border: 1px solid rgba(220, 38, 38, 0.2);
167
167
-
border-radius: 8px;
168
168
-
padding: 10px 14px;
169
169
-
font-size: 0.875rem;
170
170
-
margin-bottom: 20px;
171
171
-
}
172
172
-
173
173
-
.btn {
174
174
-
display: inline-flex;
175
175
-
align-items: center;
176
176
-
justify-content: center;
177
177
-
padding: 10px 20px;
178
178
-
font-size: 0.875rem;
179
179
-
font-weight: 500;
180
180
-
border: none;
181
181
-
border-radius: 8px;
182
182
-
cursor: pointer;
183
183
-
transition: opacity 0.15s;
184
184
-
text-decoration: none;
185
185
-
}
186
186
-
187
187
-
.btn:hover {
188
188
-
opacity: 0.85;
189
189
-
}
190
190
-
191
191
-
.btn-primary {
192
192
-
background: var(--brand-color);
193
193
-
color: #fff;
194
194
-
}
195
195
-
196
196
-
.btn-danger {
197
197
-
background: var(--danger-color);
198
198
-
color: #fff;
199
199
-
border-color: var(--danger-color);
200
200
-
}
201
201
-
202
202
-
.btn-warning {
203
203
-
background: var(--warning-color);
204
204
-
color: #000;
205
205
-
border-color: var(--warning-color);
206
206
-
}
207
207
-
208
208
-
@media (max-width: 768px) {
209
209
-
.sidebar {
210
210
-
display: none;
211
211
-
}
212
212
-
213
213
-
.main {
214
214
-
margin-left: 0;
215
215
-
}
216
216
-
}
···
5
5
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, viewport-fit=cover"/>
6
6
<meta name="referrer" content="origin-when-cross-origin"/>
7
7
<title>Request Crawl - {{pds_hostname}}</title>
8
8
-
<style>
9
9
-
{{> admin/partials/base_css.hbs}}
10
10
-
11
11
-
.page-title {
12
12
-
margin-bottom: 8px;
13
13
-
}
14
14
-
15
15
-
.page-description {
16
16
-
font-size: 0.875rem;
17
17
-
color: var(--secondary-color);
18
18
-
margin-bottom: 24px;
19
19
-
}
20
20
-
21
21
-
.form-card {
22
22
-
background: var(--bg-primary-color);
23
23
-
border: 1px solid var(--border-color);
24
24
-
border-radius: 10px;
25
25
-
padding: 24px;
26
26
-
max-width: 480px;
27
27
-
}
28
28
-
29
29
-
.form-group {
30
30
-
margin-bottom: 16px;
31
31
-
}
32
32
-
33
33
-
.form-group label {
34
34
-
display: block;
35
35
-
font-size: 0.8125rem;
36
36
-
font-weight: 500;
37
37
-
margin-bottom: 6px;
38
38
-
color: var(--primary-color);
39
39
-
}
40
40
-
41
41
-
.form-group input {
42
42
-
width: 100%;
43
43
-
padding: 10px 12px;
44
44
-
font-size: 0.875rem;
45
45
-
border: 1px solid var(--border-color);
46
46
-
border-radius: 8px;
47
47
-
background: var(--bg-primary-color);
48
48
-
color: var(--primary-color);
49
49
-
outline: none;
50
50
-
transition: border-color 0.15s;
51
51
-
}
52
52
-
53
53
-
.form-group input:focus {
54
54
-
border-color: var(--brand-color);
55
55
-
}
56
56
-
57
57
-
.form-group .hint {
58
58
-
font-size: 0.75rem;
59
59
-
color: var(--secondary-color);
60
60
-
margin-top: 4px;
61
61
-
}
62
62
-
</style>
8
8
+
<link rel="stylesheet" href="/admin/static/css/admin.css">
63
9
</head>
64
10
<body>
65
11
<div class="layout">
···
4
4
pub mod rbac;
5
5
pub mod routes;
6
6
pub mod session;
7
7
+
pub mod static_files;
7
8
pub mod store;
8
9
9
10
use axum::{Router, middleware as ax_middleware, routing::get, routing::post};
···
19
20
.route("/", get(routes::dashboard))
20
21
.route("/login", get(oauth::get_login).post(oauth::post_login))
21
22
.route("/oauth/callback", get(oauth::oauth_callback))
22
22
-
.route("/client-metadata.json", get(oauth::client_metadata_json));
23
23
+
.route("/client-metadata.json", get(oauth::client_metadata_json))
24
24
+
.route("/static/{*path}", get(static_files::serve_static));
23
25
24
26
// Routes that DO require authentication (via admin_auth middleware)
25
27
let protected_routes = Router::new()
···
1
1
+
use axum::{
2
2
+
extract::Path,
3
3
+
http::{StatusCode, header},
4
4
+
response::{IntoResponse, Response},
5
5
+
};
6
6
+
7
7
+
use crate::StaticFiles;
8
8
+
9
9
+
/// Serve embedded static files at /admin/static/*path
10
10
+
pub async fn serve_static(Path(path): Path<String>) -> Response {
11
11
+
let file = match StaticFiles::get(&path) {
12
12
+
Some(file) => file,
13
13
+
None => return StatusCode::NOT_FOUND.into_response(),
14
14
+
};
15
15
+
16
16
+
let mime_type = mime_from_path(&path);
17
17
+
18
18
+
(
19
19
+
[
20
20
+
(header::CONTENT_TYPE, mime_type),
21
21
+
(header::CACHE_CONTROL, cache_control_value()),
22
22
+
],
23
23
+
file.data.to_vec(),
24
24
+
)
25
25
+
.into_response()
26
26
+
}
27
27
+
28
28
+
fn mime_from_path(path: &str) -> &'static str {
29
29
+
match path.rsplit('.').next() {
30
30
+
Some("css") => "text/css; charset=utf-8",
31
31
+
Some("js") => "application/javascript; charset=utf-8",
32
32
+
Some("json") => "application/json",
33
33
+
Some("svg") => "image/svg+xml",
34
34
+
Some("png") => "image/png",
35
35
+
Some("jpg") | Some("jpeg") => "image/jpeg",
36
36
+
_ => "application/octet-stream",
37
37
+
}
38
38
+
}
39
39
+
40
40
+
fn cache_control_value() -> &'static str {
41
41
+
if cfg!(debug_assertions) {
42
42
+
"no-cache"
43
43
+
} else {
44
44
+
"public, max-age=3600, stale-while-revalidate=86400"
45
45
+
}
46
46
+
}
···
62
62
#[include = "*.hbs"]
63
63
struct HtmlTemplates;
64
64
65
65
+
#[derive(RustEmbed)]
66
66
+
#[folder = "static"]
67
67
+
pub struct StaticFiles;
68
68
+
65
69
/// Mostly the env variables that are used in the app
66
70
#[derive(Clone, Debug)]
67
71
pub struct AppConfig {
···
1
1
+
:root,
2
2
+
:root.light-mode {
3
3
+
--brand-color: rgb(16, 131, 254);
4
4
+
--primary-color: rgb(7, 10, 13);
5
5
+
--secondary-color: rgb(66, 86, 108);
6
6
+
--bg-primary-color: rgb(255, 255, 255);
7
7
+
--bg-secondary-color: rgb(240, 242, 245);
8
8
+
--border-color: rgb(220, 225, 230);
9
9
+
--danger-color: rgb(220, 38, 38);
10
10
+
--success-color: rgb(22, 163, 74);
11
11
+
--warning-color: rgb(234, 179, 8);
12
12
+
--table-stripe: rgba(0, 0, 0, 0.02);
13
13
+
}
14
14
+
15
15
+
@media (prefers-color-scheme: dark) {
16
16
+
:root {
17
17
+
--brand-color: rgb(16, 131, 254);
18
18
+
--primary-color: rgb(255, 255, 255);
19
19
+
--secondary-color: rgb(133, 152, 173);
20
20
+
--bg-primary-color: rgb(7, 10, 13);
21
21
+
--bg-secondary-color: rgb(13, 18, 23);
22
22
+
--border-color: rgb(40, 45, 55);
23
23
+
--table-stripe: rgba(255, 255, 255, 0.02);
24
24
+
}
25
25
+
}
26
26
+
27
27
+
:root.dark-mode {
28
28
+
--brand-color: rgb(16, 131, 254);
29
29
+
--primary-color: rgb(255, 255, 255);
30
30
+
--secondary-color: rgb(133, 152, 173);
31
31
+
--bg-primary-color: rgb(7, 10, 13);
32
32
+
--bg-secondary-color: rgb(13, 18, 23);
33
33
+
--border-color: rgb(40, 45, 55);
34
34
+
--table-stripe: rgba(255, 255, 255, 0.02);
35
35
+
}
36
36
+
37
37
+
/* --- Reset & Base ------------------------------------------- */
38
38
+
39
39
+
* {
40
40
+
margin: 0;
41
41
+
padding: 0;
42
42
+
box-sizing: border-box;
43
43
+
}
44
44
+
45
45
+
body {
46
46
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
47
47
+
background: var(--bg-secondary-color);
48
48
+
color: var(--primary-color);
49
49
+
text-rendering: optimizeLegibility;
50
50
+
-webkit-font-smoothing: antialiased;
51
51
+
}
52
52
+
53
53
+
/* --- Layout: Sidebar + Main --------------------------------- */
54
54
+
55
55
+
.layout {
56
56
+
display: flex;
57
57
+
min-height: 100vh;
58
58
+
}
59
59
+
60
60
+
.sidebar {
61
61
+
width: 220px;
62
62
+
background: var(--bg-primary-color);
63
63
+
border-right: 1px solid var(--border-color);
64
64
+
padding: 20px 0;
65
65
+
position: fixed;
66
66
+
top: 0;
67
67
+
left: 0;
68
68
+
bottom: 0;
69
69
+
overflow-y: auto;
70
70
+
display: flex;
71
71
+
flex-direction: column;
72
72
+
}
73
73
+
74
74
+
.sidebar-title {
75
75
+
font-size: 0.8125rem;
76
76
+
font-weight: 700;
77
77
+
padding: 0 20px;
78
78
+
margin-bottom: 4px;
79
79
+
white-space: nowrap;
80
80
+
overflow: hidden;
81
81
+
text-overflow: ellipsis;
82
82
+
}
83
83
+
84
84
+
.sidebar-subtitle {
85
85
+
font-size: 0.6875rem;
86
86
+
color: var(--secondary-color);
87
87
+
padding: 0 20px;
88
88
+
margin-bottom: 20px;
89
89
+
}
90
90
+
91
91
+
.sidebar nav {
92
92
+
flex: 1;
93
93
+
}
94
94
+
95
95
+
.sidebar nav a {
96
96
+
display: block;
97
97
+
padding: 8px 20px;
98
98
+
font-size: 0.8125rem;
99
99
+
color: var(--secondary-color);
100
100
+
text-decoration: none;
101
101
+
transition: background 0.1s, color 0.1s;
102
102
+
}
103
103
+
104
104
+
.sidebar nav a:hover {
105
105
+
background: var(--bg-secondary-color);
106
106
+
color: var(--primary-color);
107
107
+
}
108
108
+
109
109
+
.sidebar nav a.active {
110
110
+
color: var(--brand-color);
111
111
+
font-weight: 500;
112
112
+
}
113
113
+
114
114
+
.sidebar-footer {
115
115
+
padding: 16px 20px 0;
116
116
+
border-top: 1px solid var(--border-color);
117
117
+
margin-top: 16px;
118
118
+
}
119
119
+
120
120
+
.sidebar-footer .session-info {
121
121
+
font-size: 0.75rem;
122
122
+
color: var(--secondary-color);
123
123
+
margin-bottom: 8px;
124
124
+
}
125
125
+
126
126
+
.sidebar-footer form {
127
127
+
display: inline;
128
128
+
}
129
129
+
130
130
+
.sidebar-footer button {
131
131
+
background: none;
132
132
+
border: none;
133
133
+
font-size: 0.75rem;
134
134
+
color: var(--secondary-color);
135
135
+
cursor: pointer;
136
136
+
padding: 0;
137
137
+
text-decoration: underline;
138
138
+
}
139
139
+
140
140
+
.sidebar-footer button:hover {
141
141
+
color: var(--primary-color);
142
142
+
}
143
143
+
144
144
+
.main {
145
145
+
margin-left: 220px;
146
146
+
flex: 1;
147
147
+
padding: 32px;
148
148
+
max-width: 960px;
149
149
+
}
150
150
+
151
151
+
/* --- Common Components -------------------------------------- */
152
152
+
153
153
+
.page-title {
154
154
+
font-size: 1.5rem;
155
155
+
font-weight: 700;
156
156
+
margin-bottom: 24px;
157
157
+
}
158
158
+
159
159
+
.page-subtitle {
160
160
+
font-size: 0.8125rem;
161
161
+
color: var(--secondary-color);
162
162
+
font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
163
163
+
margin-top: -20px;
164
164
+
margin-bottom: 24px;
165
165
+
word-break: break-all;
166
166
+
}
167
167
+
168
168
+
.page-description {
169
169
+
font-size: 0.875rem;
170
170
+
color: var(--secondary-color);
171
171
+
margin-top: -16px;
172
172
+
margin-bottom: 24px;
173
173
+
}
174
174
+
175
175
+
.flash-success {
176
176
+
background: rgba(22, 163, 74, 0.1);
177
177
+
color: var(--success-color);
178
178
+
border: 1px solid rgba(22, 163, 74, 0.2);
179
179
+
border-radius: 8px;
180
180
+
padding: 10px 14px;
181
181
+
font-size: 0.875rem;
182
182
+
margin-bottom: 20px;
183
183
+
}
184
184
+
185
185
+
.flash-error {
186
186
+
background: rgba(220, 38, 38, 0.1);
187
187
+
color: var(--danger-color);
188
188
+
border: 1px solid rgba(220, 38, 38, 0.2);
189
189
+
border-radius: 8px;
190
190
+
padding: 10px 14px;
191
191
+
font-size: 0.875rem;
192
192
+
margin-bottom: 20px;
193
193
+
}
194
194
+
195
195
+
/* --- Buttons ------------------------------------------------ */
196
196
+
197
197
+
.btn {
198
198
+
display: inline-flex;
199
199
+
align-items: center;
200
200
+
justify-content: center;
201
201
+
padding: 10px 20px;
202
202
+
font-size: 0.875rem;
203
203
+
font-weight: 500;
204
204
+
border: none;
205
205
+
border-radius: 8px;
206
206
+
cursor: pointer;
207
207
+
transition: opacity 0.15s;
208
208
+
text-decoration: none;
209
209
+
}
210
210
+
211
211
+
.btn:hover {
212
212
+
opacity: 0.85;
213
213
+
}
214
214
+
215
215
+
.btn-primary {
216
216
+
background: var(--brand-color);
217
217
+
color: #fff;
218
218
+
}
219
219
+
220
220
+
.btn-danger {
221
221
+
background: var(--danger-color);
222
222
+
color: #fff;
223
223
+
border-color: var(--danger-color);
224
224
+
}
225
225
+
226
226
+
.btn-warning {
227
227
+
background: var(--warning-color);
228
228
+
color: #000;
229
229
+
border-color: var(--warning-color);
230
230
+
}
231
231
+
232
232
+
.btn-small {
233
233
+
padding: 6px 12px;
234
234
+
font-size: 0.75rem;
235
235
+
}
236
236
+
237
237
+
.btn-outline-danger {
238
238
+
background: transparent;
239
239
+
color: var(--danger-color);
240
240
+
border: 1px solid var(--danger-color);
241
241
+
}
242
242
+
243
243
+
/* --- Detail Sections ---------------------------------------- */
244
244
+
245
245
+
.detail-section {
246
246
+
background: var(--bg-primary-color);
247
247
+
border: 1px solid var(--border-color);
248
248
+
border-radius: 10px;
249
249
+
padding: 20px;
250
250
+
margin-bottom: 16px;
251
251
+
}
252
252
+
253
253
+
.detail-section h3 {
254
254
+
font-size: 0.875rem;
255
255
+
font-weight: 600;
256
256
+
margin-bottom: 12px;
257
257
+
}
258
258
+
259
259
+
.detail-row {
260
260
+
display: flex;
261
261
+
justify-content: space-between;
262
262
+
align-items: center;
263
263
+
padding: 8px 0;
264
264
+
font-size: 0.8125rem;
265
265
+
border-bottom: 1px solid var(--border-color);
266
266
+
}
267
267
+
268
268
+
.detail-row:last-child {
269
269
+
border-bottom: none;
270
270
+
}
271
271
+
272
272
+
.detail-row .label {
273
273
+
color: var(--secondary-color);
274
274
+
flex-shrink: 0;
275
275
+
}
276
276
+
277
277
+
.detail-row .value {
278
278
+
font-weight: 500;
279
279
+
word-break: break-all;
280
280
+
text-align: right;
281
281
+
max-width: 65%;
282
282
+
}
283
283
+
284
284
+
.detail-row .value a {
285
285
+
color: var(--brand-color);
286
286
+
text-decoration: none;
287
287
+
}
288
288
+
289
289
+
.detail-row .value a:hover {
290
290
+
text-decoration: underline;
291
291
+
}
292
292
+
293
293
+
/* --- Tables ------------------------------------------------- */
294
294
+
295
295
+
.table-container {
296
296
+
background: var(--bg-primary-color);
297
297
+
border: 1px solid var(--border-color);
298
298
+
border-radius: 10px;
299
299
+
overflow: hidden;
300
300
+
}
301
301
+
302
302
+
table {
303
303
+
width: 100%;
304
304
+
border-collapse: collapse;
305
305
+
}
306
306
+
307
307
+
thead th {
308
308
+
text-align: left;
309
309
+
padding: 12px 16px;
310
310
+
font-size: 0.75rem;
311
311
+
font-weight: 600;
312
312
+
color: var(--secondary-color);
313
313
+
text-transform: uppercase;
314
314
+
letter-spacing: 0.5px;
315
315
+
border-bottom: 1px solid var(--border-color);
316
316
+
}
317
317
+
318
318
+
tbody tr {
319
319
+
border-bottom: 1px solid var(--border-color);
320
320
+
}
321
321
+
322
322
+
tbody tr:last-child {
323
323
+
border-bottom: none;
324
324
+
}
325
325
+
326
326
+
tbody tr:nth-child(even) {
327
327
+
background: var(--table-stripe);
328
328
+
}
329
329
+
330
330
+
tbody td {
331
331
+
padding: 10px 16px;
332
332
+
font-size: 0.8125rem;
333
333
+
}
334
334
+
335
335
+
tbody td a {
336
336
+
color: var(--brand-color);
337
337
+
text-decoration: none;
338
338
+
}
339
339
+
340
340
+
tbody td a:hover {
341
341
+
text-decoration: underline;
342
342
+
}
343
343
+
344
344
+
/* --- Badges ------------------------------------------------- */
345
345
+
346
346
+
.badge {
347
347
+
display: inline-block;
348
348
+
padding: 2px 8px;
349
349
+
border-radius: 4px;
350
350
+
font-size: 0.75rem;
351
351
+
font-weight: 500;
352
352
+
}
353
353
+
354
354
+
.badge-success {
355
355
+
background: rgba(22, 163, 74, 0.1);
356
356
+
color: var(--success-color);
357
357
+
}
358
358
+
359
359
+
.badge-danger {
360
360
+
background: rgba(220, 38, 38, 0.1);
361
361
+
color: var(--danger-color);
362
362
+
}
363
363
+
364
364
+
.badge-warning {
365
365
+
background: rgba(234, 179, 8, 0.1);
366
366
+
color: var(--warning-color);
367
367
+
}
368
368
+
369
369
+
/* --- Forms -------------------------------------------------- */
370
370
+
371
371
+
.form-card {
372
372
+
background: var(--bg-primary-color);
373
373
+
border: 1px solid var(--border-color);
374
374
+
border-radius: 10px;
375
375
+
padding: 24px;
376
376
+
max-width: 480px;
377
377
+
}
378
378
+
379
379
+
.form-group {
380
380
+
margin-bottom: 16px;
381
381
+
}
382
382
+
383
383
+
.form-group label {
384
384
+
display: block;
385
385
+
font-size: 0.8125rem;
386
386
+
font-weight: 500;
387
387
+
margin-bottom: 6px;
388
388
+
color: var(--primary-color);
389
389
+
}
390
390
+
391
391
+
.form-group input {
392
392
+
width: 100%;
393
393
+
padding: 10px 12px;
394
394
+
font-size: 0.875rem;
395
395
+
border: 1px solid var(--border-color);
396
396
+
border-radius: 8px;
397
397
+
background: var(--bg-primary-color);
398
398
+
color: var(--primary-color);
399
399
+
outline: none;
400
400
+
transition: border-color 0.15s;
401
401
+
}
402
402
+
403
403
+
.form-group input:focus {
404
404
+
border-color: var(--brand-color);
405
405
+
}
406
406
+
407
407
+
.form-group .hint {
408
408
+
font-size: 0.75rem;
409
409
+
color: var(--secondary-color);
410
410
+
margin-top: 4px;
411
411
+
}
412
412
+
413
413
+
.search-form {
414
414
+
display: flex;
415
415
+
gap: 8px;
416
416
+
margin-bottom: 24px;
417
417
+
}
418
418
+
419
419
+
.search-form input {
420
420
+
flex: 1;
421
421
+
padding: 10px 12px;
422
422
+
font-size: 0.875rem;
423
423
+
border: 1px solid var(--border-color);
424
424
+
border-radius: 8px;
425
425
+
background: var(--bg-primary-color);
426
426
+
color: var(--primary-color);
427
427
+
outline: none;
428
428
+
}
429
429
+
430
430
+
.search-form input:focus {
431
431
+
border-color: var(--brand-color);
432
432
+
}
433
433
+
434
434
+
.create-form {
435
435
+
display: flex;
436
436
+
gap: 8px;
437
437
+
align-items: flex-end;
438
438
+
margin-bottom: 24px;
439
439
+
}
440
440
+
441
441
+
.create-form .form-group {
442
442
+
display: flex;
443
443
+
flex-direction: column;
444
444
+
gap: 4px;
445
445
+
}
446
446
+
447
447
+
.create-form label {
448
448
+
font-size: 0.75rem;
449
449
+
font-weight: 500;
450
450
+
color: var(--secondary-color);
451
451
+
}
452
452
+
453
453
+
.create-form input[type="number"] {
454
454
+
padding: 10px 12px;
455
455
+
font-size: 0.875rem;
456
456
+
border: 1px solid var(--border-color);
457
457
+
border-radius: 8px;
458
458
+
background: var(--bg-primary-color);
459
459
+
color: var(--primary-color);
460
460
+
outline: none;
461
461
+
width: 120px;
462
462
+
}
463
463
+
464
464
+
.create-form input[type="number"]:focus {
465
465
+
border-color: var(--brand-color);
466
466
+
}
467
467
+
468
468
+
/* --- Utility ------------------------------------------------ */
469
469
+
470
470
+
.mono-text {
471
471
+
font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
472
472
+
font-size: 0.75rem;
473
473
+
}
474
474
+
475
475
+
.inline-form {
476
476
+
display: inline;
477
477
+
}
478
478
+
479
479
+
.empty-state {
480
480
+
text-align: center;
481
481
+
padding: 40px 20px;
482
482
+
color: var(--secondary-color);
483
483
+
font-size: 0.875rem;
484
484
+
}
485
485
+
486
486
+
.load-more {
487
487
+
text-align: center;
488
488
+
padding: 16px;
489
489
+
}
490
490
+
491
491
+
.load-more a {
492
492
+
color: var(--brand-color);
493
493
+
text-decoration: none;
494
494
+
font-size: 0.875rem;
495
495
+
font-weight: 500;
496
496
+
}
497
497
+
498
498
+
.load-more a:hover {
499
499
+
text-decoration: underline;
500
500
+
}
501
501
+
502
502
+
/* --- Dashboard: Cards --------------------------------------- */
503
503
+
504
504
+
.cards {
505
505
+
display: grid;
506
506
+
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
507
507
+
gap: 16px;
508
508
+
margin-bottom: 32px;
509
509
+
}
510
510
+
511
511
+
.card {
512
512
+
background: var(--bg-primary-color);
513
513
+
border: 1px solid var(--border-color);
514
514
+
border-radius: 10px;
515
515
+
padding: 20px;
516
516
+
}
517
517
+
518
518
+
.card-label {
519
519
+
font-size: 0.75rem;
520
520
+
font-weight: 500;
521
521
+
color: var(--secondary-color);
522
522
+
text-transform: uppercase;
523
523
+
letter-spacing: 0.5px;
524
524
+
margin-bottom: 6px;
525
525
+
}
526
526
+
527
527
+
.card-value {
528
528
+
font-size: 1.25rem;
529
529
+
font-weight: 600;
530
530
+
}
531
531
+
532
532
+
.card-value.success {
533
533
+
color: var(--success-color);
534
534
+
}
535
535
+
536
536
+
.card-value.danger {
537
537
+
color: var(--danger-color);
538
538
+
}
539
539
+
540
540
+
/* --- Accounts: DID Cell ------------------------------------- */
541
541
+
542
542
+
.did-cell {
543
543
+
font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
544
544
+
font-size: 0.75rem;
545
545
+
color: var(--secondary-color);
546
546
+
}
547
547
+
548
548
+
/* --- Account Detail ----------------------------------------- */
549
549
+
550
550
+
.actions {
551
551
+
display: flex;
552
552
+
flex-wrap: wrap;
553
553
+
gap: 8px;
554
554
+
margin-top: 8px;
555
555
+
}
556
556
+
557
557
+
.actions form {
558
558
+
display: inline;
559
559
+
}
560
560
+
561
561
+
.actions .btn {
562
562
+
padding: 8px 16px;
563
563
+
font-size: 0.8125rem;
564
564
+
border: 1px solid var(--border-color);
565
565
+
background: var(--bg-primary-color);
566
566
+
color: var(--primary-color);
567
567
+
}
568
568
+
569
569
+
.actions .btn-primary {
570
570
+
background: var(--brand-color);
571
571
+
color: #fff;
572
572
+
border-color: var(--brand-color);
573
573
+
}
574
574
+
575
575
+
.actions .btn-danger {
576
576
+
background: var(--danger-color);
577
577
+
color: #fff;
578
578
+
border-color: var(--danger-color);
579
579
+
}
580
580
+
581
581
+
.actions .btn-warning {
582
582
+
background: var(--warning-color);
583
583
+
color: #000;
584
584
+
border-color: var(--warning-color);
585
585
+
}
586
586
+
587
587
+
.password-box {
588
588
+
background: rgba(22, 163, 74, 0.08);
589
589
+
border: 1px solid rgba(22, 163, 74, 0.2);
590
590
+
border-radius: 10px;
591
591
+
padding: 16px 20px;
592
592
+
margin-bottom: 16px;
593
593
+
}
594
594
+
595
595
+
.password-box .pw-label {
596
596
+
font-size: 0.75rem;
597
597
+
font-weight: 600;
598
598
+
color: var(--success-color);
599
599
+
margin-bottom: 6px;
600
600
+
}
601
601
+
602
602
+
.password-box .pw-value {
603
603
+
font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
604
604
+
font-size: 1rem;
605
605
+
font-weight: 600;
606
606
+
user-select: all;
607
607
+
}
608
608
+
609
609
+
.back-link {
610
610
+
display: inline-block;
611
611
+
color: var(--brand-color);
612
612
+
text-decoration: none;
613
613
+
font-size: 0.8125rem;
614
614
+
margin-bottom: 16px;
615
615
+
}
616
616
+
617
617
+
.back-link:hover {
618
618
+
text-decoration: underline;
619
619
+
}
620
620
+
621
621
+
.collection-list {
622
622
+
max-height: 200px;
623
623
+
overflow-y: auto;
624
624
+
padding: 8px 0;
625
625
+
}
626
626
+
627
627
+
.collection-item {
628
628
+
font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
629
629
+
font-size: 0.75rem;
630
630
+
padding: 4px 0;
631
631
+
color: var(--secondary-color);
632
632
+
border-bottom: 1px solid var(--border-color);
633
633
+
}
634
634
+
635
635
+
.collection-item:last-child {
636
636
+
border-bottom: none;
637
637
+
}
638
638
+
639
639
+
.threat-sig {
640
640
+
font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
641
641
+
font-size: 0.75rem;
642
642
+
padding: 4px 0;
643
643
+
color: var(--secondary-color);
644
644
+
}
645
645
+
646
646
+
/* --- Create Account ----------------------------------------- */
647
647
+
648
648
+
.success-card {
649
649
+
background: var(--bg-primary-color);
650
650
+
border: 1px solid var(--border-color);
651
651
+
border-radius: 10px;
652
652
+
padding: 24px;
653
653
+
max-width: 480px;
654
654
+
}
655
655
+
656
656
+
.success-card h3 {
657
657
+
font-size: 1rem;
658
658
+
font-weight: 600;
659
659
+
color: var(--success-color);
660
660
+
margin-bottom: 16px;
661
661
+
}
662
662
+
663
663
+
.success-card .detail-row .value {
664
664
+
display: flex;
665
665
+
align-items: center;
666
666
+
gap: 6px;
667
667
+
}
668
668
+
669
669
+
.password-highlight {
670
670
+
font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
671
671
+
background: rgba(22, 163, 74, 0.08);
672
672
+
padding: 2px 6px;
673
673
+
border-radius: 4px;
674
674
+
user-select: all;
675
675
+
}
676
676
+
677
677
+
.copy-btn {
678
678
+
background: none;
679
679
+
border: 1px solid var(--border-color);
680
680
+
border-radius: 4px;
681
681
+
padding: 2px 6px;
682
682
+
font-size: 0.6875rem;
683
683
+
cursor: pointer;
684
684
+
color: var(--secondary-color);
685
685
+
transition: color 0.15s, border-color 0.15s;
686
686
+
white-space: nowrap;
687
687
+
}
688
688
+
689
689
+
.copy-btn:hover {
690
690
+
color: var(--primary-color);
691
691
+
border-color: var(--primary-color);
692
692
+
}
693
693
+
694
694
+
/* --- Invite Codes ------------------------------------------- */
695
695
+
696
696
+
.code-box {
697
697
+
background: rgba(22, 163, 74, 0.08);
698
698
+
border: 1px solid rgba(22, 163, 74, 0.2);
699
699
+
border-radius: 10px;
700
700
+
padding: 16px 20px;
701
701
+
margin-bottom: 24px;
702
702
+
}
703
703
+
704
704
+
.code-box .code-label {
705
705
+
font-size: 0.75rem;
706
706
+
font-weight: 600;
707
707
+
color: var(--success-color);
708
708
+
margin-bottom: 6px;
709
709
+
}
710
710
+
711
711
+
.code-box .code-value {
712
712
+
font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
713
713
+
font-size: 1rem;
714
714
+
font-weight: 600;
715
715
+
user-select: all;
716
716
+
}
717
717
+
718
718
+
.code-cell {
719
719
+
font-family: 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
720
720
+
font-size: 0.75rem;
721
721
+
}
722
722
+
723
723
+
/* --- Login Page --------------------------------------------- */
724
724
+
725
725
+
.login-card {
726
726
+
background: var(--bg-primary-color);
727
727
+
border: 1px solid var(--border-color);
728
728
+
border-radius: 12px;
729
729
+
padding: 40px;
730
730
+
width: 100%;
731
731
+
max-width: 400px;
732
732
+
margin: 20px;
733
733
+
}
734
734
+
735
735
+
.login-title {
736
736
+
font-size: 1.5rem;
737
737
+
font-weight: 700;
738
738
+
text-align: center;
739
739
+
margin-bottom: 4px;
740
740
+
}
741
741
+
742
742
+
.login-subtitle {
743
743
+
font-size: 0.875rem;
744
744
+
color: var(--secondary-color);
745
745
+
text-align: center;
746
746
+
margin-bottom: 32px;
747
747
+
}
748
748
+
749
749
+
.login-card .form-group {
750
750
+
margin-bottom: 20px;
751
751
+
}
752
752
+
753
753
+
.login-card .form-group label {
754
754
+
font-size: 0.875rem;
755
755
+
}
756
756
+
757
757
+
.login-card .btn-primary {
758
758
+
width: 100%;
759
759
+
}
760
760
+
761
761
+
.error-msg {
762
762
+
background: rgba(220, 38, 38, 0.1);
763
763
+
color: var(--danger-color);
764
764
+
border: 1px solid rgba(220, 38, 38, 0.2);
765
765
+
border-radius: 8px;
766
766
+
padding: 10px 14px;
767
767
+
font-size: 0.875rem;
768
768
+
margin-bottom: 20px;
769
769
+
}
770
770
+
771
771
+
/* --- Error Page ---------------------------------------------- */
772
772
+
773
773
+
.centered {
774
774
+
display: flex;
775
775
+
align-items: center;
776
776
+
justify-content: center;
777
777
+
min-height: 100vh;
778
778
+
}
779
779
+
780
780
+
.error-card {
781
781
+
background: var(--bg-primary-color);
782
782
+
border: 1px solid var(--border-color);
783
783
+
border-radius: 12px;
784
784
+
padding: 40px;
785
785
+
text-align: center;
786
786
+
max-width: 480px;
787
787
+
width: 100%;
788
788
+
margin: 20px;
789
789
+
}
790
790
+
791
791
+
.error-card--inset {
792
792
+
margin: 60px auto;
793
793
+
}
794
794
+
795
795
+
.error-icon {
796
796
+
font-size: 2.5rem;
797
797
+
margin-bottom: 16px;
798
798
+
color: var(--danger-color);
799
799
+
}
800
800
+
801
801
+
.error-title {
802
802
+
font-size: 1.25rem;
803
803
+
font-weight: 700;
804
804
+
margin-bottom: 8px;
805
805
+
}
806
806
+
807
807
+
.error-message {
808
808
+
font-size: 0.875rem;
809
809
+
color: var(--secondary-color);
810
810
+
margin-bottom: 24px;
811
811
+
line-height: 1.5;
812
812
+
}
813
813
+
814
814
+
.error-link {
815
815
+
display: inline-flex;
816
816
+
align-items: center;
817
817
+
justify-content: center;
818
818
+
padding: 10px 20px;
819
819
+
font-size: 0.875rem;
820
820
+
font-weight: 500;
821
821
+
border: none;
822
822
+
border-radius: 8px;
823
823
+
cursor: pointer;
824
824
+
text-decoration: none;
825
825
+
background: var(--brand-color);
826
826
+
color: #fff;
827
827
+
transition: opacity 0.15s;
828
828
+
}
829
829
+
830
830
+
.error-link:hover {
831
831
+
opacity: 0.85;
832
832
+
}
833
833
+
834
834
+
/* --- Responsive --------------------------------------------- */
835
835
+
836
836
+
@media (max-width: 768px) {
837
837
+
.sidebar {
838
838
+
display: none;
839
839
+
}
840
840
+
841
841
+
.main {
842
842
+
margin-left: 0;
843
843
+
}
844
844
+
}