feat: user quota display (#12)
parent
53df153a70
commit
ebb53e715b
|
@ -75,6 +75,7 @@ func addUserFlags(flags *pflag.FlagSet) {
|
|||
flags.StringSlice("commands", nil, "a list of the commands a user can execute")
|
||||
flags.String("scope", ".", "scope for users")
|
||||
flags.String("trashDir", "", "trash directory path for users")
|
||||
flags.String("quotaFile", "", "path to file with quota data")
|
||||
flags.String("locale", "en", "locale for users")
|
||||
flags.String("viewMode", string(users.ListViewMode), "view mode for users")
|
||||
flags.Bool("singleClick", false, "use single clicks only")
|
||||
|
@ -96,6 +97,8 @@ func getUserDefaults(flags *pflag.FlagSet, defaults *settings.UserDefaults, all
|
|||
defaults.Scope = mustGetString(flags, flag.Name)
|
||||
case "trashDir":
|
||||
defaults.TrashDir = mustGetString(flags, flag.Name)
|
||||
case "quotaFile":
|
||||
defaults.QuotaFile = mustGetString(flags, flag.Name)
|
||||
case "locale":
|
||||
defaults.Locale = mustGetString(flags, flag.Name)
|
||||
case "viewMode":
|
||||
|
|
|
@ -49,3 +49,9 @@ export async function remove(id) {
|
|||
throw new Error(res.status);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getQuota() {
|
||||
return await fetchJSON(`/api/quota`, {
|
||||
method: "GET",
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
<template>
|
||||
<div id="quota">
|
||||
<div>
|
||||
<label>{{ $t("sidebar.quota.space") }}</label>
|
||||
<div class="bar" :title="spaceUsageTitle">
|
||||
<div class="progress" :style="{ width: spaceProgress + '%' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label>{{ $t("sidebar.quota.inodes") }}</label>
|
||||
<div class="bar" :title="inodeUsageTitle">
|
||||
<div class="progress" :style="{ width: inodeProgress + '%' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import filesize from "filesize";
|
||||
import { mapState } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "quota",
|
||||
computed: {
|
||||
...mapState("quota", {
|
||||
inodes: (state) => state.inodes,
|
||||
space: (state) => state.space,
|
||||
}),
|
||||
loaded() {
|
||||
return this.inodes !== null && this.space !== null;
|
||||
},
|
||||
spaceProgress() {
|
||||
if (!this.loaded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.progress(this.space);
|
||||
},
|
||||
inodeProgress() {
|
||||
if (!this.loaded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.progress(this.inodes);
|
||||
},
|
||||
spaceUsageTitle() {
|
||||
if (this.space === null) {
|
||||
return "- / -";
|
||||
} else {
|
||||
return filesize(this.space.usage) + " / " + filesize(this.space.quota);
|
||||
}
|
||||
},
|
||||
inodeUsageTitle() {
|
||||
if (this.inodes === null) {
|
||||
return "- / -";
|
||||
} else {
|
||||
return this.inodes.usage + " / " + this.inodes.quota;
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch("quota/fetch");
|
||||
},
|
||||
methods: {
|
||||
progress(metric) {
|
||||
let prc = (metric.usage / metric.quota) * 100;
|
||||
return Math.min(prc, 100);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -45,6 +45,8 @@
|
|||
</router-link>
|
||||
</div>
|
||||
|
||||
<quota v-if="quotaExists"></quota>
|
||||
|
||||
<div>
|
||||
<router-link
|
||||
class="action"
|
||||
|
@ -117,11 +119,13 @@
|
|||
<script>
|
||||
import { mapState, mapGetters } from "vuex";
|
||||
import * as auth from "@/utils/auth";
|
||||
import Quota from "@/components/Quota";
|
||||
import {
|
||||
version,
|
||||
signup,
|
||||
disableExternal,
|
||||
trashDir,
|
||||
quotaExists,
|
||||
noAuth,
|
||||
authMethod,
|
||||
authLogoutURL,
|
||||
|
@ -129,6 +133,7 @@ import {
|
|||
|
||||
export default {
|
||||
name: "sidebar",
|
||||
components: { Quota },
|
||||
computed: {
|
||||
...mapState(["user"]),
|
||||
...mapGetters(["isLogged"]),
|
||||
|
@ -139,6 +144,7 @@ export default {
|
|||
version: () => version,
|
||||
disableExternal: () => disableExternal,
|
||||
trashDir: () => trashDir,
|
||||
quotaExists: () => quotaExists,
|
||||
noAuth: () => noAuth,
|
||||
authMethod: () => authMethod,
|
||||
authLogoutURL: () => authLogoutURL,
|
||||
|
|
|
@ -143,4 +143,29 @@ main {
|
|||
|
||||
.break-word {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
#quota {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
#quota > div + div {
|
||||
margin-top: .5em;
|
||||
}
|
||||
|
||||
#quota label {
|
||||
color: #546E7A;
|
||||
}
|
||||
|
||||
#quota .bar {
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
background-color: rgba(0,0,0, 0.1);
|
||||
}
|
||||
|
||||
#quota .bar .progress {
|
||||
width: 0;
|
||||
height: 100%;
|
||||
transition: .2s ease width;
|
||||
background-color: #40c4ff;
|
||||
}
|
|
@ -267,7 +267,11 @@
|
|||
"preview": "Preview",
|
||||
"settings": "Settings",
|
||||
"signup": "Signup",
|
||||
"siteSettings": "Site Settings"
|
||||
"siteSettings": "Site Settings",
|
||||
"quota": {
|
||||
"space": "Space",
|
||||
"inodes": "Inodes"
|
||||
}
|
||||
},
|
||||
"success": {
|
||||
"linkCopied": "Link copied!"
|
||||
|
|
|
@ -3,6 +3,7 @@ import Vuex from "vuex";
|
|||
import mutations from "./mutations";
|
||||
import getters from "./getters";
|
||||
import upload from "./modules/upload";
|
||||
import quota from "./modules/quota";
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
|
@ -31,5 +32,5 @@ export default new Vuex.Store({
|
|||
state,
|
||||
getters,
|
||||
mutations,
|
||||
modules: { upload },
|
||||
modules: { upload, quota },
|
||||
});
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import { users as api } from "@/api";
|
||||
|
||||
const state = {
|
||||
inodes: null,
|
||||
space: null,
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
setQuota(state, { inodes, space }) {
|
||||
state.inodes = inodes;
|
||||
state.space = space;
|
||||
},
|
||||
};
|
||||
|
||||
const actions = {
|
||||
fetch: async (context) => {
|
||||
try {
|
||||
let data = await api.getQuota();
|
||||
if (
|
||||
data !== null &&
|
||||
data.inodes != undefined &&
|
||||
data.space != undefined
|
||||
) {
|
||||
context.commit("setQuota", data);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default { state, mutations, actions, namespaced: true };
|
|
@ -48,7 +48,7 @@ function success(button) {
|
|||
el.style.opacity = 0;
|
||||
|
||||
setTimeout(() => {
|
||||
el.classList.remove("spin");
|
||||
el.classList.remove("pulse-spin");
|
||||
el.innerHTML = "done";
|
||||
el.style.opacity = 1;
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ const signup = window.FileBrowser.Signup;
|
|||
const version = window.FileBrowser.Version;
|
||||
const logoURL = `${staticURL}/img/logo.svg`;
|
||||
const trashDir = window.FileBrowser.TrashDir;
|
||||
const quotaExists = window.FileBrowser.QuotaExists;
|
||||
const noAuth = window.FileBrowser.NoAuth;
|
||||
const authMethod = window.FileBrowser.AuthMethod;
|
||||
const authLogoutURL = window.FileBrowser.AuthLogoutURL;
|
||||
|
@ -27,6 +28,7 @@ export {
|
|||
signup,
|
||||
version,
|
||||
trashDir,
|
||||
quotaExists,
|
||||
noAuth,
|
||||
authMethod,
|
||||
authLogoutURL,
|
||||
|
|
|
@ -67,6 +67,8 @@ func NewHandler(
|
|||
api.Handle("/settings", monkey(settingsGetHandler, "")).Methods("GET")
|
||||
api.Handle("/settings", monkey(settingsPutHandler, "")).Methods("PUT")
|
||||
|
||||
api.Handle("/quota", monkey(quotaGetHandler, "")).Methods("GET")
|
||||
|
||||
api.PathPrefix("/raw").Handler(monkey(rawHandler, "/api/raw")).Methods("GET")
|
||||
api.PathPrefix("/preview/{size}/{path:.*}").
|
||||
Handler(monkey(previewHandler(imgSvc, fileCache, server.EnableThumbnails, server.ResizePreview), "/api/preview")).Methods("GET")
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
type quotaData struct {
|
||||
InodeLimit uint64 `json:"inodeLimit"`
|
||||
InodeQuota uint64 `json:"inodeQuota"`
|
||||
InodeUsage uint64 `json:"inodeUsage"`
|
||||
SpaceLimit uint64 `json:"spaceLimit"`
|
||||
SpaceQuota uint64 `json:"spaceQuota"`
|
||||
SpaceUsage uint64 `json:"spaceUsage"`
|
||||
}
|
||||
|
||||
var quotaGetHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||
content, err := os.ReadFile(d.user.QuotaFile)
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
|
||||
data := quotaData{}
|
||||
err = json.Unmarshal(content, &data)
|
||||
if err != nil {
|
||||
return errToStatus(err), err
|
||||
}
|
||||
|
||||
res := map[string]map[string]uint64{
|
||||
"inodes": {
|
||||
"quota": data.InodeQuota,
|
||||
"usage": data.InodeUsage,
|
||||
},
|
||||
"space": {
|
||||
"quota": data.SpaceQuota,
|
||||
"usage": data.SpaceUsage,
|
||||
},
|
||||
}
|
||||
|
||||
return renderJSON(w, r, res)
|
||||
})
|
|
@ -34,6 +34,7 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys
|
|||
"StaticURL": path.Join(d.server.BaseURL, "/static"),
|
||||
"Signup": d.settings.Signup,
|
||||
"TrashDir": d.settings.Defaults.TrashDir,
|
||||
"QuotaExists": d.settings.Defaults.QuotaFile != "",
|
||||
"NoAuth": d.settings.AuthMethod == auth.MethodNoAuth,
|
||||
"AuthMethod": d.settings.AuthMethod,
|
||||
"AuthLogoutURL": d.settings.AuthLogoutURL,
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
type UserDefaults struct {
|
||||
Scope string `json:"scope"`
|
||||
TrashDir string `json:"trashDir"`
|
||||
QuotaFile string `json:"quotaFile"`
|
||||
Locale string `json:"locale"`
|
||||
ViewMode users.ViewMode `json:"viewMode"`
|
||||
SingleClick bool `json:"singleClick"`
|
||||
|
@ -23,6 +24,7 @@ type UserDefaults struct {
|
|||
func (d *UserDefaults) Apply(u *users.User) {
|
||||
u.Scope = d.Scope
|
||||
u.TrashDir = d.TrashDir
|
||||
u.QuotaFile = d.QuotaFile
|
||||
u.Locale = d.Locale
|
||||
u.ViewMode = d.ViewMode
|
||||
u.SingleClick = d.SingleClick
|
||||
|
|
|
@ -26,6 +26,7 @@ type User struct {
|
|||
Password string `json:"password"`
|
||||
Scope string `json:"scope"`
|
||||
TrashDir string `json:"trashDir"`
|
||||
QuotaFile string `json:"quotaFile"`
|
||||
Locale string `json:"locale"`
|
||||
LockPassword bool `json:"lockPassword"`
|
||||
ViewMode ViewMode `json:"viewMode"`
|
||||
|
|
Loading…
Reference in New Issue