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
stk/static/robots.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
User-agent: *
|
stk/tasks/__init__.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Async background tasks.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
from stk.tasks import run_in_background
|
|
5
|
+
|
|
6
|
+
async def my_handler():
|
|
7
|
+
await run_in_background(send_welcome_email(user_id=123))
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def run_in_background(coro):
|
|
17
|
+
"""Fire-and-forget async task with error logging.
|
|
18
|
+
|
|
19
|
+
Use for work that doesn't need a result: emails, webhooks, cache warming, etc.
|
|
20
|
+
The task runs in the current event loop, no extra processes, no Redis.
|
|
21
|
+
"""
|
|
22
|
+
task = asyncio.create_task(coro)
|
|
23
|
+
task.add_done_callback(_log_task_exception)
|
|
24
|
+
return task
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def run_with_session(coro_factory):
|
|
28
|
+
"""Fire-and-forget async task that gets its own DB session.
|
|
29
|
+
|
|
30
|
+
coro_factory is an async callable that takes a session argument:
|
|
31
|
+
async def my_task(session):
|
|
32
|
+
await session.execute(...)
|
|
33
|
+
|
|
34
|
+
await run_with_session(my_task)
|
|
35
|
+
"""
|
|
36
|
+
import stk.extensions as ext
|
|
37
|
+
|
|
38
|
+
async def _wrapper():
|
|
39
|
+
async with ext.async_session_factory() as session:
|
|
40
|
+
try:
|
|
41
|
+
await coro_factory(session)
|
|
42
|
+
await session.commit()
|
|
43
|
+
except Exception:
|
|
44
|
+
await session.rollback()
|
|
45
|
+
raise
|
|
46
|
+
|
|
47
|
+
return await run_in_background(_wrapper())
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _log_task_exception(task: asyncio.Task):
|
|
51
|
+
if not task.cancelled() and task.exception():
|
|
52
|
+
logger.error(
|
|
53
|
+
"Background task failed: %s", task.exception(), exc_info=task.exception()
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
async def cleanup_expired_sessions():
|
|
58
|
+
"""Deactivate expired sessions and delete very old rows."""
|
|
59
|
+
from datetime import datetime, timedelta
|
|
60
|
+
|
|
61
|
+
from sqlalchemy import delete, update
|
|
62
|
+
|
|
63
|
+
import stk.extensions as ext
|
|
64
|
+
from stk.user.models import Session
|
|
65
|
+
|
|
66
|
+
async with ext.async_session_factory() as session:
|
|
67
|
+
lifetime = 3600
|
|
68
|
+
cutoff = datetime.now() - timedelta(seconds=lifetime)
|
|
69
|
+
delete_cutoff = datetime.now() - timedelta(days=30)
|
|
70
|
+
|
|
71
|
+
await session.execute(
|
|
72
|
+
update(Session)
|
|
73
|
+
.where(Session.is_active == True, Session.created_at < cutoff) # noqa: E712
|
|
74
|
+
.values(is_active=False)
|
|
75
|
+
)
|
|
76
|
+
await session.execute(delete(Session).where(Session.created_at < delete_cutoff))
|
|
77
|
+
await session.commit()
|
|
78
|
+
logger.info("Session cleanup complete")
|
stk/templates/401.html
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
{% extends "layout.html" %}
|
|
3
|
+
|
|
4
|
+
{% block page_title %}Unauthorized{% endblock %}
|
|
5
|
+
|
|
6
|
+
{% block content %}
|
|
7
|
+
<div class="jumbotron">
|
|
8
|
+
<div class="text-center">
|
|
9
|
+
<h1>401</h1>
|
|
10
|
+
<p>You are not authorized to see this page.
|
|
11
|
+
</p>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
{% endblock %}
|
stk/templates/404.html
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
|
|
2
|
+
{% block page_title %}{% endblock %}
|
|
3
|
+
|
|
4
|
+
{% block content %}
|
|
5
|
+
|
|
6
|
+
<div style="padding: 50px;margin: 100px auto; text-align: center;">
|
|
7
|
+
<h1 style="font-size: 90px;font-weight: bold;">404</h1>
|
|
8
|
+
<p>Sorry, that page doesn't exist.</p>
|
|
9
|
+
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
{% endblock %}
|
stk/templates/500.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
|
|
2
|
+
{% extends "layout.html" %}
|
|
3
|
+
|
|
4
|
+
{% block page_title %}Server error{% endblock %}
|
|
5
|
+
|
|
6
|
+
{% block content %}
|
|
7
|
+
<div class="jumbotron">
|
|
8
|
+
<div class="text-center">
|
|
9
|
+
<h1>500</h1>
|
|
10
|
+
<p>Sorry, something went wrong on our system. Don't panic, we are fixing it! Please try again later.</p>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
{% endblock %}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<img src="/static/img/stk.svg" alt="{{ config.APP_NAME }}" height="{{ brand_size|default(40) }}" class="logo-adaptive">
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>{{ config.APP_NAME }}</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
|
+
{% block css %}{% endblock %}
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
{% block content %}{% endblock %}
|
|
19
|
+
|
|
20
|
+
<script src="{{ url_for('static', filename='js/vue.min.js') }}"></script>
|
|
21
|
+
<script src="{{ url_for('static', filename='js/vuetify.min.js') }}"></script>
|
|
22
|
+
<script src="{{ url_for('static', filename='js/axios.min.js') }}"></script>
|
|
23
|
+
<script src="{{ url_for('static', filename='js/config.js') }}"></script>
|
|
24
|
+
{% block js %}{% endblock %}
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
{% extends 'layout.html' %}
|
|
2
|
+
{% block css %}
|
|
3
|
+
{% endblock %}
|
|
4
|
+
{% block content %}
|
|
5
|
+
|
|
6
|
+
<v-card class="ma-2 mt-12 w-100 h-100">
|
|
7
|
+
<v-toolbar>
|
|
8
|
+
<v-toolbar-title>Activity Logs</v-toolbar-title>
|
|
9
|
+
<v-spacer></v-spacer>
|
|
10
|
+
</v-toolbar>
|
|
11
|
+
<v-card-text>
|
|
12
|
+
|
|
13
|
+
<v-data-table-server
|
|
14
|
+
:headers="headers"
|
|
15
|
+
:items="items"
|
|
16
|
+
:items-length="itemsLength"
|
|
17
|
+
:page="options.page"
|
|
18
|
+
:items-per-page="options.itemsPerPage"
|
|
19
|
+
@update:options="refresh"
|
|
20
|
+
hover
|
|
21
|
+
>
|
|
22
|
+
<template v-slot:item.data="{ item }">
|
|
23
|
+
<v-btn size="small" variant="text" color="primary" @click="showData(item)">
|
|
24
|
+
View Data
|
|
25
|
+
</v-btn>
|
|
26
|
+
</template>
|
|
27
|
+
</v-data-table-server>
|
|
28
|
+
|
|
29
|
+
</v-card-text>
|
|
30
|
+
</v-card>
|
|
31
|
+
|
|
32
|
+
<!-- Data Dialog -->
|
|
33
|
+
<v-dialog v-model="dataDialog" width="800">
|
|
34
|
+
<v-card v-if="dataDialog" class="pa-4">
|
|
35
|
+
<v-card-title class="d-flex align-center">
|
|
36
|
+
Activity Data
|
|
37
|
+
<v-spacer></v-spacer>
|
|
38
|
+
<v-btn icon="ti ti-x" size="small" variant="text" @click="dataDialog = false"></v-btn>
|
|
39
|
+
</v-card-title>
|
|
40
|
+
|
|
41
|
+
<v-card-text>
|
|
42
|
+
<pre class="pa-4 rounded bg-grey-lighten-4"><code>${JSON.stringify(currentData, null, 2)}</code></pre>
|
|
43
|
+
</v-card-text>
|
|
44
|
+
</v-card>
|
|
45
|
+
</v-dialog>
|
|
46
|
+
|
|
47
|
+
<v-snackbar size="small" class="d-flex" v-model="snackBar" rounded="pill" elevation="25">
|
|
48
|
+
${snackMessage}
|
|
49
|
+
<template v-slot:actions>
|
|
50
|
+
<v-btn @click="snackBar=false" icon="ti ti-x" class="ml-auto" variant="text" size="small"></v-btn>
|
|
51
|
+
</template>
|
|
52
|
+
</v-snackbar>
|
|
53
|
+
|
|
54
|
+
{% endblock %} {% block js %}
|
|
55
|
+
|
|
56
|
+
<script>
|
|
57
|
+
const {createApp} = Vue;
|
|
58
|
+
const {createVuetify} = Vuetify;
|
|
59
|
+
|
|
60
|
+
const vuetify = createVuetify(config.vuetifyConfig);
|
|
61
|
+
|
|
62
|
+
window.app = createApp({
|
|
63
|
+
mixins: [layoutMixin],
|
|
64
|
+
data() {
|
|
65
|
+
return {
|
|
66
|
+
snackBar: false,
|
|
67
|
+
snackMessage: "",
|
|
68
|
+
items: [],
|
|
69
|
+
itemsLength: 0,
|
|
70
|
+
options: {
|
|
71
|
+
page: 1,
|
|
72
|
+
itemsPerPage: 25
|
|
73
|
+
},
|
|
74
|
+
dataDialog: false,
|
|
75
|
+
currentData: null,
|
|
76
|
+
|
|
77
|
+
headers: [
|
|
78
|
+
{title: 'ID', value: 'id', sortable: true},
|
|
79
|
+
{title: 'User', value: 'user', sortable: true},
|
|
80
|
+
{title: 'Action', value: 'action', sortable: true},
|
|
81
|
+
{title: 'Date', value: 'created_at', sortable: true},
|
|
82
|
+
{title: 'Data', value: 'data', sortable: false}
|
|
83
|
+
]
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
mounted() {
|
|
88
|
+
// No refresh on mount
|
|
89
|
+
},
|
|
90
|
+
delimiters: config.delimiters,
|
|
91
|
+
|
|
92
|
+
methods: {
|
|
93
|
+
showSnack(message) {
|
|
94
|
+
this.snackMessage = message;
|
|
95
|
+
this.snackBar = true;
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
refresh(options) {
|
|
99
|
+
// Use options from parameter or component state
|
|
100
|
+
if (options) {
|
|
101
|
+
// Update options with the values from the event
|
|
102
|
+
this.options = {
|
|
103
|
+
...this.options,
|
|
104
|
+
page: options.page,
|
|
105
|
+
itemsPerPage: options.itemsPerPage
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Make API request with pagination parameters
|
|
110
|
+
axios.get(`/api/activities?page=${this.options.page}&per_page=${this.options.itemsPerPage}`)
|
|
111
|
+
.then(res => {
|
|
112
|
+
this.items = res.data.items;
|
|
113
|
+
this.itemsLength = res.data.total;
|
|
114
|
+
// Update itemsPerPage from API response if provided
|
|
115
|
+
if (res.data.perPage) {
|
|
116
|
+
this.options.itemsPerPage = res.data.perPage;
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
.catch(error => {
|
|
120
|
+
console.error('Error fetching activities:', error);
|
|
121
|
+
this.showSnack('Failed to load activities');
|
|
122
|
+
});
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
showData(item) {
|
|
126
|
+
this.currentData = item.data;
|
|
127
|
+
this.dataDialog = true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
registerStkComponents(app);
|
|
133
|
+
app.use(vuetify).mount("#app");
|
|
134
|
+
</script>
|
|
135
|
+
{% endblock %}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
{% extends 'layout.html' %}
|
|
2
|
+
{% block css %}
|
|
3
|
+
{% endblock %}
|
|
4
|
+
{% block content %}
|
|
5
|
+
|
|
6
|
+
<v-card class="ma-2 mt-12 w-100 h-100">
|
|
7
|
+
<v-toolbar>
|
|
8
|
+
<v-toolbar-title>Roles Dashboard</v-toolbar-title>
|
|
9
|
+
<v-spacer></v-spacer>
|
|
10
|
+
</v-toolbar>
|
|
11
|
+
<v-card-text>
|
|
12
|
+
|
|
13
|
+
<v-data-table-server
|
|
14
|
+
:headers="headers"
|
|
15
|
+
:items="items"
|
|
16
|
+
:items-length="itemsLength"
|
|
17
|
+
:page="options.page"
|
|
18
|
+
:items-per-page="options.itemsPerPage"
|
|
19
|
+
@update:options="refresh"
|
|
20
|
+
hover
|
|
21
|
+
>
|
|
22
|
+
<template v-slot:top>
|
|
23
|
+
<v-toolbar color="transparent" class="mb-4" dense elevation="0">
|
|
24
|
+
<v-btn class="ml-auto" @click="createItem" size="small" variant="elevated" color="primary">
|
|
25
|
+
<template v-slot:prepend><i class="ti ti-plus"></i></template>
|
|
26
|
+
Add Role
|
|
27
|
+
</v-btn>
|
|
28
|
+
</v-toolbar>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<template v-slot:item.description="{ item }">
|
|
32
|
+
<span>${item.description}</span>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<template v-slot:item.actions="{ item }">
|
|
36
|
+
<v-icon
|
|
37
|
+
small
|
|
38
|
+
class="mr-2"
|
|
39
|
+
@click="editItem(item)"
|
|
40
|
+
>
|
|
41
|
+
ti ti-pencil
|
|
42
|
+
</v-icon>
|
|
43
|
+
<v-icon
|
|
44
|
+
small
|
|
45
|
+
@click="deleteItem(item)"
|
|
46
|
+
>
|
|
47
|
+
ti ti-trash
|
|
48
|
+
</v-icon>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
</v-data-table-server>
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
</v-card-text>
|
|
55
|
+
</v-card>
|
|
56
|
+
|
|
57
|
+
<!--Edit Dialog-->
|
|
58
|
+
<v-dialog v-model="edialog" width="660">
|
|
59
|
+
<v-card v-if="edialog" class="pa-6">
|
|
60
|
+
<v-sheet class="d-flex justify-end">
|
|
61
|
+
<v-btn @click="edialog=false" size="small" icon="ti ti-x" variant="text"></v-btn>
|
|
62
|
+
</v-sheet>
|
|
63
|
+
<v-card-title>Role Editor</v-card-title>
|
|
64
|
+
|
|
65
|
+
<v-card-text>
|
|
66
|
+
<!-- fields go here -->
|
|
67
|
+
<v-text-field label="Name" v-model="eitem.name"></v-text-field>
|
|
68
|
+
<v-textarea label="Description" v-model="eitem.description" rows="3"></v-textarea>
|
|
69
|
+
</v-card-text>
|
|
70
|
+
|
|
71
|
+
<v-card-actions>
|
|
72
|
+
<v-spacer></v-spacer>
|
|
73
|
+
<v-btn color="primary" variant="elevated" @click="saveItem">Save</v-btn>
|
|
74
|
+
</v-card-actions>
|
|
75
|
+
</v-card>
|
|
76
|
+
|
|
77
|
+
</v-dialog>
|
|
78
|
+
|
|
79
|
+
<v-snackbar size="small" class="d-flex" v-model="snackBar" rounded="pill" elevation="25">
|
|
80
|
+
|
|
81
|
+
${snackMessage}
|
|
82
|
+
<template v-slot:actions>
|
|
83
|
+
<v-btn @click="snackBar=false" icon="ti ti-x" class="ml-auto" variant="text" size="small"></v-btn>
|
|
84
|
+
</template>
|
|
85
|
+
</v-snackbar>
|
|
86
|
+
|
|
87
|
+
{% endblock %} {% block js %}
|
|
88
|
+
|
|
89
|
+
<script>
|
|
90
|
+
const {createApp, toRaw} = Vue;
|
|
91
|
+
const {createVuetify} = Vuetify;
|
|
92
|
+
|
|
93
|
+
const vuetify = createVuetify(config.vuetifyConfig);
|
|
94
|
+
|
|
95
|
+
window.app = createApp({
|
|
96
|
+
mixins: [layoutMixin],
|
|
97
|
+
data() {
|
|
98
|
+
return {
|
|
99
|
+
snackBar: false,
|
|
100
|
+
snackMessage: "",
|
|
101
|
+
items: [],
|
|
102
|
+
itemsLength: 0,
|
|
103
|
+
options: {
|
|
104
|
+
page: 1,
|
|
105
|
+
itemsPerPage: 25
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
headers: [
|
|
109
|
+
{title: 'ID', value: 'id', sortable: true},
|
|
110
|
+
{title: 'Name', value: 'name', sortable: true},
|
|
111
|
+
{title: 'Description', value: 'description', sortable: false},
|
|
112
|
+
{title: 'Actions', value: 'actions', sortable: false}
|
|
113
|
+
],
|
|
114
|
+
edialog: false,
|
|
115
|
+
eitem: {
|
|
116
|
+
id: "",
|
|
117
|
+
name: "",
|
|
118
|
+
description: ""
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
mounted() {
|
|
124
|
+
// No refresh on mount
|
|
125
|
+
},
|
|
126
|
+
delimiters: config.delimiters,
|
|
127
|
+
|
|
128
|
+
methods: {
|
|
129
|
+
showSnack(message) {
|
|
130
|
+
this.snackMessage = message;
|
|
131
|
+
this.snackBar = true;
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
refresh(options) {
|
|
135
|
+
// Use options from parameter or component state
|
|
136
|
+
if (options) {
|
|
137
|
+
// Update options with the values from the event
|
|
138
|
+
this.options = {
|
|
139
|
+
...this.options,
|
|
140
|
+
page: options.page,
|
|
141
|
+
itemsPerPage: options.itemsPerPage
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Make API request with pagination parameters
|
|
146
|
+
axios.get(`/api/roles?page=${this.options.page}&per_page=${this.options.itemsPerPage}`)
|
|
147
|
+
.then(res => {
|
|
148
|
+
this.items = res.data.items;
|
|
149
|
+
this.itemsLength = res.data.total;
|
|
150
|
+
// Update itemsPerPage from API response if provided
|
|
151
|
+
if (res.data.perPage) {
|
|
152
|
+
this.options.itemsPerPage = res.data.perPage;
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
.catch(error => {
|
|
156
|
+
console.error('Error fetching roles:', error);
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
createItem() {
|
|
161
|
+
this.edialog = true;
|
|
162
|
+
this.eitem = {};
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
editItem(item) {
|
|
166
|
+
this.eitem = toRaw(item);
|
|
167
|
+
this.$nextTick(() => {
|
|
168
|
+
this.edialog = true;
|
|
169
|
+
});
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
saveItem() {
|
|
173
|
+
if (this.eitem.id) {
|
|
174
|
+
// edit mode
|
|
175
|
+
axios.post(`/api/role/${this.eitem.id}`, {item: this.eitem})
|
|
176
|
+
.then(res => {
|
|
177
|
+
this.showSnack(res.data?.message);
|
|
178
|
+
this.refresh();
|
|
179
|
+
})
|
|
180
|
+
.catch(err => {
|
|
181
|
+
this.showSnack(err.response?.data);
|
|
182
|
+
});
|
|
183
|
+
} else {
|
|
184
|
+
// create mode
|
|
185
|
+
axios.post('/api/role/', {item: this.eitem})
|
|
186
|
+
.then(res => {
|
|
187
|
+
this.showSnack(res.data?.message);
|
|
188
|
+
this.refresh();
|
|
189
|
+
})
|
|
190
|
+
.catch(err => {
|
|
191
|
+
this.showSnack(err.response?.data);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
this.edialog = false;
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
deleteItem(item) {
|
|
198
|
+
if (confirm('Are you sure?')) {
|
|
199
|
+
axios.delete(`/api/role/${item.id}`)
|
|
200
|
+
.then(res => {
|
|
201
|
+
this.showSnack(res.data?.message);
|
|
202
|
+
this.refresh();
|
|
203
|
+
})
|
|
204
|
+
.catch(err => {
|
|
205
|
+
this.showSnack(err.response?.data);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
registerStkComponents(app);
|
|
213
|
+
app.use(vuetify).mount("#app");
|
|
214
|
+
</script>
|
|
215
|
+
{% endblock %}
|