stkit 17.0.0__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.
Files changed (128) hide show
  1. stk/__init__.py +19 -0
  2. stk/__main__.py +6 -0
  3. stk/agent_login.py +78 -0
  4. stk/app.py +323 -0
  5. stk/checks.py +634 -0
  6. stk/cli/__init__.py +49 -0
  7. stk/cli/agent.py +267 -0
  8. stk/cli/base.py +79 -0
  9. stk/cli/database.py +199 -0
  10. stk/cli/develop.py +63 -0
  11. stk/cli/doctor.py +262 -0
  12. stk/cli/main.py +101 -0
  13. stk/cli/reports.py +296 -0
  14. stk/cli/smoke.py +399 -0
  15. stk/cli/users.py +259 -0
  16. stk/cli/watch.py +68 -0
  17. stk/extensions.py +29 -0
  18. stk/migrations/__init__.py +130 -0
  19. stk/migrations/env.py +58 -0
  20. stk/migrations/script.py.mako +17 -0
  21. stk/migrations/versions/stk_0001_initial_schema.py +213 -0
  22. stk/portal/views.py +34 -0
  23. stk/public/__init__.py +0 -0
  24. stk/public/models.py +0 -0
  25. stk/public/views.py +344 -0
  26. stk/scaffold/__init__.py +1 -0
  27. stk/scaffold/app/CLAUDE.md +65 -0
  28. stk/scaffold/app/Dockerfile +41 -0
  29. stk/scaffold/app/README.md +16 -0
  30. stk/scaffold/app/__app__/__init__.py +1 -0
  31. stk/scaffold/app/__app__/app.py +8 -0
  32. stk/scaffold/app/__app__/settings.py +7 -0
  33. stk/scaffold/app/claude/skills/stk-api/SKILL.md +52 -0
  34. stk/scaffold/app/claude/skills/stk-blueprint/SKILL.md +48 -0
  35. stk/scaffold/app/claude/skills/stk-deploy/SKILL.md +61 -0
  36. stk/scaffold/app/claude/skills/stk-lessons/SKILL.md +58 -0
  37. stk/scaffold/app/claude/skills/stk-migrate/SKILL.md +96 -0
  38. stk/scaffold/app/docker-compose.yml +88 -0
  39. stk/scaffold/app/dockerignore +13 -0
  40. stk/scaffold/app/env-sample +78 -0
  41. stk/scaffold/app/gitignore +8 -0
  42. stk/scaffold/app/nginx/nginx.conf +46 -0
  43. stk/scaffold/app/nginx/stk.conf +36 -0
  44. stk/scaffold/app/postgres/init-db.sh +8 -0
  45. stk/scaffold/app/pyproject.toml +23 -0
  46. stk/scaffold/app/tests/__init__.py +0 -0
  47. stk/scaffold/app/tests/test_app.py +23 -0
  48. stk/scaffold/generator.py +209 -0
  49. stk/scaffold/templates.py +437 -0
  50. stk/security.py +145 -0
  51. stk/settings.py +147 -0
  52. stk/shell.py +284 -0
  53. stk/static/VERSIONS.txt +4 -0
  54. stk/static/css/app.css +229 -0
  55. stk/static/css/layout.css +330 -0
  56. stk/static/css/vuetify.min.css +6 -0
  57. stk/static/icons/fonts/tabler-icons.woff2 +0 -0
  58. stk/static/icons/tabler-icons.min.css +4 -0
  59. stk/static/img/auth-bg.webp +0 -0
  60. stk/static/img/favicon.ico +0 -0
  61. stk/static/img/stk.svg +1664 -0
  62. stk/static/js/axios.min.js +5 -0
  63. stk/static/js/components/NotificationDropdown.js +156 -0
  64. stk/static/js/components/ThemeSwitcher.js +68 -0
  65. stk/static/js/components/TransitionExpand.js +64 -0
  66. stk/static/js/components/VerticalNavGroup.js +119 -0
  67. stk/static/js/components/VerticalNavLink.js +36 -0
  68. stk/static/js/components/VerticalNavSectionTitle.js +21 -0
  69. stk/static/js/components/index.js +47 -0
  70. stk/static/js/config.js +150 -0
  71. stk/static/js/vue.min.js +13 -0
  72. stk/static/js/vuetify.min.js +3148 -0
  73. stk/static/robots.txt +1 -0
  74. stk/tasks/__init__.py +78 -0
  75. stk/templates/401.html +14 -0
  76. stk/templates/404.html +12 -0
  77. stk/templates/500.html +13 -0
  78. stk/templates/_brand.html +1 -0
  79. stk/templates/auth_layout.html +26 -0
  80. stk/templates/cms/activities.html +135 -0
  81. stk/templates/cms/roles.html +215 -0
  82. stk/templates/cms/users.html +312 -0
  83. stk/templates/dashboard.html +132 -0
  84. stk/templates/index.html +52 -0
  85. stk/templates/layout.html +328 -0
  86. stk/templates/login-layout.html +54 -0
  87. stk/templates/security/_macros.html +39 -0
  88. stk/templates/security/_messages.html +12 -0
  89. stk/templates/security/change_password.html +100 -0
  90. stk/templates/security/confirm_email.html +44 -0
  91. stk/templates/security/email/change_notice.html +4 -0
  92. stk/templates/security/email/change_notice.txt +5 -0
  93. stk/templates/security/email/confirmation_instructions.html +3 -0
  94. stk/templates/security/email/confirmation_instructions.txt +3 -0
  95. stk/templates/security/email/login_instructions.html +5 -0
  96. stk/templates/security/email/login_instructions.txt +5 -0
  97. stk/templates/security/email/reset_instructions.html +1 -0
  98. stk/templates/security/email/reset_instructions.txt +3 -0
  99. stk/templates/security/email/reset_notice.html +1 -0
  100. stk/templates/security/email/reset_notice.txt +1 -0
  101. stk/templates/security/email/welcome.html +7 -0
  102. stk/templates/security/email/welcome.txt +7 -0
  103. stk/templates/security/forgot_password.html +63 -0
  104. stk/templates/security/login_user.html +206 -0
  105. stk/templates/security/mf_recovery.html +67 -0
  106. stk/templates/security/mf_recovery_codes.html +119 -0
  107. stk/templates/security/register_user.html +263 -0
  108. stk/templates/security/reset_password.html +53 -0
  109. stk/templates/security/send_confirmation.html +48 -0
  110. stk/templates/security/send_login.html +39 -0
  111. stk/templates/security/two_factor_select.html +78 -0
  112. stk/templates/security/two_factor_setup.html +166 -0
  113. stk/templates/security/two_factor_verify_code.html +71 -0
  114. stk/templates/security/wan_register.html +179 -0
  115. stk/templates/security/wan_signin.html +123 -0
  116. stk/templates/security/wan_verify.html +109 -0
  117. stk/user/__init__.py +0 -0
  118. stk/user/forms.py +10 -0
  119. stk/user/models.py +308 -0
  120. stk/user/views.py +339 -0
  121. stk/utils/email.py +32 -0
  122. stk/utils/ratelimit.py +67 -0
  123. stk/websocket.py +133 -0
  124. stkit-17.0.0.dist-info/METADATA +135 -0
  125. stkit-17.0.0.dist-info/RECORD +128 -0
  126. stkit-17.0.0.dist-info/WHEEL +4 -0
  127. stkit-17.0.0.dist-info/entry_points.txt +2 -0
  128. stkit-17.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,328 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <meta name="csrf-token" content="{{ csrf_token() }}">
