Compare commits

...

4 Commits

12 changed files with 1014 additions and 245 deletions

View File

@ -12,6 +12,7 @@
"@vueuse/core": "^10.4.1", "@vueuse/core": "^10.4.1",
"ant-design-vue": "^4.0.3", "ant-design-vue": "^4.0.3",
"axios": "^1.5.0", "axios": "^1.5.0",
"esbuild": "^0.19.5",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"pinia": "^2.1.6", "pinia": "^2.1.6",
@ -302,10 +303,325 @@
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
"integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ=="
}, },
"node_modules/@esbuild/android-arm": {
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.5.tgz",
"integrity": "sha512-bhvbzWFF3CwMs5tbjf3ObfGqbl/17ict2/uwOSfr3wmxDE6VdS2GqY/FuzIPe0q0bdhj65zQsvqfArI9MY6+AA==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.5.tgz",
"integrity": "sha512-5d1OkoJxnYQfmC+Zd8NBFjkhyCNYwM4n9ODrycTFY6Jk1IGiZ+tjVJDDSwDt77nK+tfpGP4T50iMtVi4dEGzhQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.5.tgz",
"integrity": "sha512-9t+28jHGL7uBdkBjL90QFxe7DVA+KGqWlHCF8ChTKyaKO//VLuoBricQCgwhOjA1/qOczsw843Fy4cbs4H3DVA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.5.tgz",
"integrity": "sha512-mvXGcKqqIqyKoxq26qEDPHJuBYUA5KizJncKOAf9eJQez+L9O+KfvNFu6nl7SCZ/gFb2QPaRqqmG0doSWlgkqw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.5.tgz",
"integrity": "sha512-Ly8cn6fGLNet19s0X4unjcniX24I0RqjPv+kurpXabZYSXGM4Pwpmf85WHJN3lAgB8GSth7s5A0r856S+4DyiA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.5.tgz",
"integrity": "sha512-GGDNnPWTmWE+DMchq1W8Sd0mUkL+APvJg3b11klSGUDvRXh70JqLAO56tubmq1s2cgpVCSKYywEiKBfju8JztQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.5.tgz",
"integrity": "sha512-1CCwDHnSSoA0HNwdfoNY0jLfJpd7ygaLAp5EHFos3VWJCRX9DMwWODf96s9TSse39Br7oOTLryRVmBoFwXbuuQ==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.5.tgz",
"integrity": "sha512-lrWXLY/vJBzCPC51QN0HM71uWgIEpGSjSZZADQhq7DKhPcI6NH1IdzjfHkDQws2oNpJKpR13kv7/pFHBbDQDwQ==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.5.tgz",
"integrity": "sha512-o3vYippBmSrjjQUCEEiTZ2l+4yC0pVJD/Dl57WfPwwlvFkrxoSO7rmBZFii6kQB3Wrn/6GwJUPLU5t52eq2meA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.5.tgz",
"integrity": "sha512-MkjHXS03AXAkNp1KKkhSKPOCYztRtK+KXDNkBa6P78F8Bw0ynknCSClO/ztGszILZtyO/lVKpa7MolbBZ6oJtQ==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.5.tgz",
"integrity": "sha512-42GwZMm5oYOD/JHqHska3Jg0r+XFb/fdZRX+WjADm3nLWLcIsN27YKtqxzQmGNJgu0AyXg4HtcSK9HuOk3v1Dw==",
"cpu": [
"loong64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.5.tgz",
"integrity": "sha512-kcjndCSMitUuPJobWCnwQ9lLjiLZUR3QLQmlgaBfMX23UEa7ZOrtufnRds+6WZtIS9HdTXqND4yH8NLoVVIkcg==",
"cpu": [
"mips64el"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.5.tgz",
"integrity": "sha512-yJAxJfHVm0ZbsiljbtFFP1BQKLc8kUF6+17tjQ78QjqjAQDnhULWiTA6u0FCDmYT1oOKS9PzZ2z0aBI+Mcyj7Q==",
"cpu": [
"ppc64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.5.tgz",
"integrity": "sha512-5u8cIR/t3gaD6ad3wNt1MNRstAZO+aNyBxu2We8X31bA8XUNyamTVQwLDA1SLoPCUehNCymhBhK3Qim1433Zag==",
"cpu": [
"riscv64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.5.tgz",
"integrity": "sha512-Z6JrMyEw/EmZBD/OFEFpb+gao9xJ59ATsoTNlj39jVBbXqoZm4Xntu6wVmGPB/OATi1uk/DB+yeDPv2E8PqZGw==",
"cpu": [
"s390x"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.5.tgz",
"integrity": "sha512-psagl+2RlK1z8zWZOmVdImisMtrUxvwereIdyJTmtmHahJTKb64pAcqoPlx6CewPdvGvUKe2Jw+0Z/0qhSbG1A==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.5.tgz",
"integrity": "sha512-kL2l+xScnAy/E/3119OggX8SrWyBEcqAh8aOY1gr4gPvw76la2GlD4Ymf832UCVbmuWeTf2adkZDK+h0Z/fB4g==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.5.tgz",
"integrity": "sha512-sPOfhtzFufQfTBgRnE1DIJjzsXukKSvZxloZbkJDG383q0awVAq600pc1nfqBcl0ice/WN9p4qLc39WhBShRTA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.5.tgz",
"integrity": "sha512-dGZkBXaafuKLpDSjKcB0ax0FL36YXCvJNnztjKV+6CO82tTYVDSH2lifitJ29jxRMoUhgkg9a+VA/B03WK5lcg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.5.tgz",
"integrity": "sha512-dWVjD9y03ilhdRQ6Xig1NWNgfLtf2o/STKTS+eZuF90fI2BhbwD6WlaiCGKptlqXlURVB5AUOxUj09LuwKGDTg==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.19.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.5.tgz",
"integrity": "sha512-4liggWIA4oDgUxqpZwrDhmEfAH4d0iljanDOK7AnVU89T6CzHon/ony8C5LeOdfgx60x5cnQJFZwEydVlYx4iw==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/@esbuild/win32-x64": { "node_modules/@esbuild/win32-x64": {
"version": "0.18.20", "version": "0.19.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.5.tgz",
"integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", "integrity": "sha512-czTrygUsB/jlM8qEW5MD8bgYU2Xg14lo6kBDXW6HdxKjh8M5PzETGiSHaz9MtbXBYDloHNUAUW2tMiKW4KM9Mw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -1979,9 +2295,9 @@
} }
}, },
"node_modules/esbuild": { "node_modules/esbuild": {
"version": "0.18.20", "version": "0.19.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.5.tgz",
"integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", "integrity": "sha512-bUxalY7b1g8vNhQKdB24QDmHeY4V4tw/s6Ak5z+jJX9laP5MoQseTOMemAr0gxssjNcH0MCViG8ONI2kksvfFQ==",
"hasInstallScript": true, "hasInstallScript": true,
"bin": { "bin": {
"esbuild": "bin/esbuild" "esbuild": "bin/esbuild"
@ -1990,28 +2306,28 @@
"node": ">=12" "node": ">=12"
}, },
"optionalDependencies": { "optionalDependencies": {
"@esbuild/android-arm": "0.18.20", "@esbuild/android-arm": "0.19.5",
"@esbuild/android-arm64": "0.18.20", "@esbuild/android-arm64": "0.19.5",
"@esbuild/android-x64": "0.18.20", "@esbuild/android-x64": "0.19.5",
"@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-arm64": "0.19.5",
"@esbuild/darwin-x64": "0.18.20", "@esbuild/darwin-x64": "0.19.5",
"@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-arm64": "0.19.5",
"@esbuild/freebsd-x64": "0.18.20", "@esbuild/freebsd-x64": "0.19.5",
"@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm": "0.19.5",
"@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-arm64": "0.19.5",
"@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-ia32": "0.19.5",
"@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-loong64": "0.19.5",
"@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-mips64el": "0.19.5",
"@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-ppc64": "0.19.5",
"@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-riscv64": "0.19.5",
"@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-s390x": "0.19.5",
"@esbuild/linux-x64": "0.18.20", "@esbuild/linux-x64": "0.19.5",
"@esbuild/netbsd-x64": "0.18.20", "@esbuild/netbsd-x64": "0.19.5",
"@esbuild/openbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.19.5",
"@esbuild/sunos-x64": "0.18.20", "@esbuild/sunos-x64": "0.19.5",
"@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-arm64": "0.19.5",
"@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-ia32": "0.19.5",
"@esbuild/win32-x64": "0.18.20" "@esbuild/win32-x64": "0.19.5"
} }
}, },
"node_modules/escape-string-regexp": { "node_modules/escape-string-regexp": {
@ -4705,6 +5021,372 @@
"vite": "^2.0.0 || ^3.0.0 || ^4.0.0" "vite": "^2.0.0 || ^3.0.0 || ^4.0.0"
} }
}, },
"node_modules/vite/node_modules/@esbuild/android-arm": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
"integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/android-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
"integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/android-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
"integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/darwin-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
"integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/darwin-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
"integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
"integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/freebsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
"integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/linux-arm": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
"integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/linux-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
"integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/linux-ia32": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
"integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/linux-loong64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
"integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
"cpu": [
"loong64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/linux-mips64el": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
"integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
"cpu": [
"mips64el"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/linux-ppc64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
"integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
"cpu": [
"ppc64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/linux-riscv64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
"integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
"cpu": [
"riscv64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/linux-s390x": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
"integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
"cpu": [
"s390x"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/linux-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
"integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/netbsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
"integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/openbsd-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
"integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/sunos-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
"integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/win32-arm64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
"integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/win32-ia32": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
"integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/@esbuild/win32-x64": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
"integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/vite/node_modules/esbuild": {
"version": "0.18.20",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
"integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/android-arm": "0.18.20",
"@esbuild/android-arm64": "0.18.20",
"@esbuild/android-x64": "0.18.20",
"@esbuild/darwin-arm64": "0.18.20",
"@esbuild/darwin-x64": "0.18.20",
"@esbuild/freebsd-arm64": "0.18.20",
"@esbuild/freebsd-x64": "0.18.20",
"@esbuild/linux-arm": "0.18.20",
"@esbuild/linux-arm64": "0.18.20",
"@esbuild/linux-ia32": "0.18.20",
"@esbuild/linux-loong64": "0.18.20",
"@esbuild/linux-mips64el": "0.18.20",
"@esbuild/linux-ppc64": "0.18.20",
"@esbuild/linux-riscv64": "0.18.20",
"@esbuild/linux-s390x": "0.18.20",
"@esbuild/linux-x64": "0.18.20",
"@esbuild/netbsd-x64": "0.18.20",
"@esbuild/openbsd-x64": "0.18.20",
"@esbuild/sunos-x64": "0.18.20",
"@esbuild/win32-arm64": "0.18.20",
"@esbuild/win32-ia32": "0.18.20",
"@esbuild/win32-x64": "0.18.20"
}
},
"node_modules/vitest": { "node_modules/vitest": {
"version": "0.34.4", "version": "0.34.4",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.4.tgz", "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.4.tgz",

View File

@ -17,6 +17,7 @@
"@vueuse/core": "^10.4.1", "@vueuse/core": "^10.4.1",
"ant-design-vue": "^4.0.3", "ant-design-vue": "^4.0.3",
"axios": "^1.5.0", "axios": "^1.5.0",
"esbuild": "^0.19.5",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"pinia": "^2.1.6", "pinia": "^2.1.6",

View File

@ -26,7 +26,8 @@
pathList pathList
} }
}) })
// Update human-readable x seconds ago messages from mtimes
setInterval(documentStore.updateModified, 1000)
watchEffect(() => { watchEffect(() => {
const documentHandler = new DocumentHandler() const documentHandler = new DocumentHandler()
const documentUploadHandler = new DocumentUploadHandler() const documentUploadHandler = new DocumentUploadHandler()

View File

@ -15,13 +15,14 @@
<template #headerCell="{column}"></template> <template #headerCell="{column}"></template>
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'name'"> <template v-if="column.key === 'name'">
<div class="editable-cell"> <div class="editable-cell" :class="record.type === 'folder' ? 'folder' : 'file'">
<div v-if="editableData[record.key]" class="action-container editable-cell-input-wrapper"> <div v-if="editableData[record.key]" class="action-container editable-cell-input-wrapper">
<a-input class="name" v-model:value="editableData[record.key].name" @pressEnter="save(record.key)" /> <a-input class="name" v-model:value="editableData[record.key].name" @pressEnter="save(record.key)" />
<CheckOutlined class="edit-action editable-cell-icon-check" @click="save(record.key)" /> <CheckOutlined class="edit-action editable-cell-icon-check" @click="save(record.key)" />
</div> </div>
<div v-else class="action-container editable-cell-text-wrapper"> <div v-else class="action-container editable-cell-text-wrapper">
<a class="name" :href="`#${linkBasePath}/${record.name}`">{{record.name}}</a> <a v-if="record.type === 'folder'" class="name" :href="`#${linkBasePath}/${record.name}`">{{record.name}}</a>
<a v-else class="name" :href="`${filesBasePath}/${record.name}`">{{record.name}}</a>
<edit-outlined class="edit-action editable-cell-icon" @click="edit(record.key)" /> <edit-outlined class="edit-action editable-cell-icon" @click="edit(record.key)" />
</div> </div>
</div> </div>
@ -84,9 +85,10 @@
}); });
const linkBasePath = computed(()=>{ const linkBasePath = computed(()=>{
if(Router.currentRoute.value.path === '/') return '' const path = Router.currentRoute.value.path
return Router.currentRoute.value.path return path === '/' ? '' : path
}) })
const filesBasePath = computed(() => `/files${linkBasePath.value}`)
const columns = ref<TableColumnsType>([ const columns = ref<TableColumnsType>([
{ {
@ -95,34 +97,26 @@
width: '70%', width: '70%',
key: 'name', key: 'name',
sortDirections: ['ascend', 'descend'], sortDirections: ['ascend', 'descend'],
sorter: (a: Document, b: Document, sortOrder) => { sorter: (a: Document, b: Document) => a.name.localeCompare(b.name),
return b.name.localeCompare(a.name)
}
}, },
{ {
title: 'Modified', title: 'Modified',
dataIndex: 'modified', dataIndex: 'modified',
className: 'column-date',
responsive: ['lg'], responsive: ['lg'],
sortDirections: ['ascend', 'descend'], sortDirections: ['ascend', 'descend'],
defaultSortOrder: 'descend', defaultSortOrder: 'descend',
sorter: (a: FolderDocument, b: FolderDocument) => { sorter: (a: FolderDocument, b: FolderDocument) => a.mtime - b.mtime,
const dateA = new Date(a.modified),
dateB = new Date(b.modified);
if (dateA < dateB) return -1
if (dateA > dateB) return 1
return 0
},
key: 'modified', key: 'modified',
}, },
{ {
// TODO BETTER SORT FOR MULTPLE SIZE OR CUSTOM PIPE TO kB to MB / GB // TODO BETTER SORT FOR MULTPLE SIZE OR CUSTOM PIPE TO kB to MB / GB
title: 'Size', title: 'Size',
dataIndex: 'size', dataIndex: 'sizedisp',
className: 'column-size',
responsive: ['lg'], responsive: ['lg'],
sortDirections: ['ascend', 'descend'], sortDirections: ['ascend', 'descend'],
sorter: (a: FolderDocument, b: FolderDocument) => { sorter: (a: FolderDocument, b: FolderDocument) => a.size - b.size,
return a.size - b.size
},
key: 'size', key: 'size',
}, },
{ {
@ -152,7 +146,10 @@
</script> </script>
<style scoped> <style>
.column-date, .column-size {
text-align: right;
}
main { main {
padding: 5px; padding: 5px;
height: 100%; height: 100%;
@ -175,11 +172,19 @@
.carousel-container{ .carousel-container{
height: inherit; height: inherit;
} }
.file .name::before {
content: '📄 ';
font-size: 1.5em;
}
.folder .name::before {
content: '📁 ';
font-size: 1.5em;
}
.editable-cell-text-wrapper .editable-cell-icon { .editable-cell-text-wrapper .editable-cell-icon {
visibility: hidden; /* Oculta el ícono de manera predeterminada */ visibility: hidden; /* Oculta el ícono de manera predeterminada */
} }
.editable-cell-text-wrapper:hover .editable-cell-icon { .editable-cell-text-wrapper:hover .editable-cell-icon {
visibility: visible; /* Muestra el ícono al hacer hover en el contenedor */ visibility: visible; /* Muestra el ícono al hacer hover en el contenedor */
} }
</style> </style>

View File

@ -6,13 +6,15 @@ import Client from '@/repositories/Client'
type BaseDocument = { type BaseDocument = {
name: string; name: string;
key?: number; key?: number | string;
}; };
export type FolderDocument = BaseDocument & { export type FolderDocument = BaseDocument & {
type: 'folder' | 'folder-file';
size: number; size: number;
sizedisp: string;
mtime: number;
modified: string; modified: string;
type: 'folder';
}; };
export type FileDocument = BaseDocument & { export type FileDocument = BaseDocument & {

View File

@ -1,15 +1,15 @@
import type { Document } from '@/repositories/Document'; import type { Document, FolderDocument } from '@/repositories/Document';
import type { ISimpleError } from '@/repositories/Client'; import type { ISimpleError } from '@/repositories/Client';
import { fetchFile } from '@/repositories/Document' import { fetchFile } from '@/repositories/Document'
import { formatUnixDate } from '@/utils'; import { formatSize, formatUnixDate } from '@/utils';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
type FileData = { mtime: number, size: number, dir: DirectoryData}; type FileData = { id: string, mtime: number, size: number, dir: DirectoryData};
type DirectoryData = { type DirectoryData = {
[filename: string]: FileData; [filename: string]: FileData;
}; };
export type FileStructure = {mtime: number, size: number, dir: DirectoryData}; export type FileStructure = {id: string, mtime: number, size: number, dir: DirectoryData};
export type DocumentStore = { export type DocumentStore = {
root: FileStructure, root: FileStructure,
@ -54,18 +54,21 @@ export const useDocumentStore = defineStore({
}) })
// Transform data // Transform data
let count = 0 for (const [name, attr] of Object.entries(data.dir)) {
for (const key in data.dir) { const {id, size, mtime, dir} = attr
const element: Document = { const element: Document = {
name: key, name,
key: count, key: id,
size: data.dir[key].size, size,
modified: formatUnixDate(data.dir[key].mtime), sizedisp: formatSize(size),
type: 'folder', mtime,
modified: formatUnixDate(mtime),
type: dir === undefined ? 'folder-file' : 'folder',
} }
count++
dataMapped.push(element) dataMapped.push(element)
} }
// Pre sort directory entries folders first then files, names in natural ordering
dataMapped.sort((a, b) => a.type === b.type ? a.name.localeCompare(b.name) : a.type === "folder" ? -1 : 1)
this.document = dataMapped this.document = dataMapped
this.loading = false; this.loading = false;
}, },
@ -83,11 +86,9 @@ export const useDocumentStore = defineStore({
this.selectedDocuments = this.selectedDocuments.filter(e => document.key !== e.key) this.selectedDocuments = this.selectedDocuments.filter(e => document.key !== e.key)
}, },
updateUploadingDocuments(key: number, progress: number){ updateUploadingDocuments(key: number, progress: number){
this.uploadingDocuments.forEach((document) => { for (const d of this.uploadingDocuments) {
if(document.key === key) { if(d.key === key) d.progress = progress
document.progress = progress }
}
})
}, },
pushUploadingDocuments(name: string){ pushUploadingDocuments(name: string){
this.uploadCount++; this.uploadCount++;
@ -134,9 +135,13 @@ export const useDocumentStore = defineStore({
index = index + direction index = index + direction
} }
return actualDirArr[index].name return actualDirArr[index].name
},
updateModified() {
for (const d of this.document) {
if ("mtime" in d) d.modified = formatUnixDate(d.mtime)
}
} }
}, },
getters: { getters: {
mainDocument(): Document[] { mainDocument(): Document[] {
return this.document; return this.document;

View File

@ -6,13 +6,23 @@ export function determineFileType(inputString: string): "file" | "folder" {
} }
} }
export function formatSize(size: number) {
for (const unit of [null, 'kB', 'MB', 'GB', 'TB', 'PB', 'EB']) {
if (size < 1e5) return size.toLocaleString().replace(',', '\u202F') + (unit ? `\u202F${unit}` : '')
size = Math.round(size / 1000)
}
return "huge"
}
export function formatUnixDate(t: number) { export function formatUnixDate(t: number) {
const date = new Date(t * 1000) const date = new Date(t * 1000)
const now = new Date() const now = new Date()
const diff = date.getTime() - now.getTime() const diff = date.getTime() - now.getTime()
const formatter = new Intl.RelativeTimeFormat('en', { numeric: const formatter = new Intl.RelativeTimeFormat('en', { numeric:
'auto' }) 'auto' })
if (Math.abs(diff) <= 5000) {
return 'now'
}
if (Math.abs(diff) <= 60000) { if (Math.abs(diff) <= 60000) {
return formatter.format(Math.round(diff / 1000), 'second') return formatter.format(Math.round(diff / 1000), 'second')
} }
@ -29,7 +39,7 @@ export function formatUnixDate(t: number) {
return formatter.format(Math.round(diff / 86400000), 'day') return formatter.format(Math.round(diff / 86400000), 'day')
} }
return date.toLocaleDateString() return date.toLocaleDateString(undefined, { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' })
} }
export function getFileExtension(filename: string) { export function getFileExtension(filename: string) {

View File

@ -11,19 +11,24 @@ from cista.util import filename
## Control commands ## Control commands
class ControlBase(msgspec.Struct, tag_field="op", tag=str.lower): class ControlBase(msgspec.Struct, tag_field="op", tag=str.lower):
def __call__(self): def __call__(self):
raise NotImplementedError raise NotImplementedError
class MkDir(ControlBase): class MkDir(ControlBase):
path: str path: str
def __call__(self): def __call__(self):
path = config.config.path / filename.sanitize(self.path) path = config.config.path / filename.sanitize(self.path)
path.mkdir(parents=False, exist_ok=False) path.mkdir(parents=False, exist_ok=False)
class Rename(ControlBase): class Rename(ControlBase):
path: str path: str
to: str to: str
def __call__(self): def __call__(self):
to = filename.sanitize(self.to) to = filename.sanitize(self.to)
if "/" in to: if "/" in to:
@ -31,17 +36,21 @@ class Rename(ControlBase):
path = config.config.path / filename.sanitize(self.path) path = config.config.path / filename.sanitize(self.path)
path.rename(path.with_name(to)) path.rename(path.with_name(to))
class Rm(ControlBase): class Rm(ControlBase):
sel: list[str] sel: list[str]
def __call__(self): def __call__(self):
root = config.config.path root = config.config.path
sel = [root / filename.sanitize(p) for p in self.sel] sel = [root / filename.sanitize(p) for p in self.sel]
for p in sel: for p in sel:
shutil.rmtree(p, ignore_errors=True) shutil.rmtree(p, ignore_errors=True)
class Mv(ControlBase): class Mv(ControlBase):
sel: list[str] sel: list[str]
dst: str dst: str
def __call__(self): def __call__(self):
root = config.config.path root = config.config.path
sel = [root / filename.sanitize(p) for p in self.sel] sel = [root / filename.sanitize(p) for p in self.sel]
@ -51,9 +60,11 @@ class Mv(ControlBase):
for p in sel: for p in sel:
shutil.move(p, dst) shutil.move(p, dst)
class Cp(ControlBase): class Cp(ControlBase):
sel: list[str] sel: list[str]
dst: str dst: str
def __call__(self): def __call__(self):
root = config.config.path root = config.config.path
sel = [root / filename.sanitize(p) for p in self.sel] sel = [root / filename.sanitize(p) for p in self.sel]
@ -62,30 +73,41 @@ class Cp(ControlBase):
raise BadRequest("The destination must be a directory") raise BadRequest("The destination must be a directory")
for p in sel: for p in sel:
# Note: copies as dst rather than in dst unless name is appended. # Note: copies as dst rather than in dst unless name is appended.
shutil.copytree(p, dst / p.name, dirs_exist_ok=True, ignore_dangling_symlinks=True) shutil.copytree(
p, dst / p.name, dirs_exist_ok=True, ignore_dangling_symlinks=True
)
## File uploads and downloads ## File uploads and downloads
class FileRange(msgspec.Struct): class FileRange(msgspec.Struct):
name: str name: str
size: int size: int
start: int start: int
end: int end: int
class StatusMsg(msgspec.Struct): class StatusMsg(msgspec.Struct):
status: str status: str
req: FileRange req: FileRange
class ErrorMsg(msgspec.Struct): class ErrorMsg(msgspec.Struct):
error: dict[str, Any] error: dict[str, Any]
## Directory listings ## Directory listings
class FileEntry(msgspec.Struct): class FileEntry(msgspec.Struct):
id: str
size: int size: int
mtime: int mtime: int
class DirEntry(msgspec.Struct): class DirEntry(msgspec.Struct):
id: str
size: int size: int
mtime: int mtime: int
dir: DirList dir: DirList
@ -104,30 +126,30 @@ class DirEntry(msgspec.Struct):
@property @property
def props(self): def props(self):
return { return {k: v for k, v in self.__struct_fields__ if k != "dir"}
k: v
for k, v in self.__struct_fields__
if k != "dir"
}
DirList = dict[str, FileEntry | DirEntry] DirList = dict[str, FileEntry | DirEntry]
class UpdateEntry(msgspec.Struct, omit_defaults=True): class UpdateEntry(msgspec.Struct, omit_defaults=True):
"""Updates the named entry in the tree. Fields that are set replace old values. A list of entries recurses directories.""" """Updates the named entry in the tree. Fields that are set replace old values. A list of entries recurses directories."""
name: str = "" name: str = ""
deleted: bool = False deleted: bool = False
id: str | None = None
size: int | None = None size: int | None = None
mtime: int | None = None mtime: int | None = None
dir: DirList | None = None dir: DirList | None = None
def make_dir_data(root): def make_dir_data(root):
if len(root) == 2: if len(root) == 3:
return FileEntry(*root) return FileEntry(*root)
size, mtime, listing = root id_, size, mtime, listing = root
converted = {} converted = {}
for name, data in listing.items(): for name, data in listing.items():
converted[name] = make_dir_data(data) converted[name] = make_dir_data(data)
sz = sum(x.size for x in converted.values()) sz = sum(x.size for x in converted.values())
mt = max(x.mtime for x in converted.values()) mt = max(x.mtime for x in converted.values())
return DirEntry(sz, max(mt, mtime), converted) return DirEntry(id_, sz, max(mt, mtime), converted)

View File

@ -8,6 +8,7 @@ import inotify.adapters
import msgspec import msgspec
from cista import config from cista import config
from cista.fileio import fuid
from cista.protocol import DirEntry, FileEntry, UpdateEntry from cista.protocol import DirEntry, FileEntry, UpdateEntry
pubsub = {} pubsub = {}
@ -15,9 +16,18 @@ tree = {"": None}
tree_lock = threading.Lock() tree_lock = threading.Lock()
rootpath: Path = None # type: ignore rootpath: Path = None # type: ignore
quit = False quit = False
modified_flags = "IN_CREATE", "IN_DELETE", "IN_DELETE_SELF", "IN_MODIFY", "IN_MOVE_SELF", "IN_MOVED_FROM", "IN_MOVED_TO" modified_flags = (
"IN_CREATE",
"IN_DELETE",
"IN_DELETE_SELF",
"IN_MODIFY",
"IN_MOVE_SELF",
"IN_MOVED_FROM",
"IN_MOVED_TO",
)
disk_usage = None disk_usage = None
def watcher_thread(loop): def watcher_thread(loop):
global disk_usage global disk_usage
@ -36,7 +46,8 @@ def watcher_thread(loop):
refreshdl = time.monotonic() + 60.0 refreshdl = time.monotonic() + 60.0
for event in i.event_gen(): for event in i.event_gen():
if quit: return if quit:
return
# Disk usage update # Disk usage update
du = shutil.disk_usage(rootpath) du = shutil.disk_usage(rootpath)
if du != disk_usage: if du != disk_usage:
@ -44,8 +55,10 @@ def watcher_thread(loop):
asyncio.run_coroutine_threadsafe(broadcast(format_du()), loop) asyncio.run_coroutine_threadsafe(broadcast(format_du()), loop)
break break
# Do a full refresh? # Do a full refresh?
if time.monotonic() > refreshdl: break if time.monotonic() > refreshdl:
if event is None: continue break
if event is None:
continue
_, flags, path, filename = event _, flags, path, filename = event
if not any(f in modified_flags for f in flags): if not any(f in modified_flags for f in flags):
continue continue
@ -58,50 +71,72 @@ def watcher_thread(loop):
break break
i = None # Free the inotify object i = None # Free the inotify object
def format_du(): def format_du():
return msgspec.json.encode({"space": { return msgspec.json.encode(
"disk": disk_usage.total, {
"used": disk_usage.used, "space": {
"free": disk_usage.free, "disk": disk_usage.total,
"storage": tree[""].size, "used": disk_usage.used,
}}).decode() "free": disk_usage.free,
"storage": tree[""].size,
}
}
).decode()
def format_tree(): def format_tree():
root = tree[""] root = tree[""]
return msgspec.json.encode({"update": [ return msgspec.json.encode(
UpdateEntry(size=root.size, mtime=root.mtime, dir=root.dir) {
]}).decode() "update": [
UpdateEntry(id=root.id, size=root.size, mtime=root.mtime, dir=root.dir)
]
}
).decode()
def walk(path: Path) -> DirEntry | FileEntry | None: def walk(path: Path) -> DirEntry | FileEntry | None:
try: try:
s = path.stat() s = path.stat()
id_ = fuid(s)
mtime = int(s.st_mtime) mtime = int(s.st_mtime)
if path.is_file(): if path.is_file():
return FileEntry(s.st_size, mtime) return FileEntry(id_, s.st_size, mtime)
tree = {p.name: v for p in path.iterdir() if not p.name.startswith('.') if (v := walk(p)) is not None} tree = {
p.name: v
for p in path.iterdir()
if not p.name.startswith(".")
if (v := walk(p)) is not None
}
if tree: if tree:
size = sum(v.size for v in tree.values()) size = sum(v.size for v in tree.values())
mtime = max(mtime, max(v.mtime for v in tree.values())) mtime = max(mtime, max(v.mtime for v in tree.values()))
else: else:
size = 0 size = 0
return DirEntry(size, mtime, tree) return DirEntry(id_, size, mtime, tree)
except FileNotFoundError: except FileNotFoundError:
return None return None
except OSError as e: except OSError as e:
print("OS error walking path", path, e) print("OS error walking path", path, e)
return None return None
def update(relpath: PurePosixPath, loop): def update(relpath: PurePosixPath, loop):
"""Called by inotify updates, check the filesystem and broadcast any changes.""" """Called by inotify updates, check the filesystem and broadcast any changes."""
new = walk(rootpath / relpath) new = walk(rootpath / relpath)
with tree_lock: with tree_lock:
update = update_internal(relpath, new) update = update_internal(relpath, new)
if not update: return # No changes if not update:
return # No changes
msg = msgspec.json.encode({"update": update}).decode() msg = msgspec.json.encode({"update": update}).decode()
asyncio.run_coroutine_threadsafe(broadcast(msg), loop) asyncio.run_coroutine_threadsafe(broadcast(msg), loop)
def update_internal(relpath: PurePosixPath, new: DirEntry | FileEntry | None) -> list[UpdateEntry]:
def update_internal(
relpath: PurePosixPath, new: DirEntry | FileEntry | None
) -> list[UpdateEntry]:
path = "", *relpath.parts path = "", *relpath.parts
old = tree old = tree
elems = [] elems = []
@ -142,25 +177,31 @@ def update_internal(relpath: PurePosixPath, new: DirEntry | FileEntry | None) ->
u = UpdateEntry(name) u = UpdateEntry(name)
if new: if new:
parent[name] = new parent[name] = new
if u.size != new.size: u.size = new.size if u.size != new.size:
if u.mtime != new.mtime: u.mtime = new.mtime u.size = new.size
if u.mtime != new.mtime:
u.mtime = new.mtime
if isinstance(new, DirEntry): if isinstance(new, DirEntry):
if u.dir == new.dir: u.dir = new.dir if u.dir == new.dir:
u.dir = new.dir
else: else:
del parent[name] del parent[name]
u.deleted = True u.deleted = True
update.append(u) update.append(u)
return update return update
async def broadcast(msg): async def broadcast(msg):
for queue in pubsub.values(): for queue in pubsub.values():
await queue.put_nowait(msg) await queue.put_nowait(msg)
async def start(app, loop): async def start(app, loop):
config.load_config() config.load_config()
app.ctx.watcher = threading.Thread(target=watcher_thread, args=[loop]) app.ctx.watcher = threading.Thread(target=watcher_thread, args=[loop])
app.ctx.watcher.start() app.ctx.watcher.start()
async def stop(app, loop): async def stop(app, loop):
global quit global quit
quit = True quit = True

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -5,8 +5,8 @@
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Vite Vasanko</title> <title>Vite Vasanko</title>
<script type="module" crossorigin src="/assets/index-1dc06db1.js"></script> <script type="module" crossorigin src="/assets/index-10851222.js"></script>
<link rel="stylesheet" href="/assets/index-09b10238.css"> <link rel="stylesheet" href="/assets/index-ee545ab1.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>