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,312 @@
1
+ {% extends 'layout.html' %} {% block css %} {% endblock %}
2
+ {% block content %}
3
+
4
+ <v-card class="ma-2 mt-12 w-100 h-100">
5
+ <v-toolbar>
6
+ <v-toolbar-title>Users Dashboard</v-toolbar-title>
7
+ <v-spacer></v-spacer>
8
+ </v-toolbar>
9
+ <v-card-text>
10
+
11
+ <v-data-table-server
12
+ :items="items"
13
+ :items-length="itemsLength"
14
+ :headers="headers"
15
+ :page="options.page"
16
+ :items-per-page="options.itemsPerPage"
17
+ @update:options="refresh"
18
+ hover
19
+ >
20
+
21
+ <template v-slot:top>
22
+ <v-toolbar class="mb-4" dense elevation="0" color="transparent">
23
+ <v-btn class="ml-auto" @click="createItem" size="small" color="primary" variant="elevated">
24
+ <template v-slot:prepend><i class="ti ti-plus"></i></template>
25
+ Add User
26
+ </v-btn>
27
+
28
+ </v-toolbar>
29
+ </template>
30
+ <template v-slot:item.roles="{ item }">
31
+ <v-chip v-for="role in item.roles" :key="role.id" color="primary" class="mr-2">
32
+ ${role.name}
33
+ </v-chip>
34
+
35
+ </template>
36
+
37
+ <template v-slot:item.active="{ item }">
38
+ <v-avatar size="16" v-if="item.active" color="green"></v-avatar>
39
+ <v-avatar size="16" v-if="!item.active" color="grey"></v-avatar>
40
+
41
+ </template>
42
+
43
+ <template v-slot:item.actions="{ item }">
44
+ <v-icon
45
+ small
46
+ class="mr-2"
47
+ @click="editItem(item)"
48
+ >
49
+ ti ti-pencil
50
+ </v-icon>
51
+ <v-icon
52
+ small
53
+ class="mr-2"
54
+ @click="openResetPassword(item)"
55
+ >
56
+ ti ti-key
57
+ </v-icon>
58
+ <v-icon
59
+ small
60
+ @click="deleteItem(item)"
61
+ >
62
+ ti ti-trash
63
+ </v-icon>
64
+ </template>
65
+
66
+ </v-data-table-server>
67
+
68
+
69
+ </v-card-text>
70
+ </v-card>
71
+
72
+
73
+ <!--Edit Dialog-->
74
+ <v-dialog v-model="edialog" width="660">
75
+ <v-card v-if="edialog">
76
+ <v-toolbar>
77
+ <v-toolbar-title>User Editor</v-toolbar-title>
78
+ <template v-slot:append>
79
+ <v-btn @click="edialog=false" size="small" icon="ti ti-x" variant="text"></v-btn>
80
+ </template>
81
+ </v-toolbar>
82
+
83
+
84
+
85
+ <v-card-text>
86
+ <!-- fields go here -->
87
+ <v-text-field label="Name" v-model="eitem.name"></v-text-field>
88
+ <v-text-field type="email" v-model="eitem.email" label="Email" required></v-text-field>
89
+
90
+ <v-text-field placeholder="Password" type="password" v-model="eitem.password"></v-text-field>
91
+
92
+ <v-select label="Roles"
93
+ item-title="name"
94
+ multiple
95
+ v-model="eitem.roles"
96
+ return-object
97
+ chips
98
+ clearable
99
+ :items="roles"
100
+ ></v-select>
101
+
102
+ <v-switch color="primary" label="Active" v-model="eitem.active"></v-switch>
103
+
104
+ </v-card-text>
105
+
106
+ <v-card-actions>
107
+ <v-spacer></v-spacer>
108
+ <v-btn color="primary" @click="saveItem" variant="elevated">Save</v-btn>
109
+ </v-card-actions>
110
+ </v-card>
111
+
112
+ </v-dialog>
113
+
114
+
115
+ <!--Reset Password Dialog-->
116
+ <v-dialog v-model="resetDialog" width="440">
117
+ <v-card v-if="resetDialog">
118
+ <v-toolbar>
119
+ <v-toolbar-title>Reset Password</v-toolbar-title>
120
+ <template v-slot:append>
121
+ <v-btn @click="resetDialog=false" size="small" icon="ti ti-x" variant="text"></v-btn>
122
+ </template>
123
+ </v-toolbar>
124
+ <v-card-text>
125
+ <p class="mb-4 text-body-medium">Set a new password for <strong>${resetUser.email}</strong></p>
126
+ <v-text-field label="New Password" type="password" v-model="resetPassword" :rules="[v => !!v && v.length >= 12 || 'Min 12 characters']"></v-text-field>
127
+ </v-card-text>
128
+ <v-card-actions>
129
+ <v-spacer></v-spacer>
130
+ <v-btn @click="resetDialog=false" variant="text">Cancel</v-btn>
131
+ <v-btn color="primary" @click="submitResetPassword" variant="elevated" :disabled="!resetPassword || resetPassword.length < 12">Reset</v-btn>
132
+ </v-card-actions>
133
+ </v-card>
134
+ </v-dialog>
135
+
136
+ <v-snackbar size="small" class="d-flex" v-model="snackBar" rounded="pill" elevation="25">
137
+
138
+ ${snackMessage}
139
+ <template v-slot:actions>
140
+ <v-btn @click="snackBar=false" icon="ti ti-x" class="ml-auto" size="small" variant="text"></v-btn>
141
+ </template>
142
+ </v-snackbar>
143
+
144
+
145
+ {% endblock %} {% block js %}
146
+
147
+ <script type="application/json" id="roles-data">
148
+ {{ roles|tojson|safe }}
149
+
150
+ </script>
151
+
152
+ <script>
153
+ const {createApp, toRaw} = Vue;
154
+ const {createVuetify} = Vuetify;
155
+
156
+ const vuetify = createVuetify(config.vuetifyConfig);
157
+
158
+ window.app = createApp({
159
+ mixins: [layoutMixin],
160
+ data() {
161
+ return {
162
+ errors: "",
163
+ snackBar: false,
164
+ snackMessage: "",
165
+ items: [],
166
+ itemsLength: 0,
167
+ options: {
168
+ page: 1,
169
+ itemsPerPage: 25
170
+ },
171
+
172
+ headers: [
173
+ {title: 'ID', value: 'id'},
174
+ {title: 'Name', value: 'name'},
175
+ {title: 'Email', value: 'email'},
176
+ {title: 'Roles', value: 'roles', sortable: false},
177
+ {title: 'Active', value: 'active', sortable: false},
178
+ {title: 'Actions', value: 'actions', sortable: false}
179
+ ],
180
+ edialog: false,
181
+ eitem: {
182
+ id: "",
183
+ name: "",
184
+ email: "",
185
+ password: "",
186
+ roles: [],
187
+ active: true
188
+ },
189
+ roles: JSON.parse(document.querySelector('#roles-data').textContent),
190
+ resetDialog: false,
191
+ resetUser: {},
192
+ resetPassword: ''
193
+ };
194
+ },
195
+
196
+ mounted() {
197
+ // No refresh on mount
198
+ },
199
+ delimiters: config.delimiters,
200
+
201
+ methods: {
202
+
203
+ showSnack(message) {
204
+ this.snackMessage = message;
205
+ this.snackBar = true;
206
+ },
207
+
208
+ refresh(options) {
209
+ // Use options from parameter or component state
210
+ if (options) {
211
+ // Update options with the values from the event
212
+ this.options = {
213
+ ...this.options,
214
+ page: options.page,
215
+ itemsPerPage: options.itemsPerPage
216
+ };
217
+ }
218
+
219
+ // Make API request with pagination parameters
220
+ axios.get(`/api/users?page=${this.options.page}&per_page=${this.options.itemsPerPage}`)
221
+ .then(res => {
222
+ this.items = res.data.items;
223
+ this.itemsLength = res.data.total;
224
+ // Update itemsPerPage from API response if provided
225
+ if (res.data.perPage) {
226
+ this.options.itemsPerPage = res.data.perPage;
227
+ }
228
+ })
229
+ .catch(error => {
230
+ console.error('Error fetching users:', error);
231
+ this.showSnack('Failed to load users');
232
+ });
233
+ },
234
+
235
+ createItem() {
236
+ this.edialog = true;
237
+ this.eitem = {};
238
+
239
+ },
240
+
241
+ editItem(item) {
242
+
243
+ this.eitem = toRaw(item);
244
+
245
+ this.$nextTick(() => {
246
+ this.edialog = true;
247
+ });
248
+
249
+ },
250
+
251
+ saveItem(item) {
252
+ if (this.eitem.id) {
253
+ // edit mode
254
+ axios.post(`/api/user/${this.eitem.id}`, {item: this.eitem})
255
+ .then(res => {
256
+ this.showSnack(res.data?.message);
257
+ this.refresh();
258
+ })
259
+ .catch(err => {
260
+ this.showSnack(err.response?.data);
261
+ });
262
+ } else {
263
+ // create mode
264
+ axios.post('/api/user/', {item: this.eitem})
265
+ .then(res => {
266
+ this.showSnack(res.data?.message);
267
+ this.refresh();
268
+ })
269
+ .catch(err => {
270
+ this.showSnack(err.response?.data);
271
+ });
272
+ }
273
+ this.edialog = false;
274
+ },
275
+
276
+ openResetPassword(item) {
277
+ this.resetUser = item;
278
+ this.resetPassword = '';
279
+ this.resetDialog = true;
280
+ },
281
+
282
+ submitResetPassword() {
283
+ axios.post(`/api/user/${this.resetUser.id}/reset-password`, {password: this.resetPassword})
284
+ .then(res => {
285
+ this.showSnack(res.data?.message);
286
+ this.resetDialog = false;
287
+ })
288
+ .catch(err => {
289
+ this.showSnack(err.response?.data?.message || 'Error resetting password');
290
+ });
291
+ },
292
+
293
+ deleteItem(item) {
294
+ if (confirm('Are you sure?')) {
295
+ axios.delete(`/api/user/${item.id}`)
296
+ .then(res => {
297
+ this.showSnack(res.data?.message);
298
+ this.refresh();
299
+ })
300
+ .catch(err => {
301
+ this.showSnack(err.response?.data);
302
+ });
303
+ }
304
+ }
305
+ }
306
+
307
+ });
308
+
309
+ registerStkComponents(app);
310
+ app.use(vuetify).mount("#app");
311
+ </script>
312
+ {% endblock %}
@@ -0,0 +1,132 @@
1
+ {% extends 'layout.html' %}
2
+ {% block content %}
3
+ <v-container class="py-6">
4
+ <!-- Stats Row -->
5
+ <v-row class="mb-6">
6
+ <v-col cols="12" sm="4">
7
+ <v-card class="pa-4">
8
+ <div class="d-flex align-center">
9
+ <i class="ti ti-users mr-4 text-medium-emphasis" style="font-size: 32px;"></i>
10
+ <div>
11
+ <div class="text-headline-large font-weight-bold">{{ stats.users }}</div>
12
+ <div class="text-medium-emphasis">Users</div>
13
+ </div>
14
+ </div>
15
+ </v-card>
16
+ </v-col>
17
+ <v-col cols="12" sm="4">
18
+ <v-card class="pa-4">
19
+ <div class="d-flex align-center">
20
+ <i class="ti ti-shield mr-4 text-medium-emphasis" style="font-size: 32px;"></i>
21
+ <div>
22
+ <div class="text-headline-large font-weight-bold">{{ stats.roles }}</div>
23
+ <div class="text-medium-emphasis">Roles</div>
24
+ </div>
25
+ </div>
26
+ </v-card>
27
+ </v-col>
28
+ <v-col cols="12" sm="4">
29
+ <v-card class="pa-4">
30
+ <div class="d-flex align-center">
31
+ <i class="ti ti-history mr-4 text-medium-emphasis" style="font-size: 32px;"></i>
32
+ <div>
33
+ <div class="text-headline-large font-weight-bold">{{ stats.activities }}</div>
34
+ <div class="text-medium-emphasis">Activity Logs</div>
35
+ </div>
36
+ </div>
37
+ </v-card>
38
+ </v-col>
39
+ </v-row>
40
+
41
+ <!-- Profile Card -->
42
+ <v-row>
43
+ <v-col cols="12" md="6">
44
+ <v-card>
45
+ <v-card-text class="pa-6">
46
+ <div class="d-flex align-center mb-4">
47
+ <v-avatar color="primary" size="64" class="mr-4">
48
+ <span class="text-headline-small">{{ current_user.name[:2]|upper if current_user.name else 'U' }}</span>
49
+ </v-avatar>
50
+ <div>
51
+ <div class="text-headline-small font-weight-medium">{{ current_user.display_name }}</div>
52
+ <div class="text-medium-emphasis">{{ current_user.email }}</div>
53
+ </div>
54
+ </div>
55
+ <v-divider class="my-4"></v-divider>
56
+ <div class="d-flex align-center text-medium-emphasis mb-2">
57
+ <i class="ti ti-clock mr-2"></i>
58
+ <span>Last login: {{ current_user.last_login_at.strftime('%B %-d, %Y at %-I:%M %p') if current_user.last_login_at else 'Never' }}</span>
59
+ </div>
60
+ <div class="d-flex align-center text-medium-emphasis">
61
+ <i class="ti ti-shield-check mr-2"></i>
62
+ <span>Roles: {% for role in current_user.roles %}{{ role.name }}{% if not loop.last %}, {% endif %}{% endfor %}</span>
63
+ </div>
64
+ </v-card-text>
65
+ <v-card-actions class="pa-6 pt-0">
66
+ <v-btn href="/change" variant="text">
67
+ <template v-slot:prepend><i class="ti ti-key"></i></template>
68
+ Change Password
69
+ </v-btn>
70
+ <v-btn @click="logout" variant="text">
71
+ <template v-slot:prepend><i class="ti ti-logout"></i></template>
72
+ Logout
73
+ </v-btn>
74
+ </v-card-actions>
75
+ </v-card>
76
+ </v-col>
77
+
78
+ <!-- Quick Links Card -->
79
+ <v-col cols="12" md="6">
80
+ <v-card>
81
+ <v-card-title class="pa-6 pb-2">Quick Links</v-card-title>
82
+ <v-list class="pa-2">
83
+ <v-list-item href="/users" class="rounded-lg mb-1">
84
+ <template v-slot:prepend>
85
+ <i class="ti ti-users mr-3 text-medium-emphasis" style="font-size: 22px;"></i>
86
+ </template>
87
+ <v-list-item-title>Manage Users</v-list-item-title>
88
+ <v-list-item-subtitle>Add, edit, or remove users</v-list-item-subtitle>
89
+ </v-list-item>
90
+ <v-list-item href="/roles" class="rounded-lg mb-1">
91
+ <template v-slot:prepend>
92
+ <i class="ti ti-shield mr-3 text-medium-emphasis" style="font-size: 22px;"></i>
93
+ </template>
94
+ <v-list-item-title>Manage Roles</v-list-item-title>
95
+ <v-list-item-subtitle>Configure user permissions</v-list-item-subtitle>
96
+ </v-list-item>
97
+ <v-list-item href="/activities" class="rounded-lg">
98
+ <template v-slot:prepend>
99
+ <i class="ti ti-history mr-3 text-medium-emphasis" style="font-size: 22px;"></i>
100
+ </template>
101
+ <v-list-item-title>Activity Logs</v-list-item-title>
102
+ <v-list-item-subtitle>View system activity</v-list-item-subtitle>
103
+ </v-list-item>
104
+ </v-list>
105
+ </v-card>
106
+ </v-col>
107
+ </v-row>
108
+ </v-container>
109
+ {% endblock %}
110
+
111
+ {% block js %}
112
+ <script>
113
+ const {createApp} = Vue;
114
+ const {createVuetify} = Vuetify;
115
+ const vuetify = createVuetify(config.vuetifyConfig);
116
+
117
+ const app = createApp({
118
+ mixins: [layoutMixin],
119
+ data() {
120
+ return {
121
+ // Page-specific data here
122
+ };
123
+ },
124
+ delimiters: config.delimiters
125
+ });
126
+
127
+ // Register components
128
+ registerStkComponents(app);
129
+
130
+ app.use(vuetify).mount('#app');
131
+ </script>
132
+ {% endblock %}
@@ -0,0 +1,52 @@
1
+ {% extends 'layout.html' %}
2
+ {% block body_class %}home{% endblock %}
3
+ {% block content %}
4
+ <v-container fluid class="fill-height">
5
+ <v-row align="center" justify="center">
6
+ <v-col cols="12" sm="10" md="6" lg="4" class="text-center">
7
+ <div class="mb-6">{% with brand_size = 120 %}{% include "_brand.html" %}{% endwith %}</div>
8
+
9
+ <h1 class="text-headline-large font-weight-bold mb-3">Build faster with stk</h1>
10
+ <p class="text-body-large text-medium-emphasis mb-8">
11
+ A modern Flask framework for building web applications.
12
+ </p>
13
+
14
+ {% if current_user.is_authenticated %}
15
+ <v-btn color="primary" size="large" href="/dashboard">
16
+ <template v-slot:prepend><i class="ti ti-layout-dashboard"></i></template>
17
+ Go to Dashboard
18
+ </v-btn>
19
+ {% else %}
20
+ <v-btn color="primary" size="large" href="/register">
21
+ Get Started
22
+ </v-btn>
23
+ <div class="mt-4 text-body-medium text-medium-emphasis">
24
+ Already have an account? <a href="/login" class="text-primary">Sign in</a>
25
+ </div>
26
+ {% endif %}
27
+ </v-col>
28
+ </v-row>
29
+ </v-container>
30
+
31
+ <div style="position: fixed; bottom: 24px; left: 0; right: 0;" class="text-center text-body-small text-medium-emphasis">
32
+ <a href="https://docs.stk.dev" target="_blank" rel="noopener" class="text-decoration-none">Documentation</a>
33
+ <span class="mx-2">•</span>
34
+ <a href="https://github.com/level09/stk" target="_blank" rel="noopener" class="text-decoration-none">GitHub</a>
35
+ </div>
36
+ {% endblock %}
37
+
38
+ {% block js %}
39
+ <script>
40
+ const {createApp} = Vue;
41
+ const {createVuetify} = Vuetify;
42
+ const vuetify = createVuetify(config.vuetifyConfig);
43
+
44
+ const app = createApp({
45
+ mixins: [layoutMixin],
46
+ delimiters: config.delimiters
47
+ });
48
+
49
+ registerStkComponents(app);
50
+ app.use(vuetify).mount('#app');
51
+ </script>
52
+ {% endblock %}