sub users webui feature

This commit is contained in:
arabcoders
2025-04-10 00:18:15 +03:00
parent e50bf8665f
commit 85b2476ca5
8 changed files with 264 additions and 38 deletions

View File

@@ -3,10 +3,8 @@
<nav class="navbar is-dark mb-4 is-unselectable">
<div class="navbar-brand pl-5">
<NuxtLink class="navbar-item" to="/" @click.native="(e) => changeRoute(e)">
<span class="icon-text">
<span class="icon"><i class="fas fa-home"/></span>
<span>Home</span>
</span>
<span class="icon"><i class="fas fa-home"/></span>
<span>Home</span>
</NuxtLink>
<a class="navbar-item is-hidden-tablet" id="top" href="#bottom">
@@ -23,63 +21,68 @@
<div class="navbar-menu" :class="{ 'is-active': showMenu }">
<div class="navbar-start" v-if="hasAPISettings && !showConnection">
<NuxtLink class="navbar-item" to="/backends" @click.native="(e) => changeRoute(e)">
<span class="icon-text">
<span class="icon"><i class="fas fa-server"/></span>
<span>Backends</span>
</span>
<span class="icon"><i class="fas fa-server"/></span>
<span>Backends</span>
</NuxtLink>
<NuxtLink class="navbar-item" to="/history"
@click.native="(e) => changeRoute(e, () => dEvent('history_main_link_clicked', { 'clear': true }))">
<span class="icon-text">
<span class="icon"><i class="fas fa-history"/></span>
<span>History</span>
</span>
<span class="icon"><i class="fas fa-history"/></span>
<span>History</span>
</NuxtLink>
<NuxtLink class="navbar-item" to="/tasks" @click.native="(e) => changeRoute(e)">
<span class="icon-text">
<span class="icon"><i class="fas fa-tasks"/></span>
<span>Tasks</span>
</span>
<span class="icon"><i class="fas fa-tasks"/></span>
<span>Tasks</span>
</NuxtLink>
<NuxtLink class="navbar-item" to="/env" @click.native="(e) => changeRoute(e)">
<span class="icon-text">
<span class="icon"><i class="fas fa-cogs"/></span>
<span>Env</span>
</span>
<span class="icon"><i class="fas fa-cogs"/></span>
<span>Env</span>
</NuxtLink>
<NuxtLink class="navbar-item" to="/logs" @click.native="(e) => changeRoute(e)">
<span class="icon-text">
<span class="icon"><i class="fas fa-globe"/></span>
<span>Logs</span>
</span>
<span class="icon"><i class="fas fa-globe"/></span>
<span>Logs</span>
</NuxtLink>
<div class="navbar-item has-dropdown">
<a class="navbar-link" @click="(e) => openMenu(e)">
<span class="icon-text">
<span class="icon"><i class="fas fa-ellipsis-vertical"/></span>
<span>More</span>
</span>
<span class="icon"><i class="fas fa-tools"/></span>
<span>Tools</span>
</a>
<div class="navbar-dropdown">
<NuxtLink class="navbar-item" to="/tools/plex_token" @click.native="(e) => changeRoute(e)"
v-if="hasAPISettings">
<span class="icon"><i class="fas fa-key"/></span>
<span>Plex Token</span>
</NuxtLink>
<NuxtLink class="navbar-item" to="/tools/sub_users" @click.native="(e) => changeRoute(e)"
v-if="hasAPISettings">
<span class="icon"><i class="fas fa-users"/></span>
<span>Sub Users</span>
</NuxtLink>
</div>
</div>
<div class="navbar-item has-dropdown">
<a class="navbar-link" @click="(e) => openMenu(e)">
<span class="icon"><i class="fas fa-ellipsis-vertical"/></span>
<span>More</span>
</a>
<div class="navbar-dropdown">
<NuxtLink class="navbar-item" to="/console" @click.native="(e) => changeRoute(e)" v-if="hasAPISettings">
<span class="icon-text">
<span class="icon"><i class="fas fa-terminal"/></span>
<span>Console</span>
</span>
<span class="icon"><i class="fas fa-terminal"/></span>
<span>Console</span>
</NuxtLink>
<NuxtLink class="navbar-item" to="/processes" @click.native="(e) => changeRoute(e)"
v-if="hasAPISettings">
<span class="icon-text">
<span class="icon"><i class="fas fa-microchip"/></span>
<span>Processes</span>
</span>
<span class="icon"><i class="fas fa-microchip"/></span>
<span>Processes</span>
</NuxtLink>
<hr class="navbar-divider">

View File

@@ -25,7 +25,8 @@
"plyr": "^3.7.8",
"vue": "^3.4.21",
"vue-router": "^4.3.0",
"vue-toastification": "^2.0.0-rc.5"
"vue-toastification": "^2.0.0-rc.5",
"vuedraggable": "^4.1.0"
},
"devDependencies": {}
}

View File

@@ -58,7 +58,7 @@
<h1 class="title is-4">Tools</h1>
<ul>
<li>
<NuxtLink :to="`/plex_token`" v-text="'Validate plex token'"/>
<NuxtLink :to="`/tools/plex_token`" v-text="'Validate plex token'"/>
</li>
</ul>
</div>

View File

