Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,15 @@ module.exports = {
oc_userconfig: true,
dayNames: true,
firstDay: true,
'cypress/globals': true,
},
extends: ['@nextcloud'],
plugins: [
'cypress',
],
extends: [
'@nextcloud',
'plugin:cypress/recommended',
],
rules: {
'no-tabs': 'warn',
// TODO: make sure we fix this as this is bad vue coding style.
Expand Down
10 changes: 5 additions & 5 deletions apps/files/appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,6 @@
'verb' => 'GET',
'root' => '',
],
[
'name' => 'ajax#getStorageStats',
'url' => '/ajax/getstoragestats',
'verb' => 'GET',
],
[
'name' => 'API#getThumbnail',
'url' => '/api/v1/thumbnail/{x}/{y}/{file}',
Expand All @@ -83,6 +78,11 @@
'url' => '/api/v1/recent/',
'verb' => 'GET'
],
[
'name' => 'API#getStorageStats',
'url' => '/api/v1/stats',
'verb' => 'GET'
],
[
'name' => 'API#setConfig',
'url' => '/api/v1/config/{key}',
Expand Down
1 change: 0 additions & 1 deletion apps/files/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
'OCA\\Files\\Command\\Scan' => $baseDir . '/../lib/Command/Scan.php',
'OCA\\Files\\Command\\ScanAppData' => $baseDir . '/../lib/Command/ScanAppData.php',
'OCA\\Files\\Command\\TransferOwnership' => $baseDir . '/../lib/Command/TransferOwnership.php',
'OCA\\Files\\Controller\\AjaxController' => $baseDir . '/../lib/Controller/AjaxController.php',
'OCA\\Files\\Controller\\ApiController' => $baseDir . '/../lib/Controller/ApiController.php',
'OCA\\Files\\Controller\\DirectEditingController' => $baseDir . '/../lib/Controller/DirectEditingController.php',
'OCA\\Files\\Controller\\DirectEditingViewController' => $baseDir . '/../lib/Controller/DirectEditingViewController.php',
Expand Down
1 change: 0 additions & 1 deletion apps/files/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ class ComposerStaticInitFiles
'OCA\\Files\\Command\\Scan' => __DIR__ . '/..' . '/../lib/Command/Scan.php',
'OCA\\Files\\Command\\ScanAppData' => __DIR__ . '/..' . '/../lib/Command/ScanAppData.php',
'OCA\\Files\\Command\\TransferOwnership' => __DIR__ . '/..' . '/../lib/Command/TransferOwnership.php',
'OCA\\Files\\Controller\\AjaxController' => __DIR__ . '/..' . '/../lib/Controller/AjaxController.php',
'OCA\\Files\\Controller\\ApiController' => __DIR__ . '/..' . '/../lib/Controller/ApiController.php',
'OCA\\Files\\Controller\\DirectEditingController' => __DIR__ . '/..' . '/../lib/Controller/DirectEditingController.php',
'OCA\\Files\\Controller\\DirectEditingViewController' => __DIR__ . '/..' . '/../lib/Controller/DirectEditingViewController.php',
Expand Down
57 changes: 0 additions & 57 deletions apps/files/lib/Controller/AjaxController.php

This file was deleted.

14 changes: 14 additions & 0 deletions apps/files/lib/Controller/ApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,20 @@ public function getRecentFiles() {
return new DataResponse(['files' => $files]);
}


/**
* Returns the current logged-in user's storage stats.
*
* @NoAdminRequired
*
* @param ?string $dir the directory to get the storage stats from
* @return JSONResponse
*/
public function getStorageStats($dir = '/'): JSONResponse {
$storageInfo = \OC_Helper::getStorageInfo($dir ?: '/');
return new JSONResponse(['message' => 'ok', 'data' => $storageInfo]);
}

/**
* Change the default sort mode
*
Expand Down
24 changes: 11 additions & 13 deletions apps/files/lib/Controller/ViewController.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,11 @@ protected function renderScript($appName, $scriptName) {
* @return array
* @throws \OCP\Files\NotFoundException
*/
protected function getStorageInfo() {
protected function getStorageInfo(string $dir = '/') {
\OC_Util::setupFS();
$dirInfo = \OC\Files\Filesystem::getFileInfo('/', false);
$rootInfo = \OC\Files\Filesystem::getFileInfo('/', false);

return \OC_Helper::getStorageInfo('/', $dirInfo);
return \OC_Helper::getStorageInfo($dir, $rootInfo ?: null);
}

/**
Expand Down Expand Up @@ -241,18 +241,16 @@ public function index($dir = '', $view = '', $fileid = null, $fileNotFound = fal

$nav->assign('navigationItems', $navItems);

$nav->assign('usage', \OC_Helper::humanFileSize($storageInfo['used']));
if ($storageInfo['quota'] === \OCP\Files\FileInfo::SPACE_UNLIMITED) {
$totalSpace = $this->l10n->t('Unlimited');
} else {
$totalSpace = \OC_Helper::humanFileSize($storageInfo['total']);
}
$nav->assign('total_space', $totalSpace);
$nav->assign('quota', $storageInfo['quota']);
$nav->assign('usage_relative', $storageInfo['relative']);

$contentItems = [];

try {
// If view is files, we use the directory, otherwise we use the root storage
$storageInfo = $this->getStorageInfo(($view === 'files' && $dir) ? $dir : '/');
} catch(\Exception $e) {
$storageInfo = $this->getStorageInfo();
}

$this->initialState->provideInitialState('storageStats', $storageInfo);
$this->initialState->provideInitialState('navigation', $navItems);
$this->initialState->provideInitialState('config', $this->userConfig->getConfigs());

Expand Down
153 changes: 153 additions & 0 deletions apps/files/src/components/NavigationQuota.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<template>
<NcAppNavigationItem v-if="storageStats"
:aria-label="t('files', 'Storage informations')"
:class="{ 'app-navigation-entry__settings-quota--not-unlimited': storageStats.quota >= 0}"
:loading="loadingStorageStats"
:name="storageStatsTitle"
:title="storageStatsTooltip"
class="app-navigation-entry__settings-quota"
data-cy-files-navigation-settings-quota
@click.stop.prevent="debounceUpdateStorageStats">
<ChartPie slot="icon" :size="20" />

<!-- Progress bar -->
<NcProgressBar v-if="storageStats.quota >= 0"
slot="extra"
:error="storageStats.relative > 80"
:value="Math.min(storageStats.relative, 100)" />
</NcAppNavigationItem>
</template>

<script>
import { formatFileSize } from '@nextcloud/files'
import { generateUrl } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
import { showError } from '@nextcloud/dialogs'
import { debounce, throttle } from 'throttle-debounce'
import { translate } from '@nextcloud/l10n'
import axios from '@nextcloud/axios'
import ChartPie from 'vue-material-design-icons/ChartPie.vue'
import NcAppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationItem.js'
import NcProgressBar from '@nextcloud/vue/dist/Components/NcProgressBar.js'

import logger from '../logger.js'
import { subscribe } from '@nextcloud/event-bus'

export default {
name: 'NavigationQuota',

components: {
ChartPie,
NcAppNavigationItem,
NcProgressBar,
},

data() {
return {
loadingStorageStats: false,
storageStats: loadState('files', 'storageStats', null),
}
},

computed: {
storageStatsTitle() {
const usedQuotaByte = formatFileSize(this.storageStats?.used)
const quotaByte = formatFileSize(this.storageStats?.quota)

// If no quota set
if (this.storageStats?.quota < 0) {
return this.t('files', '{usedQuotaByte} used', { usedQuotaByte })
}

return this.t('files', '{used} of {quota} used', {
used: usedQuotaByte,
quota: quotaByte,
})
},
storageStatsTooltip() {
if (!this.storageStats.relative) {
return ''
}

return this.t('files', '{relative}% used', this.storageStats)
},
},

beforeMount() {
/**
* Update storage stats every minute
* TODO: remove when all views are migrated to Vue
*/
setInterval(this.throttleUpdateStorageStats, 60 * 1000)

subscribe('files:file:created', this.throttleUpdateStorageStats)
subscribe('files:file:deleted', this.throttleUpdateStorageStats)
subscribe('files:file:moved', this.throttleUpdateStorageStats)
subscribe('files:file:updated', this.throttleUpdateStorageStats)

subscribe('files:folder:created', this.throttleUpdateStorageStats)
subscribe('files:folder:deleted', this.throttleUpdateStorageStats)
subscribe('files:folder:moved', this.throttleUpdateStorageStats)
subscribe('files:folder:updated', this.throttleUpdateStorageStats)
},

methods: {
// From user input
debounceUpdateStorageStats: debounce(200, function(event) {
this.updateStorageStats(event)
}),
// From interval or event bus
throttleUpdateStorageStats: throttle(1000, function(event) {
this.updateStorageStats(event)
}),

/**
* Update the storage stats
* Throttled at max 1 refresh per minute
*
* @param {Event} [event = null] if user interaction
*/
async updateStorageStats(event = null) {
if (this.loadingStorageStats) {
return
}

this.loadingStorageStats = true
try {
const response = await axios.get(generateUrl('/apps/files/api/v1/stats'))
if (!response?.data?.data) {
throw new Error('Invalid storage stats')
}
this.storageStats = response.data.data
} catch (error) {
logger.error('Could not refresh storage stats', { error })
// Only show to the user if it was manually triggered
if (event) {
showError(t('files', 'Could not refresh storage stats'))
}
} finally {
this.loadingStorageStats = false
}
},

t: translate,
},
}
</script>

<style lang="scss" scoped>
// User storage stats display
.app-navigation-entry__settings-quota {
// Align title with progress and icon
&--not-unlimited::v-deep .app-navigation-entry__title {
margin-top: -4px;
}

progress {
position: absolute;
bottom: 10px;
margin-left: 44px;
width: calc(100% - 44px - 22px);
}
}
</style>
Loading