skrift 0.1.0a12__py3-none-any.whl
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.
- skrift/__init__.py +1 -0
- skrift/__main__.py +12 -0
- skrift/admin/__init__.py +11 -0
- skrift/admin/controller.py +452 -0
- skrift/admin/navigation.py +105 -0
- skrift/alembic/env.py +92 -0
- skrift/alembic/script.py.mako +26 -0
- skrift/alembic/versions/20260120_210154_09b0364dbb7b_initial_schema.py +70 -0
- skrift/alembic/versions/20260122_152744_0b7c927d2591_add_roles_and_permissions.py +57 -0
- skrift/alembic/versions/20260122_172836_cdf734a5b847_add_sa_orm_sentinel_column.py +31 -0
- skrift/alembic/versions/20260122_175637_a9c55348eae7_remove_page_type_column.py +43 -0
- skrift/alembic/versions/20260122_200000_add_settings_table.py +38 -0
- skrift/alembic/versions/20260129_add_oauth_accounts.py +141 -0
- skrift/alembic/versions/20260129_add_provider_metadata.py +29 -0
- skrift/alembic.ini +77 -0
- skrift/asgi.py +670 -0
- skrift/auth/__init__.py +58 -0
- skrift/auth/guards.py +130 -0
- skrift/auth/roles.py +129 -0
- skrift/auth/services.py +184 -0
- skrift/cli.py +143 -0
- skrift/config.py +259 -0
- skrift/controllers/__init__.py +4 -0
- skrift/controllers/auth.py +595 -0
- skrift/controllers/web.py +67 -0
- skrift/db/__init__.py +3 -0
- skrift/db/base.py +7 -0
- skrift/db/models/__init__.py +7 -0
- skrift/db/models/oauth_account.py +50 -0
- skrift/db/models/page.py +26 -0
- skrift/db/models/role.py +56 -0
- skrift/db/models/setting.py +13 -0
- skrift/db/models/user.py +36 -0
- skrift/db/services/__init__.py +1 -0
- skrift/db/services/oauth_service.py +195 -0
- skrift/db/services/page_service.py +217 -0
- skrift/db/services/setting_service.py +206 -0
- skrift/lib/__init__.py +3 -0
- skrift/lib/exceptions.py +168 -0
- skrift/lib/template.py +108 -0
- skrift/setup/__init__.py +14 -0
- skrift/setup/config_writer.py +213 -0
- skrift/setup/controller.py +888 -0
- skrift/setup/middleware.py +89 -0
- skrift/setup/providers.py +214 -0
- skrift/setup/state.py +315 -0
- skrift/static/css/style.css +1003 -0
- skrift/templates/admin/admin.html +19 -0
- skrift/templates/admin/base.html +24 -0
- skrift/templates/admin/pages/edit.html +32 -0
- skrift/templates/admin/pages/list.html +62 -0
- skrift/templates/admin/settings/site.html +32 -0
- skrift/templates/admin/users/list.html +58 -0
- skrift/templates/admin/users/roles.html +42 -0
- skrift/templates/auth/dummy_login.html +102 -0
- skrift/templates/auth/login.html +139 -0
- skrift/templates/base.html +52 -0
- skrift/templates/error-404.html +19 -0
- skrift/templates/error-500.html +19 -0
- skrift/templates/error.html +19 -0
- skrift/templates/index.html +9 -0
- skrift/templates/page.html +26 -0
- skrift/templates/setup/admin.html +24 -0
- skrift/templates/setup/auth.html +110 -0
- skrift/templates/setup/base.html +407 -0
- skrift/templates/setup/complete.html +17 -0
- skrift/templates/setup/configuring.html +158 -0
- skrift/templates/setup/database.html +125 -0
- skrift/templates/setup/restart.html +28 -0
- skrift/templates/setup/site.html +39 -0
- skrift-0.1.0a12.dist-info/METADATA +235 -0
- skrift-0.1.0a12.dist-info/RECORD +74 -0
- skrift-0.1.0a12.dist-info/WHEEL +4 -0
- skrift-0.1.0a12.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{% extends "setup/base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}Create Admin Account - Skrift{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block content %}
|
|
6
|
+
<h2>Create Admin Account</h2>
|
|
7
|
+
<p class="description">Sign in with one of your configured providers to create the first admin account.</p>
|
|
8
|
+
|
|
9
|
+
<div class="oauth-buttons">
|
|
10
|
+
{% for key, provider in providers.items() %}
|
|
11
|
+
<a href="/setup/oauth/{{ key }}/login" class="oauth-btn oauth-btn-{{ key }}">
|
|
12
|
+
Sign in with {{ provider.name }}
|
|
13
|
+
</a>
|
|
14
|
+
{% endfor %}
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<p style="text-align: center; margin-top: 2rem; color: var(--muted-color, #666);">
|
|
18
|
+
<small>The first user to sign in will become the site administrator.</small>
|
|
19
|
+
</p>
|
|
20
|
+
|
|
21
|
+
<div class="form-actions" style="margin-top: 2rem;">
|
|
22
|
+
<a href="/setup/site" class="btn-secondary" role="button">Back</a>
|
|
23
|
+
</div>
|
|
24
|
+
{% endblock %}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
{% extends "setup/base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}Authentication Setup - Skrift{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block content %}
|
|
6
|
+
<h2>Authentication Providers</h2>
|
|
7
|
+
<p class="description">Configure at least one OAuth provider for user authentication.</p>
|
|
8
|
+
|
|
9
|
+
<form method="post" action="/setup/auth">
|
|
10
|
+
<div class="form-group">
|
|
11
|
+
<label for="redirect_base_url">OAuth Redirect Base URL</label>
|
|
12
|
+
<input type="url" id="redirect_base_url" name="redirect_base_url" value="{{ redirect_base_url }}" required>
|
|
13
|
+
<small>The base URL for OAuth callbacks (auto-detected from your current URL)</small>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<hr style="margin: 2rem 0;">
|
|
17
|
+
|
|
18
|
+
{% for key, provider in providers.items() %}
|
|
19
|
+
<div class="provider-card{% if key in configured_providers %} enabled{% endif %}" id="provider-{{ key }}">
|
|
20
|
+
<div class="provider-header" onclick="toggleProvider('{{ key }}')">
|
|
21
|
+
<input type="checkbox" name="{{ key }}_enabled" id="{{ key }}_enabled" {% if key in configured_providers %}checked{% endif %} onclick="event.stopPropagation(); toggleProvider('{{ key }}')">
|
|
22
|
+
<h3>{{ provider.name }}</h3>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="provider-fields">
|
|
25
|
+
<div class="provider-instructions">
|
|
26
|
+
<details>
|
|
27
|
+
<summary>Setup Instructions</summary>
|
|
28
|
+
<pre>{{ provider.instructions }}</pre>
|
|
29
|
+
<p><a href="{{ provider.console_url }}" target="_blank" rel="noopener">Open Developer Console</a></p>
|
|
30
|
+
</details>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div class="form-group">
|
|
34
|
+
<label>Redirect URI (copy this to your OAuth app settings)</label>
|
|
35
|
+
<div class="redirect-url" id="redirect-url-{{ key }}">{{ redirect_base_url }}/auth/{{ key }}/callback</div>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
{% for field in provider.fields %}
|
|
39
|
+
<div class="form-group">
|
|
40
|
+
<label for="{{ key }}_{{ field.key }}">{{ field.label }}{% if not field.optional %} *{% endif %}</label>
|
|
41
|
+
<input type="{{ field.type }}"
|
|
42
|
+
id="{{ key }}_{{ field.key }}"
|
|
43
|
+
name="{{ key }}_{{ field.key }}"
|
|
44
|
+
value="{{ configured_providers.get(key, {}).get(field.key, '') }}"
|
|
45
|
+
{% if field.placeholder %}placeholder="{{ field.placeholder }}"{% endif %}
|
|
46
|
+
{% if not field.optional %}required{% endif %}>
|
|
47
|
+
<div class="form-group env-toggle">
|
|
48
|
+
<input type="checkbox" id="{{ key }}_{{ field.key }}_env" name="{{ key }}_{{ field.key }}_env">
|
|
49
|
+
<label for="{{ key }}_{{ field.key }}_env">Use environment variable (enter env var name above)</label>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
{% endfor %}
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
{% endfor %}
|
|
56
|
+
|
|
57
|
+
<div class="form-actions">
|
|
58
|
+
<a href="/setup/database" class="btn-secondary" role="button">Back</a>
|
|
59
|
+
<button type="submit">Save & Continue</button>
|
|
60
|
+
</div>
|
|
61
|
+
</form>
|
|
62
|
+
{% endblock %}
|
|
63
|
+
|
|
64
|
+
{% block scripts %}
|
|
65
|
+
<script>
|
|
66
|
+
function toggleProvider(key) {
|
|
67
|
+
const card = document.getElementById('provider-' + key);
|
|
68
|
+
const checkbox = document.getElementById(key + '_enabled');
|
|
69
|
+
|
|
70
|
+
// Toggle the checkbox when clicking the header (but not when clicking the checkbox itself)
|
|
71
|
+
if (event.target !== checkbox) {
|
|
72
|
+
checkbox.checked = !checkbox.checked;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
card.classList.toggle('enabled', checkbox.checked);
|
|
76
|
+
|
|
77
|
+
// Toggle required attribute on fields
|
|
78
|
+
const fields = card.querySelectorAll('input[required]');
|
|
79
|
+
fields.forEach(field => {
|
|
80
|
+
if (checkbox.checked) {
|
|
81
|
+
field.setAttribute('required', '');
|
|
82
|
+
} else {
|
|
83
|
+
field.removeAttribute('required');
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Update redirect URLs when base URL changes
|
|
89
|
+
document.getElementById('redirect_base_url').addEventListener('input', function() {
|
|
90
|
+
const baseUrl = this.value;
|
|
91
|
+
{% for key in providers.keys() %}
|
|
92
|
+
document.getElementById('redirect-url-{{ key }}').textContent = baseUrl + '/auth/{{ key }}/callback';
|
|
93
|
+
{% endfor %}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Initialize required attributes based on initial state
|
|
97
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
98
|
+
{% for key in providers.keys() %}
|
|
99
|
+
const card{{ key }} = document.getElementById('provider-{{ key }}');
|
|
100
|
+
const checkbox{{ key }} = document.getElementById('{{ key }}_enabled');
|
|
101
|
+
const fields{{ key }} = card{{ key }}.querySelectorAll('.provider-fields input[type="text"], .provider-fields input[type="password"]');
|
|
102
|
+
fields{{ key }}.forEach(field => {
|
|
103
|
+
if (!checkbox{{ key }}.checked && !field.closest('.form-group').querySelector('label').textContent.includes('optional')) {
|
|
104
|
+
field.removeAttribute('required');
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
{% endfor %}
|
|
108
|
+
});
|
|
109
|
+
</script>
|
|
110
|
+
{% endblock %}
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{% block title %}Setup - Skrift{% endblock %}</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@500;600&family=Lora:ital,wght@0,400;0,600;1,400;1,600&display=swap" rel="stylesheet">
|
|
10
|
+
<link rel="stylesheet" href="/static/css/style.css">
|
|
11
|
+
<style>
|
|
12
|
+
.setup-container {
|
|
13
|
+
max-width: 600px;
|
|
14
|
+
margin: 2rem auto;
|
|
15
|
+
padding: 0 1rem;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.setup-header {
|
|
19
|
+
text-align: center;
|
|
20
|
+
margin-bottom: 2rem;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.setup-header h1 {
|
|
24
|
+
font-size: 2rem;
|
|
25
|
+
margin-bottom: 0.5rem;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.setup-progress {
|
|
29
|
+
display: flex;
|
|
30
|
+
justify-content: center;
|
|
31
|
+
gap: 0.5rem;
|
|
32
|
+
margin-bottom: 2rem;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.setup-progress-step {
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
gap: 0.5rem;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.setup-progress-dot {
|
|
42
|
+
width: 2rem;
|
|
43
|
+
height: 2rem;
|
|
44
|
+
border-radius: 50%;
|
|
45
|
+
display: flex;
|
|
46
|
+
align-items: center;
|
|
47
|
+
justify-content: center;
|
|
48
|
+
font-weight: bold;
|
|
49
|
+
font-size: 0.875rem;
|
|
50
|
+
background: var(--color-surface);
|
|
51
|
+
color: var(--color-text-muted);
|
|
52
|
+
border: 1px solid var(--color-border);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.setup-progress-dot.active {
|
|
56
|
+
background: var(--color-primary);
|
|
57
|
+
color: var(--color-primary-text);
|
|
58
|
+
border-color: var(--color-primary);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.setup-progress-dot.completed {
|
|
62
|
+
background: var(--color-success);
|
|
63
|
+
color: white;
|
|
64
|
+
border-color: var(--color-success);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.setup-progress-line {
|
|
68
|
+
width: 2rem;
|
|
69
|
+
height: 2px;
|
|
70
|
+
background: var(--color-border);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.setup-progress-line.completed {
|
|
74
|
+
background: var(--color-success);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.setup-card {
|
|
78
|
+
background: var(--color-surface);
|
|
79
|
+
border-radius: var(--radius-md);
|
|
80
|
+
padding: 2rem;
|
|
81
|
+
border: 1px solid var(--color-border);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.setup-card h2 {
|
|
85
|
+
margin-top: 0;
|
|
86
|
+
margin-bottom: 0.5rem;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.setup-card p.description {
|
|
90
|
+
color: var(--color-text-muted);
|
|
91
|
+
margin-bottom: 1.5rem;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.form-group {
|
|
95
|
+
margin-bottom: 1.5rem;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.form-group label {
|
|
99
|
+
display: block;
|
|
100
|
+
margin-bottom: 0.5rem;
|
|
101
|
+
font-weight: 500;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.form-group small {
|
|
105
|
+
display: block;
|
|
106
|
+
margin-top: 0.25rem;
|
|
107
|
+
color: var(--color-text-muted);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.form-group.env-toggle {
|
|
111
|
+
display: flex;
|
|
112
|
+
align-items: center;
|
|
113
|
+
gap: 0.5rem;
|
|
114
|
+
margin-top: 0.5rem;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.form-group.env-toggle label {
|
|
118
|
+
margin: 0;
|
|
119
|
+
font-weight: normal;
|
|
120
|
+
font-size: 0.875rem;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.form-group.env-toggle input[type="checkbox"] {
|
|
124
|
+
margin: 0;
|
|
125
|
+
width: 1.25rem;
|
|
126
|
+
height: 1.25rem;
|
|
127
|
+
flex-shrink: 0;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.radio-group {
|
|
131
|
+
display: flex;
|
|
132
|
+
gap: 1rem;
|
|
133
|
+
margin-bottom: 1rem;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.radio-option {
|
|
137
|
+
display: flex;
|
|
138
|
+
align-items: flex-start;
|
|
139
|
+
gap: 0.5rem;
|
|
140
|
+
padding: 1rem;
|
|
141
|
+
border: 2px solid var(--color-border);
|
|
142
|
+
border-radius: var(--radius-md);
|
|
143
|
+
cursor: pointer;
|
|
144
|
+
flex: 1;
|
|
145
|
+
background: var(--color-bg);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.radio-option:hover {
|
|
149
|
+
border-color: var(--color-primary);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.radio-option.selected {
|
|
153
|
+
border-color: var(--color-primary);
|
|
154
|
+
background: var(--color-surface);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.radio-option input[type="radio"] {
|
|
158
|
+
margin: 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.radio-option .radio-content {
|
|
162
|
+
flex: 1;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.radio-option .radio-content strong {
|
|
166
|
+
display: block;
|
|
167
|
+
margin-bottom: 0.25rem;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.radio-option .radio-content small {
|
|
171
|
+
color: var(--color-text-muted);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.conditional-fields {
|
|
175
|
+
display: none;
|
|
176
|
+
margin-top: 1.5rem;
|
|
177
|
+
padding-top: 1.5rem;
|
|
178
|
+
border-top: 1px solid var(--color-border);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.conditional-fields.visible {
|
|
182
|
+
display: block;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.form-actions {
|
|
186
|
+
display: flex;
|
|
187
|
+
justify-content: space-between;
|
|
188
|
+
margin-top: 2rem;
|
|
189
|
+
gap: 1rem;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.form-actions button,
|
|
193
|
+
.form-actions [role="button"] {
|
|
194
|
+
flex: 1;
|
|
195
|
+
margin-top: 0;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.alert {
|
|
199
|
+
padding: 1rem;
|
|
200
|
+
border-radius: var(--radius-md);
|
|
201
|
+
margin-bottom: 1rem;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.alert-error {
|
|
205
|
+
background: rgba(239, 68, 68, 0.1);
|
|
206
|
+
color: var(--color-error);
|
|
207
|
+
border: 1px solid var(--color-error);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.alert-success {
|
|
211
|
+
background: rgba(16, 185, 129, 0.1);
|
|
212
|
+
color: var(--color-success);
|
|
213
|
+
border: 1px solid var(--color-success);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.provider-card {
|
|
217
|
+
border: 2px solid var(--color-border);
|
|
218
|
+
border-radius: var(--radius-md);
|
|
219
|
+
margin-bottom: 1rem;
|
|
220
|
+
overflow: hidden;
|
|
221
|
+
background: var(--color-bg);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.provider-card.enabled {
|
|
225
|
+
border-color: var(--color-primary);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.provider-header {
|
|
229
|
+
display: flex;
|
|
230
|
+
align-items: center;
|
|
231
|
+
gap: 1rem;
|
|
232
|
+
padding: 1rem;
|
|
233
|
+
cursor: pointer;
|
|
234
|
+
background: var(--color-surface);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.provider-header input[type="checkbox"] {
|
|
238
|
+
margin: 0;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.provider-header h3 {
|
|
242
|
+
margin: 0;
|
|
243
|
+
flex: 1;
|
|
244
|
+
font-size: 1rem;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.provider-fields {
|
|
248
|
+
display: none;
|
|
249
|
+
padding: 1rem;
|
|
250
|
+
border-top: 1px solid var(--color-border);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.provider-card.enabled .provider-fields {
|
|
254
|
+
display: block;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.provider-instructions {
|
|
258
|
+
margin-bottom: 1rem;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.provider-instructions details {
|
|
262
|
+
background: var(--color-surface);
|
|
263
|
+
padding: 0.5rem 1rem;
|
|
264
|
+
border-radius: var(--radius-sm);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.provider-instructions summary {
|
|
268
|
+
cursor: pointer;
|
|
269
|
+
font-weight: 500;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.provider-instructions pre {
|
|
273
|
+
white-space: pre-wrap;
|
|
274
|
+
font-size: 0.875rem;
|
|
275
|
+
margin: 0.5rem 0;
|
|
276
|
+
background: var(--color-bg);
|
|
277
|
+
padding: 0.75rem;
|
|
278
|
+
border-radius: var(--radius-sm);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.redirect-url {
|
|
282
|
+
background: var(--color-bg);
|
|
283
|
+
padding: 0.5rem;
|
|
284
|
+
border-radius: var(--radius-sm);
|
|
285
|
+
font-family: var(--font-family-mono);
|
|
286
|
+
font-size: 0.875rem;
|
|
287
|
+
word-break: break-all;
|
|
288
|
+
margin: 0.5rem 0 1rem;
|
|
289
|
+
border: 1px solid var(--color-border);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.oauth-buttons {
|
|
293
|
+
display: flex;
|
|
294
|
+
flex-direction: column;
|
|
295
|
+
gap: 1rem;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.oauth-btn {
|
|
299
|
+
display: flex;
|
|
300
|
+
align-items: center;
|
|
301
|
+
justify-content: center;
|
|
302
|
+
gap: 0.75rem;
|
|
303
|
+
padding: 1rem 1.5rem;
|
|
304
|
+
border-radius: var(--radius-pill);
|
|
305
|
+
font-size: 1rem;
|
|
306
|
+
font-weight: 600;
|
|
307
|
+
text-decoration: none;
|
|
308
|
+
transition: opacity 0.2s, transform 0.1s;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.oauth-btn:hover {
|
|
312
|
+
opacity: 0.9;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.oauth-btn:active {
|
|
316
|
+
transform: translateY(1px);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.oauth-btn::after {
|
|
320
|
+
display: none;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.oauth-btn-google {
|
|
324
|
+
background: #4285f4;
|
|
325
|
+
color: white;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.oauth-btn-github {
|
|
329
|
+
background: #24292e;
|
|
330
|
+
color: white;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.oauth-btn-microsoft {
|
|
334
|
+
background: #00a4ef;
|
|
335
|
+
color: white;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.oauth-btn-discord {
|
|
339
|
+
background: #5865f2;
|
|
340
|
+
color: white;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.oauth-btn-facebook {
|
|
344
|
+
background: #1877f2;
|
|
345
|
+
color: white;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.oauth-btn-twitter {
|
|
349
|
+
background: #000000;
|
|
350
|
+
color: white;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.complete-message {
|
|
354
|
+
text-align: center;
|
|
355
|
+
padding: 2rem;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.complete-message h2 {
|
|
359
|
+
color: var(--color-success);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.complete-icon {
|
|
363
|
+
font-size: 4rem;
|
|
364
|
+
margin-bottom: 1rem;
|
|
365
|
+
color: var(--color-success);
|
|
366
|
+
}
|
|
367
|
+
</style>
|
|
368
|
+
{% block head %}{% endblock %}
|
|
369
|
+
</head>
|
|
370
|
+
<body>
|
|
371
|
+
<main class="setup-container">
|
|
372
|
+
<div class="setup-header">
|
|
373
|
+
<h1>Skrift Setup</h1>
|
|
374
|
+
<p>Configure your site in a few easy steps</p>
|
|
375
|
+
</div>
|
|
376
|
+
|
|
377
|
+
{% if step is defined %}
|
|
378
|
+
<div class="setup-progress">
|
|
379
|
+
{% for i in range(1, total_steps + 1) %}
|
|
380
|
+
{% if i > 1 %}
|
|
381
|
+
<div class="setup-progress-line{% if i <= step %} completed{% endif %}"></div>
|
|
382
|
+
{% endif %}
|
|
383
|
+
<div class="setup-progress-step">
|
|
384
|
+
<div class="setup-progress-dot{% if i < step %} completed{% elif i == step %} active{% endif %}">
|
|
385
|
+
{% if i < step %}✓{% else %}{{ i }}{% endif %}
|
|
386
|
+
</div>
|
|
387
|
+
</div>
|
|
388
|
+
{% endfor %}
|
|
389
|
+
</div>
|
|
390
|
+
{% endif %}
|
|
391
|
+
|
|
392
|
+
{% if flash %}
|
|
393
|
+
<div class="alert alert-success">{{ flash }}</div>
|
|
394
|
+
{% endif %}
|
|
395
|
+
|
|
396
|
+
{% if error %}
|
|
397
|
+
<div class="alert alert-error">{{ error }}</div>
|
|
398
|
+
{% endif %}
|
|
399
|
+
|
|
400
|
+
<div class="setup-card">
|
|
401
|
+
{% block content %}{% endblock %}
|
|
402
|
+
</div>
|
|
403
|
+
</main>
|
|
404
|
+
|
|
405
|
+
{% block scripts %}{% endblock %}
|
|
406
|
+
</body>
|
|
407
|
+
</html>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{% extends "setup/base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}Setup Complete - Skrift{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block content %}
|
|
6
|
+
<div class="complete-message">
|
|
7
|
+
<div class="complete-icon">✓</div>
|
|
8
|
+
<h2>Setup Complete!</h2>
|
|
9
|
+
<p>Your Skrift site is now configured and ready to use.</p>
|
|
10
|
+
<p style="color: var(--muted-color, #666);">You can manage your site settings, users, and content from the admin panel.</p>
|
|
11
|
+
|
|
12
|
+
<div style="margin-top: 2rem;">
|
|
13
|
+
<a href="/" role="button" style="margin-right: 1rem;">Go to Site</a>
|
|
14
|
+
<a href="/admin" role="button" class="outline">Open Admin Panel</a>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
{% endblock %}
|