7
+ <title>{% block title %}{{ config.APP_NAME }}{% endblock %}</title>
8
+ <meta name="description" content="A framework for the next decade">
9
+ <meta name="author" content="{{ config.APP_NAME }}">
10
+ <meta name="viewport" content="width=device-width, initial-scale=1">
11
+ <link rel="icon" href="/static/img/favicon.ico" type="image/x-icon">
12
+
13
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/vuetify.min.css') }}">
14
+ <link rel="stylesheet" href="{{ url_for('static', filename='icons/tabler-icons.min.css') }}">
15
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/layout.css') }}">
16
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/app.css') }}">
17
+
18
+ <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;700;800&family=Be+Vietnam+Pro:wght@400;500;700&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
19
+ <style>[v-cloak] { display: none !important; }</style>
20
+
21
+ {% block css %}{% endblock %}
22
+ {% block head_js %}{% endblock %}
23
+ </head>
24
+
25
+ <body class="{% block body_class %} {% endblock %}">
26
+ <v-app id="app" v-cloak>
27
+ <v-layout class="rounded rounded-md">
28
+ <!-- App Bar -->
29
+ <v-app-bar color="surface" density="comfortable" elevation="1">
30
+ {% if current_user.is_authenticated %}
31
+ <v-btn icon variant="text" @click="drawer = !drawer" class="d-md-none">
32
+ <i class="ti ti-menu-2" style="font-size: 24px;"></i>
33
+ </v-btn>
34
+ {% endif %}
35
+
36
+ <v-app-bar-title>
37
+ <a href="/" class="d-flex align-center text-decoration-none">
38
+ {% include "_brand.html" %}
39
+ </a>
40
+ </v-app-bar-title>
41
+
42
+ <v-spacer></v-spacer>
43
+
44
+ {% if not current_user.is_authenticated %}
45
+ <v-btn href="/login" variant="text">Sign in</v-btn>
46
+ {% else %}
47
+ <!-- Theme Switcher -->
48
+ <theme-switcher></theme-switcher>
49
+
50
+ <!-- Notifications -->
51
+ <notification-dropdown
52
+ :notifications="notifications"
53
+ @click="handleNotificationClick"
54
+ @mark-read="markNotificationRead"
55
+ @mark-all-read="markAllNotificationsRead"
56
+ @remove="removeNotification"
57
+ ></notification-dropdown>
58
+
59
+ <!-- Connection Status -->
60
+ <v-tooltip :text="wsConnected ? 'Real-time connected' : 'Reconnecting...'" location="bottom">
61
+ <template v-slot:activator="{ props }">
62
+ <div v-bind="props" :style="{width: '8px', height: '8px', borderRadius: '50%', backgroundColor: wsConnected ? '#4caf50' : '#9e9e9e', transition: 'background-color 0.3s'}" class="mr-1"></div>
63
+ </template>
64
+ </v-tooltip>
65
+
66
+ <!-- User Menu -->
67
+ <v-menu :close-on-content-click="false" location="bottom end" transition="fade-transition">
68
+ <template v-slot:activator="{ props }">
69
+ <v-btn v-bind="props" icon variant="text" size="small" class="ml-1">
70
+ <v-avatar color="primary" size="32">
71
+ <span class="text-body-medium">{{ current_user.name[:2]|upper if current_user.name else 'U' }}</span>
72
+ </v-avatar>
73
+ </v-btn>
74
+ </template>
75
+
76
+ <v-card min-width="280">
77
+ <v-list>
78
+ <v-list-item
79
+ title="{{ current_user.name }}"
80
+ subtitle="{{ current_user.email }}"
81
+ >
82
+ <template v-slot:prepend><i class="ti ti-user mr-3" style="font-size: 20px;"></i></template>
83
+ </v-list-item>
84
+ </v-list>
85
+
86
+ <v-divider></v-divider>
87
+
88
+ <v-list density="compact">
89
+ <v-list-item href="/dashboard">
90
+ <template v-slot:prepend><i class="ti ti-layout-dashboard mr-3" style="font-size: 18px;"></i></template>
91
+ <v-list-item-title>My Account</v-list-item-title>
92
+ </v-list-item>
93
+ <v-list-item href="/change">
94
+ <template v-slot:prepend><i class="ti ti-key mr-3" style="font-size: 18px;"></i></template>
95
+ <v-list-item-title>Change Password</v-list-item-title>
96
+ </v-list-item>
97
+ <v-list-item href="/tf-setup">
98
+ <template v-slot:prepend><i class="ti ti-shield-lock mr-3" style="font-size: 18px;"></i></template>
99
+ <v-list-item-title>Two-Factor Auth</v-list-item-title>
100
+ </v-list-item>
101
+ </v-list>
102
+
103
+ <v-divider></v-divider>
104
+
105
+ <v-list density="compact">
106
+ <v-list-item @click="logout" base-color="error">
107
+ <template v-slot:prepend><i class="ti ti-logout mr-3" style="font-size: 18px;"></i></template>
108
+ <v-list-item-title>Logout</v-list-item-title>
109
+ </v-list-item>
110
+ </v-list>
111
+ </v-card>
112
+ </v-menu>
113
+ {% endif %}
114
+ </v-app-bar>
115
+
116
+ {% if current_user.is_authenticated %}
117
+ <!-- Navigation Drawer (authenticated users only) -->
118
+ <v-navigation-drawer v-model="drawer" :rail="isNavCollapsed" :rail-width="68" width="260">
119
+ <!-- Nav Header -->
120
+ <div class="nav-header">
121
+ <span v-show="!isNavCollapsed" class="app-title">Navigation</span>
122
+ <v-spacer></v-spacer>
123
+ <v-btn
124
+ v-show="!isMobile"
125
+ icon
126
+ variant="text"
127
+ size="small"
128
+ @click="toggleNavCollapse"
129
+ :title="isNavCollapsed ? 'Expand Sidebar' : 'Collapse Sidebar'"
130
+ >
131
+ <i :class="isNavCollapsed ? 'ti ti-layout-sidebar-right' : 'ti ti-layout-sidebar-left-collapse'" style="font-size: 20px;"></i>
132
+ </v-btn>
133
+ </div>
134
+
135
+ <v-divider></v-divider>
136
+
137
+ <!-- Navigation Items -->
138
+ <ul class="nav-items" style="overflow-y: auto; flex: 1;">
139
+ <component
140
+ v-for="item in filteredNavItems"
141
+ :key="item.title || item.heading"
142
+ :is="resolveNavComponent(item)"
143
+ :item="item"
144
+ />
145
+ </ul>
146
+ </v-navigation-drawer>
147
+ {% endif %}
148
+
149
+ <!-- Main Content -->
150
+ <v-main class="d-flex align-center justify-center" style="min-height: 300px;">
151
+ {% block content %}
152
+ {% endblock %}
153
+ </v-main>
154
+
155
+ <!-- Flash messages (plain HTML - renders before Vue mounts) -->
156
+ {% set messages = get_flashed_messages(with_categories=true) %}
157
+ {% if messages %}
158
+ <div id="flash-messages" class="flash-container">
159
+ {% for category, message in messages %}
160
+ <div class="flash-message {{ category if category in ['success', 'warning', 'error', 'info'] else 'primary' }}">
161
+ <span>{{ message }}</span>
162
+ <button class="flash-close" onclick="this.parentElement.remove()">&times;</button>
163
+ </div>
164
+ {% endfor %}
165
+ </div>
166
+ {% endif %}
167
+ </v-layout>
168
+ </v-app>
169
+
170
+ {% if current_user.is_authenticated %}
171
+ <!-- Logout is POST-only with CSRF (quart-security >= 1.4.1) -->
172
+ <form id="logout-form" method="POST" action="/logout" hidden>
173
+ <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
174
+ </form>
175
+ {% endif %}
176
+
177
+ <!-- Flash message auto-hide (outside Vue template) -->
178
+ {% if messages %}
179
+ <script>
180
+ // Auto-hide flash messages after 5 seconds
181
+ setTimeout(function() {
182
+ const flashMessages = document.getElementById('flash-messages');
183
+ if (flashMessages) {
184
+ flashMessages.style.display = 'none';
185
+ }
186
+ }, 5000);
187
+ </script>
188
+ {% endif %}
189
+
190
+ <!-- Core Scripts -->
191
+ <script src="{{ url_for('static', filename='js/vue.min.js') }}"></script>
192
+ <script src="{{ url_for('static', filename='js/config.js') }}"></script>
193
+ <script src="{{ url_for('static', filename='js/vuetify.min.js') }}"></script>
194
+ <script src="{{ url_for('static', filename='js/axios.min.js') }}"></script>
195
+ <script>axios.defaults.headers.common["X-CSRFToken"] = document.querySelector('meta[name="csrf-token"]').content;</script>
196
+
197
+ <!-- Navigation Data: core entries plus the app's `app.navigation` -->
198
+ {% block navigation %}
199
+ <script>const stkNavigation = {{ stk_navigation() | tojson }};</script>
200
+ {% endblock %}
201
+
202
+ <!-- stk Components -->
203
+ <script src="{{ url_for('static', filename='js/components/TransitionExpand.js') }}"></script>
204
+ <script src="{{ url_for('static', filename='js/components/VerticalNavLink.js') }}"></script>
205
+ <script src="{{ url_for('static', filename='js/components/VerticalNavGroup.js') }}"></script>
206
+ <script src="{{ url_for('static', filename='js/components/VerticalNavSectionTitle.js') }}"></script>
207
+ <script src="{{ url_for('static', filename='js/components/NotificationDropdown.js') }}"></script>
208
+ <script src="{{ url_for('static', filename='js/components/ThemeSwitcher.js') }}"></script>
209
+ <script src="{{ url_for('static', filename='js/components/index.js') }}"></script>
210
+
211
+ <!-- Layout Base Script -->
212
+ <script>
213
+ // User roles passed from server
214
+ const userRoles = {{ current_user.roles | map(attribute='name') | list | tojson if current_user.is_authenticated else '[]' | safe }};
215
+
216
+ // Base app data and methods for layout
217
+ const layoutMixin = {
218
+ data() {
219
+ return {
220
+ drawer: window.innerWidth >= 960, // phones start with the menu closed
221
+ isNavCollapsed: false,
222
+ isMobile: window.innerWidth < 960,
223
+ navItems: typeof stkNavigation !== 'undefined' ? stkNavigation : [],
224
+ notifications: [],
225
+ wsConnected: false,
226
+ _ws: null,
227
+ _wsRetry: 1000,
228
+ };
229
+ },
230
+ computed: {
231
+ filteredNavItems() {
232
+ return filterNavByRole(this.navItems, userRoles);
233
+ }
234
+ },
235
+ methods: {
236
+ resolveNavComponent,
237
+ logout() {
238
+ document.getElementById('logout-form').submit();
239
+ },
240
+ toggleNavCollapse() {
241
+ this.isNavCollapsed = !this.isNavCollapsed;
242
+ localStorage.setItem('stk-nav-collapsed', this.isNavCollapsed);
243
+ },
244
+ handleResize() {
245
+ this.isMobile = window.innerWidth < 960;
246
+ if (this.isMobile) {
247
+ this.isNavCollapsed = false;
248
+ }
249
+ },
250
+ // Notification methods
251
+ handleNotificationClick(notification) {
252
+ if (notification.to) {
253
+ window.location.href = notification.to;
254
+ }
255
+ },
256
+ markNotificationRead(id) {
257
+ const notification = this.notifications.find(n => n.id === id);
258
+ if (notification) {
259
+ notification.isSeen = true;
260
+ }
261
+ },
262
+ markAllNotificationsRead() {
263
+ this.notifications.forEach(n => n.isSeen = true);
264
+ },
265
+ removeNotification(id) {
266
+ const index = this.notifications.findIndex(n => n.id === id);
267
+ if (index > -1) {
268
+ this.notifications.splice(index, 1);
269
+ }
270
+ },
271
+ _connectWs() {
272
+ const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
273
+ const ws = new WebSocket(`${proto}//${location.host}/ws`);
274
+ this._ws = ws;
275
+ ws.onopen = () => { this.wsConnected = true; this._wsRetry = 1000; };
276
+ ws.onclose = () => {
277
+ this.wsConnected = false;
278
+ setTimeout(() => this._connectWs(), Math.min(this._wsRetry *= 1.5, 30000));
279
+ };
280
+ ws.onmessage = (e) => { this._onWsMessage(JSON.parse(e.data)); };
281
+ },
282
+ _onWsMessage(msg) {
283
+ // Handle incoming messages — extend this for notifications, live updates, etc.
284
+ if (msg.type === 'notification') {
285
+ this.notifications.unshift(msg);
286
+ }
287
+ }
288
+ },
289
+ mounted() {
290
+ // Restore nav collapsed state
291
+ const savedCollapsed = localStorage.getItem('stk-nav-collapsed');
292
+ if (savedCollapsed !== null && !this.isMobile) {
293
+ this.isNavCollapsed = savedCollapsed === 'true';
294
+ }
295
+
296
+ // Handle resize
297
+ window.addEventListener('resize', this.handleResize);
298
+
299
+ // WebSocket connection (authenticated users only)
300
+ {% if current_user.is_authenticated %}
301
+ this._connectWs();
302
+ {% endif %}
303
+ },
304
+ beforeUnmount() {
305
+ window.removeEventListener('resize', this.handleResize);
306
+ if (this._ws) this._ws.close();
307
+ }
308
+ };
309
+ </script>
310
+
311
+ {% block js %}{% endblock %}
312
+
313
+ <!-- Prefetch same-origin links on hover (Chrome). Skip mutating/auth routes. -->
314
+ <script type="speculationrules">
315
+ {
316
+ "prefetch": [{
317
+ "where": {
318
+ "and": [
319
+ { "href_matches": "/*" },
320
+ { "not": { "href_matches": ["/login/*", "/oauth/*", "/ws"] } }
321
+ ]
322
+ },
323
+ "eagerness": "moderate"
324
+ }]
325
+ }
326
+ </script>
327
+ </body>
328
+ </html>
@@ -0,0 +1,54 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>{{ config.APP_NAME }} - Security</title>
6
+ <meta name="description" content="A framework for the next decade">
7
+ <meta name="author" content="{{ config.APP_NAME }}">
8
+ <meta name="viewport" content="width=device-width, initial-scale=1">
9
+ <link rel="icon" href="/static/img/favicon.ico" type="image/x-icon">
10
+
11
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/vuetify.min.css') }}">
12
+ <link rel="stylesheet" href="{{ url_for('static', filename='icons/tabler-icons.min.css') }}">
13
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/app.css') }}">
14
+
15
+ <style>
16
+ html, body { height: 100%; margin: 0; padding: 0; }
17
+ #app, .v-application, .v-application__wrap { min-height: 100vh; }
18
+ </style>
19
+ {% block css %}{% endblock %}
20
+ {% block head_js %}{% endblock %}
21
+ </head>
22
+ <body>
23
+ <div id="app">
24
+ <v-app v-cloak>
25
+ {% block content %}{% endblock %}
26
+ </v-app>
27
+ </div>
28
+
29
+ <script src="{{ url_for('static', filename='js/vue.min.js') }}"></script>
30
+ <script src="{{ url_for('static', filename='js/vuetify.min.js') }}"></script>
31
+ <script src="{{ url_for('static', filename='js/axios.min.js') }}"></script>
32
+ <script src="{{ url_for('static', filename='js/config.js') }}"></script>
33
+
34
+ <script>
35
+ // Make config available globally for templates
36
+ const delimiters = config.delimiters;
37
+ const vuetifyConfig = config.vuetifyConfig;
38
+
39
+ // Helper for handling API request errors
40
+ function handleRequestError(error) {
41
+ if (error.response && error.response.data) {
42
+ const data = error.response.data;
43
+ if (data.response && data.response.errors) {
44
+ return Object.values(data.response.errors).flat().join(', ');
45
+ }
46
+ if (data.message) return data.message;
47
+ }
48
+ return error.message || 'An error occurred';
49
+ }
50
+ </script>
51
+
52
+ {% block js %}{% endblock %}
53
+ </body>
54
+ </html>
@@ -0,0 +1,39 @@
1
+ {% macro render_field_with_errors(field) %}
2
+ <div class="fs-div" id="{{ field.id|default('fs-field') }}">
3
+ {{ field.label }} {{ field(**kwargs)|safe }}
4
+ {% if field.errors %}
5
+ <ul>
6
+ {% for error in field.errors %}<li class="fs-error-msg">{{ error }}</li>{% endfor %}
7
+ </ul>
8
+ {% endif %}
9
+ </div>
10
+ {% endmacro %}
11
+
12
+ {% macro render_field(field) %}
13
+ <div class="fs-div" id="{{ field.id|default('fs-field') }}">{{ field(**kwargs)|safe }}</div>
14
+ {% endmacro %}
15
+
16
+ {% macro render_field_errors(field) %}
17
+ <div class="fs-div" id="{{ field.id if field else 'fs-field' }}">
18
+ {% if field and field.errors %}
19
+ <ul>
20
+ {% for error in field.errors %}<li class="fs-error-msg">{{ error }}</li>{% endfor %}
21
+ </ul>
22
+ {% endif %}
23
+ </div>
24
+ {% endmacro %}
25
+
26
+ {# render WTForms (>3.0) form level errors #}
27
+ {% macro render_form_errors(form) %}
28
+ {% if form.form_errors %}
29
+ <div class="fs-div" id="fs-form-errors">
30
+ <ul>
31
+ {% for error in form.form_errors %}<li class="fs-error-msg">{{ error }}</li>{% endfor %}
32
+ </ul>
33
+ </div>
34
+ {% endif %}
35
+ {% endmacro %}
36
+
37
+ {% macro prop_next() -%}
38
+ {% if 'next' in request.args %}?next={{ request.args.next|urlencode }}{% endif %}
39
+ {%- endmacro %}
@@ -0,0 +1,12 @@
1
+ {%- with messages = get_flashed_messages(with_categories=true) -%}
2
+ {% if messages %}
3
+ {% for category, message in messages %}
4
+ <v-alert
5
+ type="{{ category if category in ['success', 'warning', 'error', 'info'] else 'info' }}"
6
+ variant="tonal"
7
+ density="compact"
8
+ class="mb-2"
9
+ >{{ message }}</v-alert>
10
+ {% endfor %}
11
+ {% endif %}
12
+ {%- endwith %}
@@ -0,0 +1,100 @@
1
+ {% extends 'layout.html' %}
2
+
3
+ {% block content %}
4
+ <v-container fluid class="pa-4">
5
+ <v-card max-width="500" class="mx-auto">
6
+ <v-card-title class="text-headline-small">
7
+ <i class="ti ti-key mr-2"></i>
8
+ Change Password
9
+ </v-card-title>
10
+ <v-card-subtitle>
11
+ Update your account password
12
+ </v-card-subtitle>
13
+
14
+ <v-card-text>
15
+ {% include "security/_messages.html" %}
16
+
17
+ <v-form
18
+ id="changePasswordForm"
19
+ action="{{ url_for_security('change_password') }}"
20
+ method="POST"
21
+ >
22
+ {{ change_password_form.hidden_tag() }}
23
+
24
+ {% if active_password and current_user.has_usable_password %}
25
+ <v-text-field
26
+ name="password"
27
+ label="Current Password"
28
+ type="password"
29
+ required
30
+ class="mb-2"
31
+ ></v-text-field>
32
+
33
+ {% if change_password_form.password.errors %}
34
+ <v-alert type="error" variant="tonal" density="compact" class="mb-4">
35
+ {{ change_password_form.password.errors[0] }}
36
+ </v-alert>
37
+ {% endif %}
38
+ {% else %}
39
+ <v-alert type="info" variant="tonal" density="compact" class="mb-4">
40
+ Set a password to enable email login for your account.
41
+ </v-alert>
42
+ {% endif %}
43
+
44
+ <v-text-field
45
+ name="new_password"
46
+ label="New Password"
47
+ type="password"
48
+ required
49
+ class="mb-2"
50
+ ></v-text-field>
51
+
52
+ {% if change_password_form.new_password.errors %}
53
+ <v-alert type="error" variant="tonal" density="compact" class="mb-4">
54
+ {{ change_password_form.new_password.errors[0] }}
55
+ </v-alert>
56
+ {% endif %}
57
+
58
+ <v-text-field
59
+ name="new_password_confirm"
60
+ label="Confirm New Password"
61
+ type="password"
62
+ required
63
+ class="mb-4"
64
+ ></v-text-field>
65
+
66
+ {% if change_password_form.new_password_confirm.errors %}
67
+ <v-alert type="error" variant="tonal" density="compact" class="mb-4">
68
+ {{ change_password_form.new_password_confirm.errors[0] }}
69
+ </v-alert>
70
+ {% endif %}
71
+
72
+ <v-btn color="primary" @click="submit">
73
+ Change Password
74
+ </v-btn>
75
+ </v-form>
76
+ </v-card-text>
77
+ </v-card>
78
+ </v-container>
79
+ {% endblock %}
80
+
81
+ {% block js %}
82
+ <script>
83
+ const { createApp } = Vue;
84
+ const { createVuetify } = Vuetify;
85
+ const vuetify = createVuetify(config.vuetifyConfig);
86
+
87
+ const app = createApp({
88
+ mixins: [layoutMixin],
89
+ delimiters: ['${', '}'],
90
+ methods: {
91
+ submit() {
92
+ document.getElementById('changePasswordForm').submit();
93
+ }
94
+ }
95
+ });
96
+
97
+ registerStkComponents(app);
98
+ app.use(vuetify).mount('#app');
99
+ </script>
100
+ {% endblock %}
@@ -0,0 +1,44 @@
1
+ {% extends 'layout.html' %}
2
+
3
+ {% block content %}
4
+ <v-app id="app" class="justify-center align-center min-vh-100 d-flex">
5
+ <v-container class="d-flex align-center justify-center">
6
+ <div class="main">
7
+ <h2>Thank for your registration</h2>
8
+ <p>
9
+ Please confirm your Email to continue.
10
+ </p>
11
+ </div>
12
+ </v-container>
13
+ <v-snackbar absolute button v-model="snackBar">
14
+ ${snackMessage}
15
+ <v-btn text color="primary" @click.native="value = false">Close</v-btn>
16
+ </v-snackbar>
17
+ </v-app>
18
+ {% endblock %}
19
+
20
+ {% block js %}
21
+ <script>
22
+ const {createApp} = Vue;
23
+ const {createVuetify} = Vuetify;
24
+ const vuetify = createVuetify(config.vuetifyConfig);
25
+
26
+ const app = createApp({
27
+ data() {
28
+ return {
29
+ snackBar: false,
30
+ snackMessage: '',
31
+ };
32
+ },
33
+ methods: {
34
+ showSnack(message) {
35
+ this.snackMessage = message;
36
+ this.snackBar = true;
37
+ },
38
+ },
39
+ delimiters: ['${', '}']
40
+ });
41
+
42
+ app.use(vuetify).mount('#app');
43
+ </script>
44
+ {% endblock %}
@@ -0,0 +1,4 @@
1
+ <p>Your password has been changed.</p>
2
+ {% if security.recoverable %}
3
+ <p>If you did not change your password, <a href="{{ url_for_security('forgot_password', _external=True) }}">click here to reset it</a>.</p>
4
+ {% endif %}
@@ -0,0 +1,5 @@
1
+ Your password has been changed
2
+ {% if security.recoverable %}
3
+ If you did not change your password, click the link below to reset it.
4
+ {{ url_for_security('forgot_password', _external=True) }}
5
+ {% endif %}
@@ -0,0 +1,3 @@
1
+ <p>Please confirm your email through the link below:</p>
2
+
3
+ <p><a href="{{ confirmation_link }}">Confirm my account</a></p>
@@ -0,0 +1,3 @@
1
+ Please confirm your email through the link below:
2
+
3
+ {{ confirmation_link }}
@@ -0,0 +1,5 @@
1
+ <p>Welcome {{ user.email }}!</p>
2
+
3
+ <p>You can log into your through the link below:</p>
4
+
5
+ <p><a href="{{ login_link }}">Login now</a></p>
@@ -0,0 +1,5 @@
1
+ Welcome {{ user.email }}!
2
+
3
+ You can log into your through the link below:
4
+
5
+ {{ login_link }}
@@ -0,0 +1 @@
1
+ <p><a href="{{ reset_link }}">Click here to reset your password</a></p>
@@ -0,0 +1,3 @@
1
+ Click the link below to reset your password:
2
+
3
+ {{ reset_link }}
@@ -0,0 +1 @@
1
+ <p>Your password has been reset</p>
@@ -0,0 +1 @@
1
+ Your password has been reset
@@ -0,0 +1,7 @@
1
+ <p>Welcome {{ user.email }}!</p>
2
+
3
+ {% if security.confirmable %}
4
+ <p>You can confirm your email through the link below:</p>
5
+
6
+ <p><a href="{{ confirmation_link }}">Confirm my account</a></p>
7
+ {% endif %}
@@ -0,0 +1,7 @@
1
+ Welcome {{ user.email }}!
2
+
3
+ {% if security.confirmable %}
4
+ You can confirm your email through the link below:
5
+
6
+ {{ confirmation_link }}
7
+ {% endif %}