mirror of
https://github.com/bitinflow/expose.git
synced 2026-03-13 13:35:54 +00:00
wip
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
<script src="https://unpkg.com/tailwindcss-jit-cdn"></script>
|
||||
<script type="tailwind-config">
|
||||
{
|
||||
"darkMode": "media",
|
||||
"darkMode": "class",
|
||||
"theme": {
|
||||
"extend": {
|
||||
"colors": {
|
||||
@@ -119,11 +119,34 @@
|
||||
<div class="pt-8 flex flex-col md:flex-row">
|
||||
<div class="w-full md:w-1/3 flex flex-col mr-5">
|
||||
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
|
||||
<div class="flex items-center pb-4">
|
||||
<div class="flex items-center pb-4 justify-between">
|
||||
<span
|
||||
@click.prevent="useDarkMode = !useDarkMode"
|
||||
class="mr-3 cursor-pointer" id="annual-billing-label">
|
||||
<span class="text-sm font-medium dark:text-gray-200 text-gray-900">Dark-Mode:</span>
|
||||
</span>
|
||||
<button type="button"
|
||||
:class="{
|
||||
'bg-pink-500': useDarkMode,
|
||||
'dark:bg-gray-700 bg-gray-200': ! useDarkMode,
|
||||
}"
|
||||
@click.prevent="useDarkMode = !useDarkMode"
|
||||
class=" relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500" role="switch" aria-checked="false" aria-labelledby="annual-billing-label">
|
||||
<span class="sr-only">Use Dark Mode</span>
|
||||
<!-- Enabled: "translate-x-5", Not Enabled: "translate-x-0" -->
|
||||
<span aria-hidden="true"
|
||||
:class="{
|
||||
'translate-x-5': useDarkMode,
|
||||
'translate-x-0': ! useDarkMode,
|
||||
}"
|
||||
class="pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex items-center pb-4 justify-between">
|
||||
<span
|
||||
@click.prevent="followLogs = !followLogs"
|
||||
class="mr-3 cursor-pointer" id="annual-billing-label">
|
||||
<span class="text-sm font-medium dark:text-gray-200 text-gray-900">Follow requests:</span>
|
||||
<span class="text-sm font-medium dark:text-gray-200 text-gray-900">Follow Requests:</span>
|
||||
</span>
|
||||
<button type="button"
|
||||
:class="{
|
||||
@@ -132,14 +155,14 @@
|
||||
}"
|
||||
@click.prevent="followLogs = !followLogs"
|
||||
class=" relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-pink-500" role="switch" aria-checked="false" aria-labelledby="annual-billing-label">
|
||||
<span class="sr-only">Use setting</span>
|
||||
<!-- Enabled: "translate-x-5", Not Enabled: "translate-x-0" -->
|
||||
<span aria-hidden="true"
|
||||
:class="{
|
||||
<span class="sr-only">Follow Request</span>
|
||||
<!-- Enabled: "translate-x-5", Not Enabled: "translate-x-0" -->
|
||||
<span aria-hidden="true"
|
||||
:class="{
|
||||
'translate-x-5': followLogs,
|
||||
'translate-x-0': ! followLogs,
|
||||
}"
|
||||
class="pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200"></span>
|
||||
class="pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex mb-4">
|
||||
@@ -176,7 +199,7 @@
|
||||
<tbody class="bg-white dark:bg-gray-700">
|
||||
<tr v-for="log in filteredLogs"
|
||||
:class="{
|
||||
'dark:bg-pink-500 dark:bg-opacity-25 bg-gray-100': currentLog === log,
|
||||
'dark:bg-pink-500 dark:bg-opacity-25 bg-pink-500 bg-opacity-25': currentLog === log,
|
||||
'even:bg-gray-50 dark:even:bg-gray-800': currentLog !== log
|
||||
}"
|
||||
@click="setLog(log.id)">
|
||||
@@ -477,6 +500,20 @@
|
||||
maxLogs: {{ max_logs }},
|
||||
highlightNextLog: false,
|
||||
followLogs: true,
|
||||
useDarkMode: window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches,
|
||||
},
|
||||
|
||||
watch: {
|
||||
useDarkMode: {
|
||||
handler: function(value) {
|
||||
if (value) {
|
||||
document.querySelector('html').classList.add('dark')
|
||||
} else {
|
||||
document.querySelector('html').classList.remove('dark')
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
||||
@@ -2,17 +2,37 @@
|
||||
<head>
|
||||
<title>Expose</title>
|
||||
<meta charset="UTF-8">
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tailwindcss/ui@latest/dist/tailwind-ui.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
|
||||
<script src="https://unpkg.com/tailwindcss-jit-cdn"></script>
|
||||
<script type="tailwind-config">
|
||||
{
|
||||
"darkMode": "class",
|
||||
"theme": {
|
||||
"extend": {
|
||||
"colors": {
|
||||
"dark-blue-800": "#ff9900"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style type="postcss">
|
||||
::selection {
|
||||
@apply bg-pink-500 bg-opacity-50;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="min-h-screen bg-white" id="app">
|
||||
<div class="min-h-screen bg-white">
|
||||
<nav class="bg-white border-b border-gray-200">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<div class="flex">
|
||||
<div class="flex items-center">
|
||||
<div class="flex-shrink-0 flex items-center font-bold">
|
||||
Expose
|
||||
<a href="https://expose.beyondco.de" target="_blank" class="inline-flex items-center self-start">
|
||||
<img src="https://beyondco.de/apps/icons/expose.png" class="h-12">
|
||||
<p class="ml-4 font-headline text-lg">Expose</p>
|
||||
</a>
|
||||
</div>
|
||||
<div class="hidden sm:-my-px sm:ml-6 sm:flex">
|
||||
<a href="/users"
|
||||
@@ -60,6 +80,38 @@
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8" id="stats">
|
||||
<div class="py-8">
|
||||
<dl class="mt-5 grid grid-cols-1 gap-5 sm:grid-cols-3">
|
||||
<div class="px-4 py-5 bg-white shadow rounded-lg overflow-hidden sm:p-6">
|
||||
<dt class="text-sm font-medium text-gray-500 truncate">
|
||||
Total Users
|
||||
</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-gray-900">
|
||||
@{ users.total }
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-5 bg-white shadow rounded-lg overflow-hidden sm:p-6">
|
||||
<dt class="text-sm font-medium text-gray-500 truncate">
|
||||
Shared Sites
|
||||
</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-gray-900">
|
||||
@{ sites.length }
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div class="px-4 py-5 bg-white shadow rounded-lg overflow-hidden sm:p-6">
|
||||
<dt class="text-sm font-medium text-gray-500 truncate">
|
||||
Shared TCP connections
|
||||
</dt>
|
||||
<dd class="mt-1 text-3xl font-semibold text-gray-900">
|
||||
@{ tcp_connections.length }
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="py-10">
|
||||
<header>
|
||||
@@ -70,12 +122,60 @@
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8" id="app">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
{% block scripts %}{% endblock %}
|
||||
<script>
|
||||
new Vue({
|
||||
el: '#stats',
|
||||
|
||||
delimiters: ['@{', '}'],
|
||||
|
||||
data: {
|
||||
users: [],
|
||||
sites: [],
|
||||
tcp_connections: [],
|
||||
},
|
||||
|
||||
methods: {
|
||||
getUsers(page) {
|
||||
fetch('/api/users')
|
||||
.then((response) => {
|
||||
return response.json();
|
||||
}).then((data) => {
|
||||
this.users = data.paginated;
|
||||
});
|
||||
},
|
||||
|
||||
getConnections() {
|
||||
fetch('/api/tcp')
|
||||
.then((response) => {
|
||||
return response.json();
|
||||
}).then((data) => {
|
||||
this.tcp_connections = data.tcp_connections;
|
||||
});
|
||||
},
|
||||
|
||||
getSites() {
|
||||
fetch('/api/sites')
|
||||
.then((response) => {
|
||||
return response.json();
|
||||
}).then((data) => {
|
||||
this.sites = data.sites;
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.getUsers();
|
||||
this.getConnections();
|
||||
this.getSites();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
<div class="max-w-lg flex rounded-md shadow-sm">
|
||||
<textarea id="motd" name="motd" rows="3"
|
||||
v-model="configuration.messages.message_of_the_day"
|
||||
class="form-textarea block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5"></textarea>
|
||||
class="form-textarea border-gray-300 rounded-md block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5"></textarea>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500">This message will be shown when a
|
||||
successful connection can be established.</p>
|
||||
@@ -66,7 +66,7 @@
|
||||
<div class="max-w-lg flex rounded-md shadow-sm">
|
||||
<input type="number" id="maximum_connection_length" name="maximum_connection_length"
|
||||
v-model="configuration.maximum_connection_length"
|
||||
class="form-textarea block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5" />
|
||||
class="form-input border-gray-300 rounded-md block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500">The amount of minutes that clients may be connected to this expose server. 0 means there is no limit.</p>
|
||||
</div>
|
||||
@@ -84,7 +84,7 @@
|
||||
name="invalid_auth_token"
|
||||
rows="3"
|
||||
v-model="configuration.messages.invalid_auth_token"
|
||||
class="form-textarea block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5"></textarea>
|
||||
class="form-input border-gray-300 rounded-md block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5"></textarea>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500">This message will be shown when a
|
||||
user tries to connect with an invalid authentication token.</p>
|
||||
@@ -101,7 +101,7 @@
|
||||
<div class="max-w-lg flex rounded-md shadow-sm">
|
||||
<textarea id="subdomain_taken" name="subdomain_taken" rows="3"
|
||||
v-model="configuration.messages.subdomain_taken"
|
||||
class="form-textarea block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5"></textarea>
|
||||
class="border-gray-300 rounded-md block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5"></textarea>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500">This message will be shown when a
|
||||
user tries to connect with an already registered subdomain. You can use
|
||||
@@ -109,6 +109,70 @@
|
||||
a placeholder.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
|
||||
<label for="motd"
|
||||
class="block text-sm font-medium leading-5 text-gray-700 sm:mt-px sm:pt-2">
|
||||
Custom Subdomain Unauthorized
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||
<div class="max-w-lg flex rounded-md shadow-sm">
|
||||
<textarea id="motd" name="motd" rows="3"
|
||||
v-model="configuration.messages.custom_subdomain_unauthorized"
|
||||
class="form-textarea border-gray-300 rounded-md block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5"></textarea>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500">This message will be shown when a user tries to use a custom subdomain, but is not allowed to.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
|
||||
<label for="motd"
|
||||
class="block text-sm font-medium leading-5 text-gray-700 sm:mt-px sm:pt-2">
|
||||
TCP Port Sharing Unauthorized
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||
<div class="max-w-lg flex rounded-md shadow-sm">
|
||||
<textarea id="motd" name="motd" rows="3"
|
||||
v-model="configuration.messages.tcp_port_sharing_unauthorized"
|
||||
class="form-textarea border-gray-300 rounded-md block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5"></textarea>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500">This message will be shown when a user tries to use share a TCP port, but is not allowed to.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
|
||||
<label for="motd"
|
||||
class="block text-sm font-medium leading-5 text-gray-700 sm:mt-px sm:pt-2">
|
||||
No free TCP port available
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||
<div class="max-w-lg flex rounded-md shadow-sm">
|
||||
<textarea id="motd" name="motd" rows="3"
|
||||
v-model="configuration.messages.no_free_tcp_port_available"
|
||||
class="form-textarea border-gray-300 rounded-md block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5"></textarea>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500">This message will be shown when no TCP port can be allocated.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
|
||||
<label for="motd"
|
||||
class="block text-sm font-medium leading-5 text-gray-700 sm:mt-px sm:pt-2">
|
||||
TCP Port Sharing Disabled
|
||||
</label>
|
||||
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||
<div class="max-w-lg flex rounded-md shadow-sm">
|
||||
<textarea id="motd" name="motd" rows="3"
|
||||
v-model="configuration.messages.tcp_port_sharing_disabled"
|
||||
class="form-textarea border-gray-300 rounded-md block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5"></textarea>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-500">This message will be shown when TCP port sharing is not allowed.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||
Subdomain
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||
User
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||
Shared At
|
||||
</th>
|
||||
@@ -28,6 +31,9 @@
|
||||
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500">
|
||||
@{ site.subdomain }.{{ configuration.hostname()}}:{{ configuration.port() }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500">
|
||||
@{ site.user?.name }
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500">
|
||||
@{ site.shared_at }
|
||||
</td>
|
||||
@@ -56,10 +62,18 @@
|
||||
delimiters: ['@{', '}'],
|
||||
|
||||
data: {
|
||||
sites: {{ sites|json_encode|raw }}
|
||||
sites: []
|
||||
},
|
||||
|
||||
methods: {
|
||||
getSites() {
|
||||
fetch('/api/sites')
|
||||
.then((response) => {
|
||||
return response.json();
|
||||
}).then((data) => {
|
||||
this.sites = data.sites;
|
||||
});
|
||||
},
|
||||
disconnectSite(id) {
|
||||
fetch('/api/sites/' + id, {
|
||||
method: 'DELETE',
|
||||
@@ -69,6 +83,10 @@
|
||||
this.sites = data.sites;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.getSites();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
|
||||
<div
|
||||
class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b border-gray-200">
|
||||
<table class="min-w-full" v-if="connections.length > 0">
|
||||
<table class="min-w-full" v-if="tcp_connections.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||
@@ -14,6 +14,9 @@
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||
Expose Port
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||
User
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
|
||||
Shared At
|
||||
</th>
|
||||
@@ -21,13 +24,16 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white">
|
||||
<tr v-for="connection in connections">
|
||||
<tr v-for="connection in tcp_connections">
|
||||
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 font-medium text-gray-900">
|
||||
@{ connection.port }
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500">
|
||||
@{ connection.shared_port }
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500">
|
||||
@{ connection.user?.name }
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap border-b border-gray-200 text-sm leading-5 text-gray-500">
|
||||
@{ connection.shared_at }
|
||||
</td>
|
||||
@@ -54,19 +60,32 @@
|
||||
delimiters: ['@{', '}'],
|
||||
|
||||
data: {
|
||||
connections: {{ connections|json_encode|raw }}
|
||||
tcp_connections: []
|
||||
},
|
||||
|
||||
methods: {
|
||||
getConnections() {
|
||||
fetch('/api/tcp')
|
||||
.then((response) => {
|
||||
return response.json();
|
||||
}).then((data) => {
|
||||
this.tcp_connections = data.tcp_connections;
|
||||
});
|
||||
},
|
||||
|
||||
disconnectConnection(id) {
|
||||
fetch('/api/tcp/' + id, {
|
||||
method: 'DELETE',
|
||||
}).then((response) => {
|
||||
return response.json();
|
||||
}).then((data) => {
|
||||
this.connections = data.connections;
|
||||
this.tcp_connections = data.tcp_connections;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.getConnections();
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -19,8 +19,9 @@
|
||||
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||
<div class="max-w-lg flex rounded-md shadow-sm">
|
||||
<input id="username"
|
||||
type="text"
|
||||
v-model="userForm.name"
|
||||
class="flex-1 form-input block w-full rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5"/>
|
||||
class="flex-1 border-gray-300 block w-full rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -78,6 +79,15 @@
|
||||
</div>
|
||||
</form>
|
||||
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
|
||||
<div class="max-w-lg flex rounded-md shadow-sm mb-8">
|
||||
<input id="search"
|
||||
type="text"
|
||||
v-model="search"
|
||||
autocomplete="off"
|
||||
placeholder="Search users"
|
||||
class="flex-1 border-gray-300 block w-full rounded-md transition duration-150 ease-in-out sm:text-sm sm:leading-5"/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b border-gray-200">
|
||||
<table class="min-w-full" v-if="users.length > 0">
|
||||
@@ -171,6 +181,7 @@
|
||||
delimiters: ['@{', '}'],
|
||||
|
||||
data: {
|
||||
search: '',
|
||||
userForm: {
|
||||
name: '',
|
||||
can_specify_subdomains: true,
|
||||
@@ -181,14 +192,26 @@
|
||||
},
|
||||
|
||||
computed: {
|
||||
total : function() {
|
||||
return this.paginated.total;
|
||||
},
|
||||
users : function() {
|
||||
return this.paginated.users;
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
search(val) {
|
||||
if (val.length < 3) {
|
||||
return;
|
||||
}
|
||||
this.getUsers(1);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
getUsers(page) {
|
||||
fetch('/api/users?page=' + page)
|
||||
fetch('/api/users?search='+this.search+'&page=' + page)
|
||||
.then((response) => {
|
||||
return response.json();
|
||||
}).then((data) => {
|
||||
|
||||
Reference in New Issue
Block a user