@@ -0,0 +1,200 @@
<template>
<div>
<div class="columns is-multiline">
<div class="column is-12 is-clearfix is-unselectable">
<span id="env_page_title" class="title is-4">
<span class="icon"><i class="fas fa-users"/></span>
Create Sub-users
</span>
<div class="is-pulled-right">
<div class="field is-grouped">
<p class="control">
<button class="button is-purple" v-tooltip.bottom="'Export Association.'" @click="exportMapping">
<span class="icon"><i class="fas fa-file-export"/></span>
</button>
</p>
<p class="control">
<button class="button is-primary" v-tooltip.bottom="'Create new user assoication.'" @click="addNewUser">
<span class="icon"><i class="fas fa-plus"></i></span>
</button>
</p>
<p class="control">
<button class="button is-info" @click="loadContent" :disabled="isLoading"
:class="{'is-loading':isLoading}">
<span class="icon"><i class="fas fa-sync"/></span>
</button>
</p>
</div>
</div>
<div class="is-hidden-mobile">
<span class="subtitle">
Drag & Drop the relevant users accounts to form association.
</span>
</div>
</div>
<div class="column is-12">
<h2 class="title is-4">Matched users</h2>
</div>
<div class="column is-12" v-for="(group, index) in matchedUsers" :key="index">
<div class="card">
<header class="card-header is-block">
<div class="control has-icons-left">
<input type="text" class="input is-fullwidth" v-model="group.user" required>
<span class="icon is-left"><i class="fas fa-user"/></span>
</div>
</header>
<div class="card-content">
<draggable v-model="group.matched" :group="{ name: 'shared', pull: true, put: true }" animation="150"
item-key="id">
<template #item="{ element }">
<div class="draggable-item">
<span>{{ element.backend }}@{{ element.username }}</span>
</div>
</template>
</draggable>
</div>
</div>
</div>
<div class="column is-12">
<h2 class="title is-4">Users with no association.</h2>
</div>
<div class="column is-12">
<div class="card">
<header class="card-header is-block">
<p class="card-header-title is-text-overflow">Users with no association.</p>
</header>
<div class="card-content">
<draggable v-model="unmatched" :group="{ name: 'shared', pull: true, put: true }" animation="150"
item-key="id">
<template #item="{ element }">
<div class="draggable-item">
<span>{{ element.backend }}@{{ element.username }}</span>
</div>
</template>
</draggable>
</div>
<div v-if="unmatched?.length <1">
<Message message_class="has-background-success-90 has-text-dark" icon="fas fa-check-circle">
<p>
<span class="icon"><i class="fas fa-check"/></span>
<span>All users are associated.</span>
</p>
</Message>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
const data = {
"matched": [
{
"user": "user1",
"matched": [
{
"id": "u1a", "backend": "backend_name1", "username": "user_u1a"
},
{
"id": "u1b", "backend": "backend_name2", "username": "user_u1b"
},
{
"id": "u1c", "backend": "backend_name3", "username": "user_u1c"
}
]
},
{
"user": "user2",
"matched": [
{
"id": "u2a", "backend": "backend_name1", "username": "user_u2a"
},
{
"id": "u2b", "backend": "backend_name2", "username": "user_u2b"
},
{
"id": "u2c", "backend": "backend_name3", "username": "user_u2c"
}
]
},
{
"user": "user3",
"matched": [
{
"id": "u3a", "backend": "backend_name1", "username": "user_u3a"
},
{
"id": "u3b", "backend": "backend_name2", "username": "user_u3b"
},
{
"id": "u3c", "backend": "backend_name3", "username": "user_u3c"
}
]
}
],
"unmatched": [
{
"id": "u4a", "backend": "backend_name1", "username": "user_u4a"
},
{
"id": "u4b", "backend": "backend_name2", "username": "user_u4b"
},
{
"id": "u4c", "backend": "backend_name3", "username": "user_u4c"
}
]
}
const matchedUsers = ref(data.matched)
const unmatched = ref(data.unmatched)
// Function to add a new matched group with a default name
const addNewUser = () => {
const newUserName = 'user ' + (matchedUsers.value.length + 1)
matchedUsers.value.push({
user: newUserName,
matched: []
})
}
</script>
<style scoped>
table {
margin-bottom: 1em;
}
th, td {
padding: 8px;
text-align: left;
}
/* Make containers flex so items wrap side by side */
.users-list,
.unmatched-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
padding: 8px;
border: 1px dashed #ccc;
background-color: #fafafa;
}
/* Let draggable items size to content */
.draggable-item {
display: inline-flex;
align-items: center;
padding: 4px 8px;
background: #f1f1f1;
cursor: move;
border: 1px solid #ddd;
border-radius: 4px;
margin: .25rem;
}
</style>

View File

@@ -0,0 +1,5 @@
import draggable from 'vuedraggable'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.component('draggable', draggable)
})

View File

@@ -49,6 +49,11 @@
"name": "autoscroll",
"description": "Automatically scroll to an element.",
"doc-url": ""
},
{
"name": "sortable",
"description": "Automatically sort.",
"doc-url": ""
}
],
"vue-components": [

View File

@@ -3958,6 +3958,11 @@ smob@^1.0.0:
resolved "https://registry.yarnpkg.com/smob/-/smob-1.5.0.tgz#85d79a1403abf128d24d3ebc1cdc5e1a9548d3ab"
integrity sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==
sortablejs@1.14.0:
version "1.14.0"
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.14.0.tgz#6d2e17ccbdb25f464734df621d4f35d4ab35b3d8"
integrity sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==
source-map-js@^1.0.1, source-map-js@^1.2.0, source-map-js@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
@@ -4539,6 +4544,13 @@ vue@^3.4.21, vue@^3.5.13:
"@vue/server-renderer" "3.5.13"
"@vue/shared" "3.5.13"
vuedraggable@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/vuedraggable/-/vuedraggable-4.1.0.tgz#edece68adb8a4d9e06accff9dfc9040e66852270"
integrity sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==
dependencies:
sortablejs "1.14.0"
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"