mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-05-23 17:49:40 +02:00
feat: measure and display user quota (#72)
This commit is contained in:
+8
-5
@@ -164,18 +164,21 @@ async function connect(db) {
|
|||||||
console.error(`Repository ${r.fullName} is not found (renamed)`);
|
console.error(`Repository ${r.fullName} is not found (renamed)`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let size = 0;
|
let size = { storage: 0, file: 0 };
|
||||||
function recursiveCount(files) {
|
function recursiveCount(files) {
|
||||||
let total = 0;
|
const out = { storage: 0, file: 0 };
|
||||||
for (const name in files) {
|
for (const name in files) {
|
||||||
const file = files[name];
|
const file = files[name];
|
||||||
if (file.size && file.sha && parseInt(file.size) == file.size) {
|
if (file.size && file.sha && parseInt(file.size) == file.size) {
|
||||||
total += file.size as number;
|
out.storage += file.size as number;
|
||||||
|
out.file++;
|
||||||
} else if (typeof file == "object") {
|
} else if (typeof file == "object") {
|
||||||
total += recursiveCount(file);
|
const r = recursiveCount(file);
|
||||||
|
out.storage += r.storage;
|
||||||
|
out.file += r.file;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return total;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (r.originalFiles) {
|
if (r.originalFiles) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"repoId_already_used": "The repository id is already used.",
|
"repoId_already_used": "The repository id is already used.",
|
||||||
"invalid_repoId": "The format of the repository id is invalid.",
|
"invalid_repoId": "The format of the repository id is invalid.",
|
||||||
"branch_not_specified": "The branch is not specified.",
|
"branch_not_specified": "The branch is not specified.",
|
||||||
|
"branch_not_found": "The branch of the repository cannot be found.",
|
||||||
"options_not_provided": "Anonymization options are mandatory.",
|
"options_not_provided": "Anonymization options are mandatory.",
|
||||||
"invalid_terms_format": "Terms are in an invalid format.",
|
"invalid_terms_format": "Terms are in an invalid format.",
|
||||||
"unable_to_anonymize": "An error happened during the anonymization process. Please try later or report the issue.",
|
"unable_to_anonymize": "An error happened during the anonymization process. Please try later or report the issue.",
|
||||||
|
|||||||
@@ -152,6 +152,67 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-1 mr-1" style="margin-top: -0.25rem !important">
|
||||||
|
<strong>Repository</strong>
|
||||||
|
<div class="progress" style="min-width: 150px">
|
||||||
|
<div
|
||||||
|
class="progress-bar"
|
||||||
|
ng-class="{'progress-bar-striped progress-bar-animated w-100 bg-dark': !quota, 'bg-success': quota.repository.percent < 25 || quota.repository.total == 0, 'bg-danger': quota.repository.percent > 95 && quota.repository.total > 0, 'bg-warning': quota.repository.percent > 75 && quota.repository.total > 0 }"
|
||||||
|
role="progressbar"
|
||||||
|
style="width: {{quota.repository.percent}}%;"
|
||||||
|
aria-valuenow="{{quota.repository.used}}"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="{{quota.repository.total}}"
|
||||||
|
>
|
||||||
|
<span ng-show="quota"
|
||||||
|
>{{quota.repository.used |
|
||||||
|
number}}/{{quota.repository.total}}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-1 mr-1" style="margin-top: -0.25rem !important">
|
||||||
|
<strong>Storage</strong>
|
||||||
|
<div class="progress" style="min-width: 150px">
|
||||||
|
<div
|
||||||
|
class="progress-bar"
|
||||||
|
ng-class="{'progress-bar-striped progress-bar-animated w-100 bg-dark': !quota, 'bg-success': quota.storage.percent < 25 || quota.storage.total == 0, 'bg-danger': quota.storage.percent > 95 && quota.storage.total > 0, 'bg-warning': quota.storage.percent > 75 && quota.storage.total > 0 }"
|
||||||
|
role="progressbar"
|
||||||
|
style="width: {{quota.storage.percent}}%;"
|
||||||
|
aria-valuenow="{{quota.storage.used}}"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="{{quota.storage.total}}"
|
||||||
|
>
|
||||||
|
<span ng-show="quota"
|
||||||
|
>{{quota.storage.used |
|
||||||
|
humanFileSize}}/{{quota.storage.total|
|
||||||
|
humanFileSize}}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-1 mr-1" style="margin-top: -0.25rem !important">
|
||||||
|
<strong>File</strong>
|
||||||
|
<div class="progress" style="min-width: 150px">
|
||||||
|
<div
|
||||||
|
class="progress-bar"
|
||||||
|
ng-class="{'progress-bar-striped progress-bar-animated w-100 bg-dark': !quota, 'bg-success': quota.file.percent < 25 || quota.file.total == 0, 'bg-danger': quota.file.percent > 95 && quota.file.total > 0, 'bg-warning': quota.file.percent > 75 && quota.file.total > 0 }"
|
||||||
|
role="progressbar"
|
||||||
|
style="width: {{quota.file.percent}}%;"
|
||||||
|
aria-valuenow="{{quota.file.used}}"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="{{quota.file.total}}"
|
||||||
|
>
|
||||||
|
<span ng-show="quota"
|
||||||
|
>{{quota.file.used | number}}/{{quota.file.total ||
|
||||||
|
"∞"}}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -235,7 +296,7 @@
|
|||||||
data-toggle="tooltip"
|
data-toggle="tooltip"
|
||||||
data-placement="bottom"
|
data-placement="bottom"
|
||||||
>
|
>
|
||||||
<i class="fas fa-database"></i> {{::repo.size |
|
<i class="fas fa-database"></i> {{::repo.size.storage |
|
||||||
humanFileSize}}</span
|
humanFileSize}}</span
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -1,4 +1,56 @@
|
|||||||
<div class="container py-4">
|
<div class="container py-4">
|
||||||
|
<h2>Quota</h2>
|
||||||
|
<h3>Quota</h3>
|
||||||
|
<h5>Repository</h5>
|
||||||
|
<div class="progress">
|
||||||
|
<div
|
||||||
|
class="progress-bar"
|
||||||
|
ng-class="{'progress-bar-striped progress-bar-animated w-100 bg-dark': !quota, 'bg-success': quota.repository.percent < 25 || quota.repository.total == 0, 'bg-danger': quota.repository.percent > 95 && quota.repository.total > 0, 'bg-warning': quota.repository.percent > 75 && quota.repository.total > 0 }"
|
||||||
|
role="progressbar"
|
||||||
|
style="width: {{quota.repository.percent}}%;"
|
||||||
|
aria-valuenow="{{quota.repository.used}}"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="{{quota.repository.total}}"
|
||||||
|
>
|
||||||
|
<span ng-show="quota"
|
||||||
|
>{{quota.repository.used | number}}/{{quota.repository.total}}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h5>Storage</h5>
|
||||||
|
<div class="progress">
|
||||||
|
<div
|
||||||
|
class="progress-bar"
|
||||||
|
ng-class="{'progress-bar-striped progress-bar-animated w-100 bg-dark': !quota, 'bg-success': quota.storage.percent < 25 || quota.storage.total == 0, 'bg-danger': quota.storage.percent > 95 && quota.storage.total > 0, 'bg-warning': quota.storage.percent > 75 && quota.storage.total > 0 }"
|
||||||
|
role="progressbar"
|
||||||
|
style="width: {{quota.storage.percent}}%;"
|
||||||
|
aria-valuenow="{{quota.storage.used}}"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="{{quota.storage.total}}"
|
||||||
|
>
|
||||||
|
<span ng-show="quota"
|
||||||
|
>{{quota.storage.used | humanFileSize}}/{{quota.storage.total|
|
||||||
|
humanFileSize}}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h5>File</h5>
|
||||||
|
<div class="progress">
|
||||||
|
<div
|
||||||
|
class="progress-bar"
|
||||||
|
ng-class="{'progress-bar-striped progress-bar-animated w-100 bg-dark': !quota, 'bg-success': quota.file.percent < 25 || quota.file.total == 0, 'bg-danger': quota.file.percent > 95 && quota.file.total > 0, 'bg-warning': quota.file.percent > 75 && quota.file.total > 0 }"
|
||||||
|
role="progressbar"
|
||||||
|
style="width: {{quota.file.percent}}%;"
|
||||||
|
aria-valuenow="{{quota.file.used}}"
|
||||||
|
aria-valuemin="0"
|
||||||
|
aria-valuemax="{{quota.file.total}}"
|
||||||
|
>
|
||||||
|
<span ng-show="quota"
|
||||||
|
>{{quota.file.used | number}}/{{quota.file.total || "∞"}}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h2>Default anonymization options</h2>
|
<h2>Default anonymization options</h2>
|
||||||
<form class="form needs-validation" name="default" novalidate>
|
<form class="form needs-validation" name="default" novalidate>
|
||||||
<!-- Terms -->
|
<!-- Terms -->
|
||||||
|
|||||||
+17
-7
@@ -406,6 +406,7 @@ angular
|
|||||||
$http.get("/api/user").then(
|
$http.get("/api/user").then(
|
||||||
(res) => {
|
(res) => {
|
||||||
if (res) $scope.user = res.data;
|
if (res) $scope.user = res.data;
|
||||||
|
getQuota();
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
$scope.user = null;
|
$scope.user = null;
|
||||||
@@ -425,6 +426,22 @@ angular
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
getOptions();
|
getOptions();
|
||||||
|
function getQuota() {
|
||||||
|
$http.get("/api/user/quota").then((res) => {
|
||||||
|
$scope.quota = res.data;
|
||||||
|
$scope.quota.storage.percent = $scope.quota.storage.total
|
||||||
|
? ($scope.quota.storage.used * 100) / $scope.quota.storage.total
|
||||||
|
: 100;
|
||||||
|
$scope.quota.file.percent = $scope.quota.file.total
|
||||||
|
? ($scope.quota.file.used * 100) / $scope.quota.file.total
|
||||||
|
: 100;
|
||||||
|
$scope.quota.repository.percent = $scope.quota.repository.total
|
||||||
|
? ($scope.quota.repository.used * 100) /
|
||||||
|
$scope.quota.repository.total
|
||||||
|
: 100;
|
||||||
|
}, console.error);
|
||||||
|
}
|
||||||
|
getQuota();
|
||||||
|
|
||||||
function getMessage() {
|
function getMessage() {
|
||||||
$http.get("/api/message").then(
|
$http.get("/api/message").then(
|
||||||
@@ -605,13 +622,6 @@ angular
|
|||||||
}
|
}
|
||||||
getRepositories();
|
getRepositories();
|
||||||
|
|
||||||
function getQuota() {
|
|
||||||
$http.get("/api/user/quota").then((res) => {
|
|
||||||
$scope.quota = res.data;
|
|
||||||
}, console.error);
|
|
||||||
}
|
|
||||||
// getQuota();
|
|
||||||
|
|
||||||
$scope.removeRepository = (repo) => {
|
$scope.removeRepository = (repo) => {
|
||||||
if (
|
if (
|
||||||
confirm(
|
confirm(
|
||||||
|
|||||||
+33
-14
@@ -82,7 +82,7 @@ export default class Repository {
|
|||||||
}
|
}
|
||||||
const files = await this.source.getFiles();
|
const files = await this.source.getFiles();
|
||||||
this._model.originalFiles = files;
|
this._model.originalFiles = files;
|
||||||
this._model.size = 0;
|
this._model.size = { storage: 0, file: 0 };
|
||||||
await this.computeSize();
|
await this.computeSize();
|
||||||
await this._model.save();
|
await this._model.save();
|
||||||
|
|
||||||
@@ -149,14 +149,21 @@ export default class Repository {
|
|||||||
const branch = this.source.branch;
|
const branch = this.source.branch;
|
||||||
if (
|
if (
|
||||||
branch.commit ==
|
branch.commit ==
|
||||||
branches.filter((f) => f.name == branch.name)[0].commit
|
branches.filter((f) => f.name == branch.name)[0]?.commit
|
||||||
) {
|
) {
|
||||||
console.log(`${this._model.repoId} is up to date`);
|
console.log(`${this._model.repoId} is up to date`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._model.source.commit = branches.filter(
|
this._model.source.commit = branches.filter(
|
||||||
(f) => f.name == branch.name
|
(f) => f.name == branch.name
|
||||||
)[0].commit;
|
)[0]?.commit;
|
||||||
|
|
||||||
|
if (!this._model.source.commit) {
|
||||||
|
console.error(
|
||||||
|
`${branch.name} for ${this.source.githubRepository.fullName} is not found`
|
||||||
|
);
|
||||||
|
throw new Error("branch_not_found");
|
||||||
|
}
|
||||||
this._model.anonymizeDate = new Date();
|
this._model.anonymizeDate = new Date();
|
||||||
console.log(
|
console.log(
|
||||||
`${this._model.repoId} will be updated to ${this._model.source.commit}`
|
`${this._model.repoId} will be updated to ${this._model.source.commit}`
|
||||||
@@ -218,7 +225,7 @@ export default class Repository {
|
|||||||
*/
|
*/
|
||||||
private async resetSate(status?: RepositoryStatus) {
|
private async resetSate(status?: RepositoryStatus) {
|
||||||
if (status) this._model.status = status;
|
if (status) this._model.status = status;
|
||||||
this._model.size = 0;
|
this._model.size = { storage: 0, file: 0 };
|
||||||
this._model.originalFiles = null;
|
this._model.originalFiles = null;
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
this._model.save(),
|
this._model.save(),
|
||||||
@@ -227,27 +234,39 @@ export default class Repository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute the size of the repository in bite.
|
* Compute the size of the repository in term of storage and number of files.
|
||||||
*
|
*
|
||||||
* @returns The size of the repository in bite
|
* @returns The size of the repository in bite
|
||||||
*/
|
*/
|
||||||
async computeSize(): Promise<number> {
|
async computeSize(): Promise<{
|
||||||
if (this._model.status != "ready") return 0;
|
/**
|
||||||
if (this._model.size) return this._model.size;
|
* Size of the repository in bit
|
||||||
|
*/
|
||||||
|
storage: number;
|
||||||
|
/**
|
||||||
|
* The number of files
|
||||||
|
*/
|
||||||
|
file: number;
|
||||||
|
}> {
|
||||||
|
if (this._model.status != "ready") return { storage: 0, file: 0 };
|
||||||
|
if (this._model.size.file) return this._model.size;
|
||||||
function recursiveCount(files) {
|
function recursiveCount(files) {
|
||||||
let total = 0;
|
const out = { storage: 0, file: 0 };
|
||||||
for (const name in files) {
|
for (const name in files) {
|
||||||
const file = files[name];
|
const file = files[name];
|
||||||
if (file.size) {
|
if (file.size) {
|
||||||
total += file.size as number;
|
out.storage += file.size as number;
|
||||||
|
out.file++;
|
||||||
} else if (typeof file == "object") {
|
} else if (typeof file == "object") {
|
||||||
total += recursiveCount(file);
|
const r = recursiveCount(file);
|
||||||
|
out.storage += r.storage;
|
||||||
|
out.file += r.file;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return total;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
const files = await this.files({ force: false });
|
const files = await this.files();
|
||||||
this._model.size = recursiveCount(files);
|
this._model.size = recursiveCount(files);
|
||||||
await this._model.save();
|
await this._model.save();
|
||||||
return this._model.size;
|
return this._model.size;
|
||||||
@@ -291,7 +310,7 @@ export default class Repository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get size() {
|
get size() {
|
||||||
if (this._model.status != "ready") return 0;
|
if (this._model.status != "ready") return { storage: 0, file: 0 };
|
||||||
return this._model.size;
|
return this._model.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,8 +46,14 @@ const AnonymizedRepositorySchema = new Schema({
|
|||||||
default: new Date(),
|
default: new Date(),
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
type: Number,
|
storage: {
|
||||||
default: 0,
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
file: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,10 @@ export interface IAnonymizedRepository {
|
|||||||
};
|
};
|
||||||
pageView: number;
|
pageView: number;
|
||||||
lastView: Date;
|
lastView: Date;
|
||||||
size: number;
|
size: {
|
||||||
|
storage: number;
|
||||||
|
file: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAnonymizedRepositoryDocument
|
export interface IAnonymizedRepositoryDocument
|
||||||
|
|||||||
+14
-3
@@ -29,14 +29,25 @@ router.get("/", async (req: express.Request, res: express.Response) => {
|
|||||||
router.get("/quota", async (req: express.Request, res: express.Response) => {
|
router.get("/quota", async (req: express.Request, res: express.Response) => {
|
||||||
try {
|
try {
|
||||||
const user = await getUser(req);
|
const user = await getUser(req);
|
||||||
|
const repositories = await user.getRepositories();
|
||||||
const sizes = await Promise.all(
|
const sizes = await Promise.all(
|
||||||
(await user.getRepositories())
|
repositories
|
||||||
.filter((r) => r.status == "ready")
|
.filter((r) => r.status == "ready")
|
||||||
.map((r) => r.computeSize())
|
.map((r) => r.computeSize())
|
||||||
);
|
);
|
||||||
res.json({
|
res.json({
|
||||||
used: sizes.reduce((sum, i) => sum + i, 0),
|
storage: {
|
||||||
total: config.DEFAULT_QUOTA,
|
used: sizes.reduce((sum, i) => sum + i.storage, 0),
|
||||||
|
total: config.DEFAULT_QUOTA,
|
||||||
|
},
|
||||||
|
file: {
|
||||||
|
used: sizes.reduce((sum, i) => sum + i.file, 0),
|
||||||
|
total: 0,
|
||||||
|
},
|
||||||
|
repository: {
|
||||||
|
used: repositories.length,
|
||||||
|
total: 20,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, res);
|
handleError(error, res);
|
||||||
|
|||||||
Reference in New Issue
Block a user