init
This commit is contained in:
@@ -13,35 +13,47 @@
|
|||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-divider/>
|
<v-divider/>
|
||||||
</v-list>
|
</v-list>
|
||||||
|
<UserStats :stats="user_stats"/>
|
||||||
</v-navigation-drawer>
|
</v-navigation-drawer>
|
||||||
<v-navigation-drawer v-model="right_drawer" location="right" temporary>
|
<v-navigation-drawer v-model="right_drawer" location="right" temporary>
|
||||||
<Friend v-if="right_drawer" :active_user="display_user.id" @userSelected="userSelectedUpdated"/>
|
<Friend v-if="right_drawer" :active_user="display_user.id" @userSelected="userSelectedUpdated"/>
|
||||||
</v-navigation-drawer>
|
</v-navigation-drawer>
|
||||||
<v-app-bar>
|
<v-app-bar app>
|
||||||
<v-app-bar-title><v-btn variant="plain" href="/">Oxpanel</v-btn></v-app-bar-title>
|
<v-app-bar-title>
|
||||||
<v-spacer/>
|
<v-row align="center" justify="start" style="max-width: 100%">
|
||||||
<v-menu>
|
<v-col class="v-col-1 text-center">
|
||||||
<template v-slot:activator="{ props }">
|
<v-btn variant="plain" href="/">Oxpanel</v-btn>
|
||||||
<v-btn v-bind="props">Manage</v-btn>
|
</v-col>
|
||||||
</template>
|
<v-col class="v-col-3">
|
||||||
<v-list>
|
<v-text-field flat solo-inverted hide-details prepend-inner-icon="mdi-magnify" label="Search" v-model="filters.search" clearable @click:clear="filters.search = ''"/>
|
||||||
<v-list-item href="/password_change/" title="Change password"/>
|
</v-col>
|
||||||
<v-list-item>
|
</v-row>
|
||||||
<v-list-item-action>
|
</v-app-bar-title>
|
||||||
<form action="/logout/" method="post">
|
<template v-slot:append>
|
||||||
<input type="hidden" name="csrfmiddlewaretoken" :value="Cookies.get('csrftoken')">
|
<v-menu>
|
||||||
<button>Disconnect</button>
|
<template v-slot:activator="{ props }">
|
||||||
</form>
|
<v-btn v-bind="props">Manage</v-btn>
|
||||||
</v-list-item-action>
|
</template>
|
||||||
</v-list-item>
|
<v-list>
|
||||||
</v-list>
|
<v-list-item href="/password_change/" title="Change password"/>
|
||||||
</v-menu>
|
<v-list-item>
|
||||||
<v-btn
|
<v-list-item-action>
|
||||||
@click="right_drawer = !right_drawer"
|
<form action="/logout/" method="post">
|
||||||
prepend-icon="mdi-account-group"
|
<input type="hidden" name="csrfmiddlewaretoken" :value="Cookies.get('csrftoken')">
|
||||||
:color="display_user.id === user.id ? 'green':'orange'"
|
<button>Disconnect</button>
|
||||||
:text="display_user.username"
|
</form>
|
||||||
/>
|
</v-list-item-action>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
<v-btn
|
||||||
|
@click="right_drawer = !right_drawer"
|
||||||
|
prepend-icon="mdi-account-group"
|
||||||
|
:color="display_user.id === user.id ? 'green':'orange'"
|
||||||
|
:text="display_user.username"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
</v-app-bar>
|
</v-app-bar>
|
||||||
<v-main>
|
<v-main>
|
||||||
<TorrentList :loading="loading_torrents" :torrents="torrents" :delete_disabled="user.id !== display_user.id"/>
|
<TorrentList :loading="loading_torrents" :torrents="torrents" :delete_disabled="user.id !== display_user.id"/>
|
||||||
@@ -54,6 +66,7 @@ import Cookies from "js-cookie";
|
|||||||
import TorrentList from "@/components/torrent/TorrentList.vue";
|
import TorrentList from "@/components/torrent/TorrentList.vue";
|
||||||
import UploadForm from "@/components/torrent/UploadForm.vue";
|
import UploadForm from "@/components/torrent/UploadForm.vue";
|
||||||
import Friend from "@/components/auth/Friend.vue";
|
import Friend from "@/components/auth/Friend.vue";
|
||||||
|
import UserStats from "@/components/torrent/UserStats.vue";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -68,7 +81,32 @@ export default {
|
|||||||
},
|
},
|
||||||
loading_torrents: false,
|
loading_torrents: false,
|
||||||
filters: {},
|
filters: {},
|
||||||
torrents: []
|
torrents: [],
|
||||||
|
user_stats: {
|
||||||
|
"torrents_size": 0,
|
||||||
|
"torrents_len": 0,
|
||||||
|
"torrent_len_shared": 0,
|
||||||
|
"torrents_total_len": 0,
|
||||||
|
"user_max_size": 0,
|
||||||
|
"user_usage_percent": 0,
|
||||||
|
"disk_total": 0,
|
||||||
|
"disk_used": 0,
|
||||||
|
"disk_free": 0,
|
||||||
|
"disk_usage_percent": 0,
|
||||||
|
},
|
||||||
|
filter_timer: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
filters: {
|
||||||
|
handler(){
|
||||||
|
// console.log(JSON.stringify(this.filters));
|
||||||
|
clearTimeout(this.filter_timer);
|
||||||
|
this.filter_timer = setTimeout(() => {
|
||||||
|
this.fetchTorrents();
|
||||||
|
}, 200)
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted(){
|
async mounted(){
|
||||||
@@ -83,17 +121,25 @@ export default {
|
|||||||
break;
|
break;
|
||||||
case "add_torrent":
|
case "add_torrent":
|
||||||
this.fetchTorrent(message.torrent_id);
|
this.fetchTorrent(message.torrent_id);
|
||||||
|
this.fetchUserStats();
|
||||||
break;
|
break;
|
||||||
case "remove_torrent":
|
case "remove_torrent":
|
||||||
this.removeTorrent(message.torrent_id);
|
this.removeTorrent(message.torrent_id);
|
||||||
|
this.fetchUserStats();
|
||||||
break;
|
break;
|
||||||
case "update_torrent":
|
case "update_torrent":
|
||||||
this.updateTorrent(message.torrent_id, message.updated_fields)
|
this.updateTorrent(message.torrent_id, message.updated_fields)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
await this.fetchTorrents();
|
await this.fetchTorrents();
|
||||||
|
await this.fetchUserStats();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async fetchUserStats(){
|
||||||
|
let response = await fetch("/api/users/user_stats/");
|
||||||
|
this.user_stats = await response.json();
|
||||||
|
},
|
||||||
torrentAdded(torrent){
|
torrentAdded(torrent){
|
||||||
if(!(torrent.id in this.torrentsAsObject)){
|
if(!(torrent.id in this.torrentsAsObject)){
|
||||||
this.torrents.unshift(torrent);
|
this.torrents.unshift(torrent);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-list density="compact">
|
<v-list density="compact">
|
||||||
<div v-if="loading">
|
<div v-if="loading" class="text-center">
|
||||||
<v-progress-circular indeterminate/>
|
<v-progress-circular indeterminate/>
|
||||||
Loading files ...
|
Loading files ...
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
45
app/frontend/src/components/torrent/UserStats.vue
Normal file
45
app/frontend/src/components/torrent/UserStats.vue
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item
|
||||||
|
prepend-icon="mdi-file-multiple"
|
||||||
|
:title="`${stats.torrents_total_len} torrent${stats.torrents_total_len !== 1 ? 's':''}`"
|
||||||
|
class="text-center"
|
||||||
|
/>
|
||||||
|
<v-list-item prepend-icon="mdi-harddisk" :title="`${fs_format(stats.torrents_size)}/${fs_format(stats.user_max_size)}`" class="text-center">
|
||||||
|
<v-progress-linear :model-value="stats.user_usage_percent" height="10" :color="getProgressColor(stats.user_usage_percent)"/>
|
||||||
|
</v-list-item>
|
||||||
|
<!-- <v-list-item prepend-icon="mdi-server" :title="`Server usage ${stats.disk_usage_percent.toFixed(2)}%`" class="text-center">-->
|
||||||
|
<!-- <template v-slot:subtitle>-->
|
||||||
|
<!-- <v-progress-linear :model-value="stats.disk_usage_percent" height="10" :color="getProgressColor(stats.disk_usage_percent)"/>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
<!-- </v-list-item>-->
|
||||||
|
<!-- <v-list-item prepend-icon="mdi-server" :title="`${stats.disk_used}/${stats.disk_total}`"/>-->
|
||||||
|
</v-list>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {fs_format} from "@/plugins/utils.js";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
stats: {
|
||||||
|
required: true,
|
||||||
|
type: Object
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data(){
|
||||||
|
return {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getProgressColor(value){
|
||||||
|
if(value < 50) return "green";
|
||||||
|
if(value < 75) return "orange";
|
||||||
|
return "red";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -116,6 +116,10 @@ class TorrentViewSet(mixins.CreateModelMixin,
|
|||||||
sub = SharedUser.objects.filter(torrent_id=OuterRef("pk"), user_id=user_id).values("date_created")
|
sub = SharedUser.objects.filter(torrent_id=OuterRef("pk"), user_id=user_id).values("date_created")
|
||||||
qs = qs.annotate(last_date=Coalesce(sub, "date_created")).order_by("-last_date")
|
qs = qs.annotate(last_date=Coalesce(sub, "date_created")).order_by("-last_date")
|
||||||
|
|
||||||
|
search = self.request.query_params.get("search", None)
|
||||||
|
if search:
|
||||||
|
qs = qs.filter(name__icontains=search)
|
||||||
|
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
|
|||||||
@@ -32,14 +32,6 @@ class UserAdmin(BaseUserAdmin):
|
|||||||
return filesizeformat(obj.size_used)
|
return filesizeformat(obj.size_used)
|
||||||
size_used.short_description = "Size used"
|
size_used.short_description = "Size used"
|
||||||
|
|
||||||
def save_formset(self, request, form, formset, change):
|
|
||||||
print("save_formset")
|
|
||||||
return super().save_formset(request, form, formset, change)
|
|
||||||
|
|
||||||
def save_model(self, request, obj, form, change):
|
|
||||||
print("save_model")
|
|
||||||
return super().save_model(request, obj, form, change)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Invitation)
|
@admin.register(Invitation)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from rest_framework.viewsets import ModelViewSet, GenericViewSet
|
|||||||
from rest_framework import mixins
|
from rest_framework import mixins
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
import shutil
|
||||||
|
|
||||||
from .models import User, FriendRequest, Invitation
|
from .models import User, FriendRequest, Invitation
|
||||||
from .forms import RegisterForm
|
from .forms import RegisterForm
|
||||||
@@ -92,6 +93,29 @@ class UserViewSet(mixins.RetrieveModelMixin,
|
|||||||
return Response({"success": True, "message": f"The friend {friend.username} successfully removed"})
|
return Response({"success": True, "message": f"The friend {friend.username} successfully removed"})
|
||||||
return Response({"success": False, "message": f"error"})
|
return Response({"success": False, "message": f"error"})
|
||||||
|
|
||||||
|
@action(methods=["get"], detail=False)
|
||||||
|
def user_stats(self, request):
|
||||||
|
stats = User.objects.filter(id=request.user.id).aggregate(
|
||||||
|
total_size=Sum("torrents__size"),
|
||||||
|
total_torrent=Count("torrents"),
|
||||||
|
total_shared_torrent=Count("torrents_shares")
|
||||||
|
)
|
||||||
|
|
||||||
|
disk_usage = shutil.disk_usage("/")
|
||||||
|
|
||||||
|
return Response({
|
||||||
|
"torrents_size": stats["total_size"],
|
||||||
|
"torrents_len": stats["total_torrent"],
|
||||||
|
"torrent_len_shared": stats["total_shared_torrent"],
|
||||||
|
"torrents_total_len": stats["total_torrent"] + stats["total_shared_torrent"],
|
||||||
|
"user_max_size": request.user.max_size,
|
||||||
|
"user_usage_percent": (stats["total_size"] / request.user.max_size) * 100,
|
||||||
|
"disk_total": disk_usage.total,
|
||||||
|
"disk_used": disk_usage.used,
|
||||||
|
"disk_free": disk_usage.free,
|
||||||
|
"disk_usage_percent": (disk_usage.used / disk_usage.total) * 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
class FriendRequestViewSet(mixins.ListModelMixin,
|
class FriendRequestViewSet(mixins.ListModelMixin,
|
||||||
mixins.DestroyModelMixin,
|
mixins.DestroyModelMixin,
|
||||||
|
|||||||
Reference in New Issue
Block a user