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.
- stk/__init__.py +19 -0
- stk/__main__.py +6 -0
- stk/agent_login.py +78 -0
- stk/app.py +323 -0
- stk/checks.py +634 -0
- stk/cli/__init__.py +49 -0
- stk/cli/agent.py +267 -0
- stk/cli/base.py +79 -0
- stk/cli/database.py +199 -0
- stk/cli/develop.py +63 -0
- stk/cli/doctor.py +262 -0
- stk/cli/main.py +101 -0
- stk/cli/reports.py +296 -0
- stk/cli/smoke.py +399 -0
- stk/cli/users.py +259 -0
- stk/cli/watch.py +68 -0
- stk/extensions.py +29 -0
- stk/migrations/__init__.py +130 -0
- stk/migrations/env.py +58 -0
- stk/migrations/script.py.mako +17 -0
- stk/migrations/versions/stk_0001_initial_schema.py +213 -0
- stk/portal/views.py +34 -0
- stk/public/__init__.py +0 -0
- stk/public/models.py +0 -0
- stk/public/views.py +344 -0
- stk/scaffold/__init__.py +1 -0
- stk/scaffold/app/CLAUDE.md +65 -0
- stk/scaffold/app/Dockerfile +41 -0
- stk/scaffold/app/README.md +16 -0
- stk/scaffold/app/__app__/__init__.py +1 -0
- stk/scaffold/app/__app__/app.py +8 -0
- stk/scaffold/app/__app__/settings.py +7 -0
- stk/scaffold/app/claude/skills/stk-api/SKILL.md +52 -0
- stk/scaffold/app/claude/skills/stk-blueprint/SKILL.md +48 -0
- stk/scaffold/app/claude/skills/stk-deploy/SKILL.md +61 -0
- stk/scaffold/app/claude/skills/stk-lessons/SKILL.md +58 -0
- stk/scaffold/app/claude/skills/stk-migrate/SKILL.md +96 -0
- stk/scaffold/app/docker-compose.yml +88 -0
- stk/scaffold/app/dockerignore +13 -0
- stk/scaffold/app/env-sample +78 -0
- stk/scaffold/app/gitignore +8 -0
- stk/scaffold/app/nginx/nginx.conf +46 -0
- stk/scaffold/app/nginx/stk.conf +36 -0
- stk/scaffold/app/postgres/init-db.sh +8 -0
- stk/scaffold/app/pyproject.toml +23 -0
- stk/scaffold/app/tests/__init__.py +0 -0
- stk/scaffold/app/tests/test_app.py +23 -0
- stk/scaffold/generator.py +209 -0
- stk/scaffold/templates.py +437 -0
- stk/security.py +145 -0
- stk/settings.py +147 -0
- stk/shell.py +284 -0
- stk/static/VERSIONS.txt +4 -0
- stk/static/css/app.css +229 -0
- stk/static/css/layout.css +330 -0
- stk/static/css/vuetify.min.css +6 -0
- stk/static/icons/fonts/tabler-icons.woff2 +0 -0
- stk/static/icons/tabler-icons.min.css +4 -0
- stk/static/img/auth-bg.webp +0 -0
- stk/static/img/favicon.ico +0 -0
- stk/static/img/stk.svg +1664 -0
- stk/static/js/axios.min.js +5 -0
- stk/static/js/components/NotificationDropdown.js +156 -0
- stk/static/js/components/ThemeSwitcher.js +68 -0
- stk/static/js/components/TransitionExpand.js +64 -0
- stk/static/js/components/VerticalNavGroup.js +119 -0
- stk/static/js/components/VerticalNavLink.js +36 -0
- stk/static/js/components/VerticalNavSectionTitle.js +21 -0
- stk/static/js/components/index.js +47 -0
- stk/static/js/config.js +150 -0
- stk/static/js/vue.min.js +13 -0
- stk/static/js/vuetify.min.js +3148 -0
- stk/static/robots.txt +1 -0
- stk/tasks/__init__.py +78 -0
- stk/templates/401.html +14 -0
- stk/templates/404.html +12 -0
- stk/templates/500.html +13 -0
- stk/templates/_brand.html +1 -0
- stk/templates/auth_layout.html +26 -0
- stk/templates/cms/activities.html +135 -0
- stk/templates/cms/roles.html +215 -0
- stk/templates/cms/users.html +312 -0
- stk/templates/dashboard.html +132 -0
- stk/templates/index.html +52 -0
- stk/templates/layout.html +328 -0
- stk/templates/login-layout.html +54 -0
- stk/templates/security/_macros.html +39 -0
- stk/templates/security/_messages.html +12 -0
- stk/templates/security/change_password.html +100 -0
- stk/templates/security/confirm_email.html +44 -0
- stk/templates/security/email/change_notice.html +4 -0
- stk/templates/security/email/change_notice.txt +5 -0
- stk/templates/security/email/confirmation_instructions.html +3 -0
- stk/templates/security/email/confirmation_instructions.txt +3 -0
- stk/templates/security/email/login_instructions.html +5 -0
- stk/templates/security/email/login_instructions.txt +5 -0
- stk/templates/security/email/reset_instructions.html +1 -0
- stk/templates/security/email/reset_instructions.txt +3 -0
- stk/templates/security/email/reset_notice.html +1 -0
- stk/templates/security/email/reset_notice.txt +1 -0
- stk/templates/security/email/welcome.html +7 -0
- stk/templates/security/email/welcome.txt +7 -0
- stk/templates/security/forgot_password.html +63 -0
- stk/templates/security/login_user.html +206 -0
- stk/templates/security/mf_recovery.html +67 -0
- stk/templates/security/mf_recovery_codes.html +119 -0
- stk/templates/security/register_user.html +263 -0
- stk/templates/security/reset_password.html +53 -0
- stk/templates/security/send_confirmation.html +48 -0
- stk/templates/security/send_login.html +39 -0
- stk/templates/security/two_factor_select.html +78 -0
- stk/templates/security/two_factor_setup.html +166 -0
- stk/templates/security/two_factor_verify_code.html +71 -0
- stk/templates/security/wan_register.html +179 -0
- stk/templates/security/wan_signin.html +123 -0
- stk/templates/security/wan_verify.html +109 -0
- stk/user/__init__.py +0 -0
- stk/user/forms.py +10 -0
- stk/user/models.py +308 -0
- stk/user/views.py +339 -0
- stk/utils/email.py +32 -0
- stk/utils/ratelimit.py +67 -0
- stk/websocket.py +133 -0
- stkit-17.0.0.dist-info/METADATA +135 -0
- stkit-17.0.0.dist-info/RECORD +128 -0
- stkit-17.0.0.dist-info/WHEEL +4 -0
- stkit-17.0.0.dist-info/entry_points.txt +2 -0
- 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 %}
|
stk/templates/index.html
ADDED
|
@@ -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 %}
|