skrift 0.1.0a1__py3-none-any.whl → 0.1.0a3__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/alembic/env.py +2 -1
- skrift/alembic/versions/20260129_add_oauth_accounts.py +134 -0
- skrift/alembic.ini +2 -2
- skrift/asgi.py +19 -11
- skrift/cli.py +22 -13
- skrift/config.py +59 -5
- skrift/controllers/auth.py +168 -22
- skrift/db/models/__init__.py +2 -1
- skrift/db/models/oauth_account.py +37 -0
- skrift/db/models/user.py +5 -5
- skrift/setup/config_writer.py +4 -2
- skrift/setup/controller.py +209 -72
- skrift/setup/providers.py +53 -2
- skrift/setup/state.py +185 -4
- skrift/static/css/style.css +3 -3
- skrift/templates/auth/dummy_login.html +102 -0
- skrift/templates/auth/login.html +14 -0
- skrift/templates/setup/configuring.html +158 -0
- {skrift-0.1.0a1.dist-info → skrift-0.1.0a3.dist-info}/METADATA +3 -1
- {skrift-0.1.0a1.dist-info → skrift-0.1.0a3.dist-info}/RECORD +22 -18
- {skrift-0.1.0a1.dist-info → skrift-0.1.0a3.dist-info}/WHEEL +0 -0
- {skrift-0.1.0a1.dist-info → skrift-0.1.0a3.dist-info}/entry_points.txt +0 -0
skrift/setup/state.py
CHANGED
|
@@ -3,16 +3,36 @@
|
|
|
3
3
|
This module implements a two-tier detection strategy:
|
|
4
4
|
1. Pre-database check: Can we connect to a database?
|
|
5
5
|
2. Post-database check: Is setup complete (check for setup_completed_at setting)?
|
|
6
|
+
|
|
7
|
+
Smart step detection: If config is already present, skip to the first incomplete step.
|
|
6
8
|
"""
|
|
7
9
|
|
|
8
10
|
import os
|
|
11
|
+
import subprocess
|
|
9
12
|
from enum import Enum
|
|
10
13
|
from pathlib import Path
|
|
11
|
-
|
|
14
|
+
import yaml
|
|
12
15
|
from sqlalchemy import text
|
|
13
16
|
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
# Track if migrations have been run this session to avoid running multiple times
|
|
19
|
+
_migrations_run = False
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def reset_migrations_flag() -> None:
|
|
23
|
+
"""Reset the migrations flag to allow re-running migrations.
|
|
24
|
+
|
|
25
|
+
Call this when starting the configuring page to ensure migrations run fresh.
|
|
26
|
+
"""
|
|
27
|
+
global _migrations_run
|
|
28
|
+
_migrations_run = False
|
|
29
|
+
|
|
30
|
+
from skrift.config import get_config_path
|
|
31
|
+
from skrift.db.services.setting_service import (
|
|
32
|
+
SETUP_COMPLETED_AT_KEY,
|
|
33
|
+
SITE_NAME_KEY,
|
|
34
|
+
get_setting,
|
|
35
|
+
)
|
|
16
36
|
|
|
17
37
|
|
|
18
38
|
class SetupStep(Enum):
|
|
@@ -27,7 +47,7 @@ class SetupStep(Enum):
|
|
|
27
47
|
|
|
28
48
|
def app_yaml_exists() -> bool:
|
|
29
49
|
"""Check if app.yaml exists in the current working directory."""
|
|
30
|
-
return (
|
|
50
|
+
return get_config_path().exists()
|
|
31
51
|
|
|
32
52
|
|
|
33
53
|
def get_database_url_from_yaml() -> str | None:
|
|
@@ -38,7 +58,7 @@ def get_database_url_from_yaml() -> str | None:
|
|
|
38
58
|
"""
|
|
39
59
|
import yaml
|
|
40
60
|
|
|
41
|
-
config_path =
|
|
61
|
+
config_path = get_config_path()
|
|
42
62
|
if not config_path.exists():
|
|
43
63
|
return None
|
|
44
64
|
|
|
@@ -102,6 +122,167 @@ async def is_setup_complete(db_session: AsyncSession) -> bool:
|
|
|
102
122
|
return False
|
|
103
123
|
|
|
104
124
|
|
|
125
|
+
def is_auth_configured() -> bool:
|
|
126
|
+
"""Check if at least one OAuth provider is fully configured in app.yaml.
|
|
127
|
+
|
|
128
|
+
A provider is considered configured if it has both client_id and client_secret.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
True if at least one provider is configured, False otherwise.
|
|
132
|
+
"""
|
|
133
|
+
config_path = get_config_path()
|
|
134
|
+
if not config_path.exists():
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
with open(config_path, "r") as f:
|
|
139
|
+
config = yaml.safe_load(f)
|
|
140
|
+
|
|
141
|
+
if not config:
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
auth = config.get("auth", {})
|
|
145
|
+
providers = auth.get("providers", {})
|
|
146
|
+
|
|
147
|
+
for _, provider_config in providers.items():
|
|
148
|
+
if not isinstance(provider_config, dict):
|
|
149
|
+
continue
|
|
150
|
+
# Check if provider has both client_id and client_secret (even as env var refs)
|
|
151
|
+
client_id = provider_config.get("client_id", "")
|
|
152
|
+
client_secret = provider_config.get("client_secret", "")
|
|
153
|
+
if client_id and client_secret:
|
|
154
|
+
return True
|
|
155
|
+
|
|
156
|
+
return False
|
|
157
|
+
except Exception:
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def run_migrations_if_needed() -> tuple[bool, str | None]:
|
|
162
|
+
"""Run database migrations if they haven't been run this session.
|
|
163
|
+
|
|
164
|
+
This ensures the database schema is up to date before checking for
|
|
165
|
+
settings or other database-dependent configuration.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Tuple of (success, error_message)
|
|
169
|
+
"""
|
|
170
|
+
global _migrations_run
|
|
171
|
+
if _migrations_run:
|
|
172
|
+
return True, None
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
# Try skrift-db first
|
|
176
|
+
result = subprocess.run(
|
|
177
|
+
["skrift-db", "upgrade", "head"],
|
|
178
|
+
capture_output=True,
|
|
179
|
+
text=True,
|
|
180
|
+
cwd=Path.cwd(),
|
|
181
|
+
timeout=60,
|
|
182
|
+
)
|
|
183
|
+
if result.returncode == 0:
|
|
184
|
+
_migrations_run = True
|
|
185
|
+
return True, None
|
|
186
|
+
# If skrift-db fails, try alembic directly
|
|
187
|
+
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
result = subprocess.run(
|
|
192
|
+
["alembic", "upgrade", "head"],
|
|
193
|
+
capture_output=True,
|
|
194
|
+
text=True,
|
|
195
|
+
cwd=Path.cwd(),
|
|
196
|
+
timeout=60,
|
|
197
|
+
)
|
|
198
|
+
if result.returncode == 0:
|
|
199
|
+
_migrations_run = True
|
|
200
|
+
return True, None
|
|
201
|
+
return False, result.stderr
|
|
202
|
+
except subprocess.TimeoutExpired:
|
|
203
|
+
return False, "Migration timed out"
|
|
204
|
+
except FileNotFoundError:
|
|
205
|
+
return False, "Neither skrift-db nor alembic found"
|
|
206
|
+
except Exception as e:
|
|
207
|
+
return False, str(e)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
async def is_site_configured() -> bool:
|
|
211
|
+
"""Check if site settings have been configured in the database.
|
|
212
|
+
|
|
213
|
+
The site step is considered complete if site_name has been set.
|
|
214
|
+
Returns False if the settings table doesn't exist yet (pre-migration).
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
True if site is configured, False otherwise.
|
|
218
|
+
"""
|
|
219
|
+
db_url = get_database_url_from_yaml()
|
|
220
|
+
if not db_url:
|
|
221
|
+
return False
|
|
222
|
+
|
|
223
|
+
engine = None
|
|
224
|
+
try:
|
|
225
|
+
engine = create_async_engine(db_url)
|
|
226
|
+
from sqlalchemy.ext.asyncio import async_sessionmaker
|
|
227
|
+
|
|
228
|
+
async_session = async_sessionmaker(engine, expire_on_commit=False)
|
|
229
|
+
async with async_session() as session:
|
|
230
|
+
try:
|
|
231
|
+
site_name = await get_setting(session, SITE_NAME_KEY)
|
|
232
|
+
return site_name is not None
|
|
233
|
+
except Exception:
|
|
234
|
+
# Table might not exist yet (before migration)
|
|
235
|
+
return False
|
|
236
|
+
except Exception:
|
|
237
|
+
return False
|
|
238
|
+
finally:
|
|
239
|
+
if engine:
|
|
240
|
+
await engine.dispose()
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
async def get_first_incomplete_step() -> SetupStep:
|
|
244
|
+
"""Determine the first incomplete step in the setup wizard.
|
|
245
|
+
|
|
246
|
+
This function checks configuration completeness for each step and returns
|
|
247
|
+
the first step that needs to be completed. Use this to skip already-configured
|
|
248
|
+
steps when the user is forced back into the setup wizard.
|
|
249
|
+
|
|
250
|
+
If database is configured and connectable, runs migrations to ensure
|
|
251
|
+
all tables exist before checking database-dependent configuration.
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
The first setup step that needs user input.
|
|
255
|
+
"""
|
|
256
|
+
# Step 1: Database - check if we can connect
|
|
257
|
+
if not app_yaml_exists():
|
|
258
|
+
return SetupStep.DATABASE
|
|
259
|
+
|
|
260
|
+
db_url = get_database_url_from_yaml()
|
|
261
|
+
if not db_url:
|
|
262
|
+
return SetupStep.DATABASE
|
|
263
|
+
|
|
264
|
+
can_connect, _ = await can_connect_to_database()
|
|
265
|
+
if not can_connect:
|
|
266
|
+
return SetupStep.DATABASE
|
|
267
|
+
|
|
268
|
+
# Database is configured and connectable - run migrations to ensure tables exist
|
|
269
|
+
migration_success, _ = run_migrations_if_needed()
|
|
270
|
+
if not migration_success:
|
|
271
|
+
# If migrations fail, go back to database step to show the error
|
|
272
|
+
return SetupStep.DATABASE
|
|
273
|
+
|
|
274
|
+
# Step 2: Auth - check if at least one provider is configured
|
|
275
|
+
if not is_auth_configured():
|
|
276
|
+
return SetupStep.AUTH
|
|
277
|
+
|
|
278
|
+
# Step 3: Site - check if site settings exist in DB
|
|
279
|
+
if not await is_site_configured():
|
|
280
|
+
return SetupStep.SITE
|
|
281
|
+
|
|
282
|
+
# Step 4: Admin - always go here if setup not complete
|
|
283
|
+
return SetupStep.ADMIN
|
|
284
|
+
|
|
285
|
+
|
|
105
286
|
async def get_setup_step(db_session: AsyncSession | None = None) -> SetupStep:
|
|
106
287
|
"""Determine which setup step the user should be on.
|
|
107
288
|
|
skrift/static/css/style.css
CHANGED
|
@@ -175,7 +175,7 @@ a {
|
|
|
175
175
|
position: relative;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
a:not([role="button"])::after {
|
|
178
|
+
a:not([role="button"]):not(.oauth-btn)::after {
|
|
179
179
|
content: '';
|
|
180
180
|
position: absolute;
|
|
181
181
|
left: 0;
|
|
@@ -188,11 +188,11 @@ a:not([role="button"])::after {
|
|
|
188
188
|
transition: transform 500ms ease-out;
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
a:hover {
|
|
191
|
+
a:not([role="button"]):not(.oauth-btn):hover {
|
|
192
192
|
color: var(--color-primary-hover);
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
a:not([role="button"]):hover::after {
|
|
195
|
+
a:not([role="button"]):not(.oauth-btn):hover::after {
|
|
196
196
|
transform: scaleX(1);
|
|
197
197
|
transform-origin: left;
|
|
198
198
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
{% extends "base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}Dummy Login (Dev) - {{ site_name() }}{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block head %}
|
|
6
|
+
<style>
|
|
7
|
+
.login-container {
|
|
8
|
+
max-width: 400px;
|
|
9
|
+
margin: 2rem auto;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.warning-banner {
|
|
13
|
+
background: #fef3c7;
|
|
14
|
+
border: 1px solid #f59e0b;
|
|
15
|
+
color: #92400e;
|
|
16
|
+
padding: 1rem;
|
|
17
|
+
border-radius: 0.5rem;
|
|
18
|
+
margin-bottom: 1.5rem;
|
|
19
|
+
text-align: center;
|
|
20
|
+
font-weight: 600;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.form-group {
|
|
24
|
+
margin-bottom: 1rem;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.form-group label {
|
|
28
|
+
display: block;
|
|
29
|
+
margin-bottom: 0.5rem;
|
|
30
|
+
font-weight: 600;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.form-group input {
|
|
34
|
+
width: 100%;
|
|
35
|
+
padding: 0.75rem;
|
|
36
|
+
border: 1px solid var(--color-border, #ccc);
|
|
37
|
+
border-radius: 0.5rem;
|
|
38
|
+
font-size: 1rem;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.form-group small {
|
|
42
|
+
display: block;
|
|
43
|
+
margin-top: 0.25rem;
|
|
44
|
+
color: var(--color-text-muted, #666);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.submit-btn {
|
|
48
|
+
width: 100%;
|
|
49
|
+
padding: 1rem;
|
|
50
|
+
background: #6b7280;
|
|
51
|
+
color: white;
|
|
52
|
+
border: none;
|
|
53
|
+
border-radius: 0.5rem;
|
|
54
|
+
font-size: 1rem;
|
|
55
|
+
font-weight: 600;
|
|
56
|
+
cursor: pointer;
|
|
57
|
+
margin-top: 1rem;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.submit-btn:hover {
|
|
61
|
+
background: #4b5563;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.back-link {
|
|
65
|
+
display: block;
|
|
66
|
+
text-align: center;
|
|
67
|
+
margin-top: 1.5rem;
|
|
68
|
+
color: var(--color-text-muted, #666);
|
|
69
|
+
}
|
|
70
|
+
</style>
|
|
71
|
+
{% endblock %}
|
|
72
|
+
|
|
73
|
+
{% block content %}
|
|
74
|
+
<div class="login-container">
|
|
75
|
+
<article>
|
|
76
|
+
<header>
|
|
77
|
+
<h1>Dummy Login</h1>
|
|
78
|
+
</header>
|
|
79
|
+
|
|
80
|
+
<div class="warning-banner">
|
|
81
|
+
DEVELOPMENT ONLY - Not for production use
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<form method="post" action="/auth/dummy-login">
|
|
85
|
+
<div class="form-group">
|
|
86
|
+
<label for="email">Email</label>
|
|
87
|
+
<input type="email" id="email" name="email" required placeholder="test@example.com">
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<div class="form-group">
|
|
91
|
+
<label for="name">Name</label>
|
|
92
|
+
<input type="text" id="name" name="name" placeholder="Test User">
|
|
93
|
+
<small>Optional. Defaults to email username if not provided.</small>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<button type="submit" class="submit-btn">Login</button>
|
|
97
|
+
</form>
|
|
98
|
+
|
|
99
|
+
<a href="/auth/login" class="back-link">← Back to login options</a>
|
|
100
|
+
</article>
|
|
101
|
+
</div>
|
|
102
|
+
{% endblock %}
|
skrift/templates/auth/login.html
CHANGED
|
@@ -120,6 +120,20 @@
|
|
|
120
120
|
<p>No authentication providers are configured. Please contact the site administrator.</p>
|
|
121
121
|
{% endfor %}
|
|
122
122
|
</div>
|
|
123
|
+
|
|
124
|
+
{% if has_dummy %}
|
|
125
|
+
<div style="margin-top: 1.5rem; padding-top: 1.5rem; border-top: 1px solid var(--color-border, #ccc);">
|
|
126
|
+
<p style="text-align: center; color: var(--color-text-muted, #666); font-size: 0.875rem; margin-bottom: 1rem;">
|
|
127
|
+
Development Only
|
|
128
|
+
</p>
|
|
129
|
+
<a href="/auth/dummy/login" class="oauth-btn" style="background: #6b7280; color: white;">
|
|
130
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
131
|
+
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"/>
|
|
132
|
+
</svg>
|
|
133
|
+
Dummy Login (Dev)
|
|
134
|
+
</a>
|
|
135
|
+
</div>
|
|
136
|
+
{% endif %}
|
|
123
137
|
</article>
|
|
124
138
|
</div>
|
|
125
139
|
{% endblock %}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
{% extends "setup/base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}Configuring Database - Skrift{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block head %}
|
|
6
|
+
<style>
|
|
7
|
+
.configuring-content {
|
|
8
|
+
text-align: center;
|
|
9
|
+
padding: 2rem 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.spinner {
|
|
13
|
+
width: 48px;
|
|
14
|
+
height: 48px;
|
|
15
|
+
border: 4px solid var(--color-border);
|
|
16
|
+
border-top-color: var(--color-primary);
|
|
17
|
+
border-radius: 50%;
|
|
18
|
+
animation: spin 1s linear infinite;
|
|
19
|
+
margin: 0 auto 1.5rem;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.spinner.complete {
|
|
23
|
+
border-color: var(--color-success);
|
|
24
|
+
border-top-color: var(--color-success);
|
|
25
|
+
animation: none;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.spinner.error {
|
|
29
|
+
border-color: var(--color-error);
|
|
30
|
+
border-top-color: var(--color-error);
|
|
31
|
+
animation: none;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@keyframes spin {
|
|
35
|
+
to { transform: rotate(360deg); }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.status-icon {
|
|
39
|
+
width: 48px;
|
|
40
|
+
height: 48px;
|
|
41
|
+
margin: 0 auto 1.5rem;
|
|
42
|
+
font-size: 48px;
|
|
43
|
+
line-height: 1;
|
|
44
|
+
display: none;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.status-icon.complete {
|
|
48
|
+
display: block;
|
|
49
|
+
color: var(--color-success);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.status-icon.error {
|
|
53
|
+
display: block;
|
|
54
|
+
color: var(--color-error);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.status-message {
|
|
58
|
+
font-size: 1.125rem;
|
|
59
|
+
margin-bottom: 0.5rem;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.status-detail {
|
|
63
|
+
color: var(--color-text-muted);
|
|
64
|
+
font-size: 0.875rem;
|
|
65
|
+
min-height: 1.25rem;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.form-actions button:disabled {
|
|
69
|
+
opacity: 0.5;
|
|
70
|
+
cursor: not-allowed;
|
|
71
|
+
}
|
|
72
|
+
</style>
|
|
73
|
+
{% endblock %}
|
|
74
|
+
|
|
75
|
+
{% block content %}
|
|
76
|
+
<h2>Configuring Database</h2>
|
|
77
|
+
<p class="description">Setting up your database and running migrations.</p>
|
|
78
|
+
|
|
79
|
+
<div class="configuring-content">
|
|
80
|
+
<div class="spinner" id="spinner"></div>
|
|
81
|
+
<div class="status-icon" id="status-icon"></div>
|
|
82
|
+
<div class="status-message" id="status-message">Initializing...</div>
|
|
83
|
+
<div class="status-detail" id="status-detail"></div>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div class="form-actions">
|
|
87
|
+
<a href="/setup/database" role="button" class="btn-secondary">Back</a>
|
|
88
|
+
<button type="button" id="next-button" disabled>Continue</button>
|
|
89
|
+
</div>
|
|
90
|
+
{% endblock %}
|
|
91
|
+
|
|
92
|
+
{% block scripts %}
|
|
93
|
+
<script>
|
|
94
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
95
|
+
const spinner = document.getElementById('spinner');
|
|
96
|
+
const statusIcon = document.getElementById('status-icon');
|
|
97
|
+
const statusMessage = document.getElementById('status-message');
|
|
98
|
+
const statusDetail = document.getElementById('status-detail');
|
|
99
|
+
const nextButton = document.getElementById('next-button');
|
|
100
|
+
|
|
101
|
+
let nextStep = 'auth'; // Default fallback
|
|
102
|
+
|
|
103
|
+
function setComplete(step) {
|
|
104
|
+
spinner.style.display = 'none';
|
|
105
|
+
statusIcon.textContent = '\u2713';
|
|
106
|
+
statusIcon.classList.add('complete');
|
|
107
|
+
statusMessage.textContent = 'Database configured successfully!';
|
|
108
|
+
statusDetail.textContent = '';
|
|
109
|
+
nextButton.disabled = false;
|
|
110
|
+
if (step) {
|
|
111
|
+
nextStep = step;
|
|
112
|
+
}
|
|
113
|
+
nextButton.onclick = function() {
|
|
114
|
+
window.location.href = '/setup/' + nextStep;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function setError(message) {
|
|
119
|
+
spinner.style.display = 'none';
|
|
120
|
+
statusIcon.textContent = '\u2717';
|
|
121
|
+
statusIcon.classList.add('error');
|
|
122
|
+
statusMessage.textContent = 'Configuration failed';
|
|
123
|
+
statusDetail.textContent = message || 'An error occurred during setup.';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function setStatus(message, detail) {
|
|
127
|
+
statusMessage.textContent = message;
|
|
128
|
+
statusDetail.textContent = detail || '';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Connect to SSE endpoint
|
|
132
|
+
const eventSource = new EventSource('/setup/configuring/status');
|
|
133
|
+
|
|
134
|
+
eventSource.onmessage = function(event) {
|
|
135
|
+
const data = JSON.parse(event.data);
|
|
136
|
+
|
|
137
|
+
if (data.status === 'running') {
|
|
138
|
+
setStatus(data.message, data.detail);
|
|
139
|
+
} else if (data.status === 'complete') {
|
|
140
|
+
setComplete(data.next_step);
|
|
141
|
+
eventSource.close();
|
|
142
|
+
} else if (data.status === 'error') {
|
|
143
|
+
setError(data.message);
|
|
144
|
+
eventSource.close();
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
eventSource.onerror = function() {
|
|
149
|
+
// Check if we were successful before the connection closed
|
|
150
|
+
if (!nextButton.disabled) {
|
|
151
|
+
return; // Already complete, ignore the error
|
|
152
|
+
}
|
|
153
|
+
setError('Connection lost. Please refresh the page.');
|
|
154
|
+
eventSource.close();
|
|
155
|
+
};
|
|
156
|
+
});
|
|
157
|
+
</script>
|
|
158
|
+
{% endblock %}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: skrift
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0a3
|
|
4
4
|
Summary: A lightweight async Python CMS for crafting modern websites
|
|
5
5
|
Requires-Python: >=3.13
|
|
6
6
|
Requires-Dist: advanced-alchemy>=0.26.0
|
|
@@ -15,6 +15,8 @@ Requires-Dist: pyyaml>=6.0.0
|
|
|
15
15
|
Requires-Dist: ruamel-yaml>=0.18.0
|
|
16
16
|
Requires-Dist: sqlalchemy[asyncio]>=2.0.36
|
|
17
17
|
Requires-Dist: uvicorn>=0.34.0
|
|
18
|
+
Provides-Extra: docs
|
|
19
|
+
Requires-Dist: zensical>=0.0.19; extra == 'docs'
|
|
18
20
|
Description-Content-Type: text/markdown
|
|
19
21
|
|
|
20
22
|
# Skrift
|
|
@@ -1,33 +1,35 @@
|
|
|
1
1
|
skrift/__init__.py,sha256=eXE5PFVkJpH5XsV_ZlrTIeFPUPrmcHYAj4GpRS3R5PY,29
|
|
2
2
|
skrift/__main__.py,sha256=Fs17xxkqTjZpJn9MMC6pzkW4sOzNhV5RGLsA2ONPFww,271
|
|
3
|
-
skrift/alembic.ini,sha256=
|
|
4
|
-
skrift/asgi.py,sha256=
|
|
5
|
-
skrift/cli.py,sha256=
|
|
6
|
-
skrift/config.py,sha256=
|
|
3
|
+
skrift/alembic.ini,sha256=mYguI6CbMCTyfHctsGiTyf9Z5gv21FdeI3qtfgOHO3A,1815
|
|
4
|
+
skrift/asgi.py,sha256=U-9p_JlWlwjrhKJzAlBHpQ3DvTB2uIcvZe3-5Gs87Ec,19248
|
|
5
|
+
skrift/cli.py,sha256=DB_MssHeBAvpm7DVgd3V-BQr5BjBqSO0O_0vm15PZJ8,1941
|
|
6
|
+
skrift/config.py,sha256=7Jqqm9jXff7rIRY83C4jchDVk_1UkpGdKATX3JgX974,7423
|
|
7
7
|
skrift/admin/__init__.py,sha256=x81Cj_ilVmv6slaMl16HHyT_AgrnLxKEWkS0RPa4V9s,289
|
|
8
8
|
skrift/admin/controller.py,sha256=5ZDypvKHXLNDESsKNsdsH2E3Si5OqlpzttFl7Ot8aF0,15651
|
|
9
9
|
skrift/admin/navigation.py,sha256=VwttFoIUIJy5rONKIkJd5w4CNkUpeK22_OfLGHecN34,3382
|
|
10
|
-
skrift/alembic/env.py,sha256=
|
|
10
|
+
skrift/alembic/env.py,sha256=GaQx7D-3f0zVTV4YJNhN0GOfqHL99N4VBfMzjZJz0Bc,2673
|
|
11
11
|
skrift/alembic/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy6HUS9NFvDgl93dMj8,635
|
|
12
12
|
skrift/alembic/versions/20260120_210154_09b0364dbb7b_initial_schema.py,sha256=X6w1vbVKFurhEcblTLGO4Nd_IKkMVtb8TsptV4gpAJ4,2750
|
|
13
13
|
skrift/alembic/versions/20260122_152744_0b7c927d2591_add_roles_and_permissions.py,sha256=yCGjnMTpfSMHVYQCsPTD6wdDI8pJSUtGRESO6bz0KfU,2672
|
|
14
14
|
skrift/alembic/versions/20260122_172836_cdf734a5b847_add_sa_orm_sentinel_column.py,sha256=96TCvvQbYk3RGpZdEQ0x4bIDXxBLc3Gv7VTItTq9T54,850
|
|
15
15
|
skrift/alembic/versions/20260122_175637_a9c55348eae7_remove_page_type_column.py,sha256=XJ55LhwzgN07MAREk6w4Bx2Vu3KvMe_bDkIjgE6EfqM,1223
|
|
16
16
|
skrift/alembic/versions/20260122_200000_add_settings_table.py,sha256=aG4pxp18fwonGAWt7EoT0XaiIEmu7zltSrj8e2a8QZU,1310
|
|
17
|
+
skrift/alembic/versions/20260129_add_oauth_accounts.py,sha256=SJYQl0cfyJnUKLzsbGqu97Kcm7waPhxmpyARJXB3Z1Q,5240
|
|
17
18
|
skrift/auth/__init__.py,sha256=uHMqty3dgDSYlReVT96WhygzH6qNAWSVDWoxumzxmsA,1155
|
|
18
19
|
skrift/auth/guards.py,sha256=QePajHsGnJ4R_hlhzblr5IoAgZcY5jzeZ64bJwDL9hM,4451
|
|
19
20
|
skrift/auth/roles.py,sha256=jpqK4qaavqAhJRxhptm2x5mUb6KkwEALaml8sEH4Sug,2267
|
|
20
21
|
skrift/auth/services.py,sha256=h6GTXdN5UMRYglnaFz4asMoutVkSSAyL3_Vt56N26pA,5441
|
|
21
22
|
skrift/controllers/__init__.py,sha256=bVr0tsSGz7jBi002Lqd1AA1FQd7ZA_IagsqTKpiHiK0,147
|
|
22
|
-
skrift/controllers/auth.py,sha256=
|
|
23
|
+
skrift/controllers/auth.py,sha256=2XQSljDnqdk1NZZTfYLBAqfZb4Ref2p3Uy2maUkKJgY,18881
|
|
23
24
|
skrift/controllers/web.py,sha256=vmoBS1u5G9gCBu65S49yqZn_WBKlmsqlcvX5tYXTKnE,2348
|
|
24
25
|
skrift/db/__init__.py,sha256=uSghyDFT2K4SFiEqUzdjCGzWpS-Oy6Sd1FUappau-v0,52
|
|
25
26
|
skrift/db/base.py,sha256=QJplFj9235kZdScASEpvyNHln6YW2hqbHwJEYZ3OSsc,173
|
|
26
|
-
skrift/db/models/__init__.py,sha256=
|
|
27
|
+
skrift/db/models/__init__.py,sha256=wFF9YWe7rhIWIzRuMtAWVDcZdJsNcKnjfhKbLZw9JVs,341
|
|
28
|
+
skrift/db/models/oauth_account.py,sha256=kLb-1Do8a7tluW3f-uhA_BpgkZ9h10kg_8v0jInDrwY,1216
|
|
27
29
|
skrift/db/models/page.py,sha256=CI5W2sWq0zbKHxfwH5TpmGdJ4zlTicckzpdZtaqjJVE,1019
|
|
28
30
|
skrift/db/models/role.py,sha256=VkwkF3XWemmFtkUpQRk6RTIBfcukrU-0PUPCeo8x834,1768
|
|
29
31
|
skrift/db/models/setting.py,sha256=Am4HTyq2LFR408R6BZb--slys1xM8YLYq9HTq_gybD0,392
|
|
30
|
-
skrift/db/models/user.py,sha256=
|
|
32
|
+
skrift/db/models/user.py,sha256=kkR3h0CphUyvOGJOpiZH6Qp28LqW2OriwVTfybs7vuY,1338
|
|
31
33
|
skrift/db/services/__init__.py,sha256=qAC24IPOYg6AampUWonmLeajljQRMwUw0lgXksQG6Nk,69
|
|
32
34
|
skrift/db/services/page_service.py,sha256=cXJ-urV7LjUDoVsT1P52f6wcOsur3T-pvqKkGG4xlvI,5246
|
|
33
35
|
skrift/db/services/setting_service.py,sha256=eqFxukn8QFrDHQbaBILOLjP1nr34JPIPFT_NnhmeaTY,5613
|
|
@@ -35,12 +37,12 @@ skrift/lib/__init__.py,sha256=PL66bS4_-90zJDMsQSnzqkkxwkO_3aLcYl9l9cDrE2Y,65
|
|
|
35
37
|
skrift/lib/exceptions.py,sha256=p8ceLIQCc7agCwW6-mhBDAuMAMxZDcf9TDLC6PfztU4,5803
|
|
36
38
|
skrift/lib/template.py,sha256=4_urkRfvth75yNeQ5TyGTHvkvs3vVef7TcwZx0k285k,4226
|
|
37
39
|
skrift/setup/__init__.py,sha256=3VjFPMES5y0M5cQ9R4C1xazqiEPEDqTPjX9-3rBMXnA,478
|
|
38
|
-
skrift/setup/config_writer.py,sha256=
|
|
39
|
-
skrift/setup/controller.py,sha256=
|
|
40
|
+
skrift/setup/config_writer.py,sha256=YFH3FVjXN7Rum2fzGVPAQRkjdc9b0bHECDqMKYiEkhg,6347
|
|
41
|
+
skrift/setup/controller.py,sha256=v0Ey8T7ptJ5A3vOqQ1TUAXH1bQwA0288J5uyUWMihsw,34250
|
|
40
42
|
skrift/setup/middleware.py,sha256=Nai8ZG2vHldngmAhq7kWzAwKRNcP5tHKhJHa5dCh404,2941
|
|
41
|
-
skrift/setup/providers.py,sha256=
|
|
42
|
-
skrift/setup/state.py,sha256=
|
|
43
|
-
skrift/static/css/style.css,sha256=
|
|
43
|
+
skrift/setup/providers.py,sha256=0BFKB6168NcmtXxFF6ofHgEDMQD2FbXkexsqrARVtDI,7967
|
|
44
|
+
skrift/setup/state.py,sha256=RMe9LtIjzDoOm9u-Nk5-KAnr_JBiQIjWDpTP9E30ezc,9304
|
|
45
|
+
skrift/static/css/style.css,sha256=sJ7-y8nrUdB5EB5_CyjWo1CTnqmYq0UgMZYMjxw0988,20824
|
|
44
46
|
skrift/templates/base.html,sha256=4bg4s4VdES0dSvhJYLgrfrN26ynqeq1-3jyKPkWWVWk,2065
|
|
45
47
|
skrift/templates/error-404.html,sha256=sJrDaF3Or3Nyki8mxo3wBxLLzgy4wkB9p9wdS8pRA6k,409
|
|
46
48
|
skrift/templates/error-500.html,sha256=MR0wJ1JKLqdmdvsoJbQnZxLkxDPE59LrlbtVPKLM8-A,401
|
|
@@ -54,15 +56,17 @@ skrift/templates/admin/pages/list.html,sha256=LYh1vEwSHkyXihjcy5PIo33iA7UeIxprw4
|
|
|
54
56
|
skrift/templates/admin/settings/site.html,sha256=xHZCZGj9XqyLKCe2eoBlpI3oFIx5VoG_5FP-VAF2mz4,1516
|
|
55
57
|
skrift/templates/admin/users/list.html,sha256=9GWql1la5Srm-OOgooHR9Eouk7ZL66K_FXY6HAAB5lA,1732
|
|
56
58
|
skrift/templates/admin/users/roles.html,sha256=pos-ZM-gXYCN_D8DZpzwEBEm_WvmOKMMPz-3xJqs7nY,1473
|
|
57
|
-
skrift/templates/auth/
|
|
59
|
+
skrift/templates/auth/dummy_login.html,sha256=G2ykWSiOK7XFdr7dzD-7VvKLTXzYT6Lduq5Pc6B21I0,2430
|
|
60
|
+
skrift/templates/auth/login.html,sha256=kBOfKRvKqn0L_POtXgOdl_rzUTNbr9F5NKtqxOMXQdk,7057
|
|
58
61
|
skrift/templates/setup/admin.html,sha256=BSIztZT2iqxVSW23Tfg7ZM51SdGrKL422CR05DPjNic,804
|
|
59
62
|
skrift/templates/setup/auth.html,sha256=0DVL0kU6DlJ2pWe4zwc3DsIVfBAdqpMJxPYvS4zm4OM,4953
|
|
60
63
|
skrift/templates/setup/base.html,sha256=LTXqbnHMvx1wDsxFvo4BSieBPD9pcLMj6NM4ZGzErFM,10372
|
|
61
64
|
skrift/templates/setup/complete.html,sha256=oyT-rYPl0uuyOjPXgNeLr8YoptW9QjHTlScZSViDvTk,630
|
|
65
|
+
skrift/templates/setup/configuring.html,sha256=2KHW9h2BrJgL_kO5IizbAYs4pnFLyRf76IQvEj_cNRM,4607
|
|
62
66
|
skrift/templates/setup/database.html,sha256=gU4-315-QraHa2Eq4Fh3b55QpOM2CkJzh27_Yz13frA,5495
|
|
63
67
|
skrift/templates/setup/restart.html,sha256=GHg31F_e2uLFhWUzJoalk0Y0oYLqsFWyZXWKX3mblbY,1355
|
|
64
68
|
skrift/templates/setup/site.html,sha256=PSOH-q1-ZBl47iSW9-Ad6lEfJn_fzdGD3Pk4vb3xgK4,1680
|
|
65
|
-
skrift-0.1.
|
|
66
|
-
skrift-0.1.
|
|
67
|
-
skrift-0.1.
|
|
68
|
-
skrift-0.1.
|
|
69
|
+
skrift-0.1.0a3.dist-info/METADATA,sha256=BNvNuT2HgfrWYz5Eh5zKmp-9_6JypPmEM8PiALxdeDg,6435
|
|
70
|
+
skrift-0.1.0a3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
71
|
+
skrift-0.1.0a3.dist-info/entry_points.txt,sha256=4AIrmbeWKOdZnvTsKT3US6N3X9rrgk9jEDsYOPEZ1AE,74
|
|
72
|
+
skrift-0.1.0a3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|