Skip to content

Commit e647b9f

Browse files
authored
Merge pull request #32443 from nextcloud/port/background-job-admin
2 parents 697b83b + e8e0f97 commit e647b9f

File tree

10 files changed

+254
-141
lines changed

10 files changed

+254
-141
lines changed

apps/settings/lib/Settings/Admin/Server.php

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -33,28 +33,25 @@
3333
use OCP\IConfig;
3434
use OCP\IDBConnection;
3535
use OCP\IL10N;
36+
use OCP\IURLGenerator;
3637
use OCP\Settings\IDelegatedSettings;
3738

3839
class Server implements IDelegatedSettings {
3940
use TProfileHelper;
4041

41-
/** @var IDBConnection */
42-
private $connection;
43-
/** @var IInitialState */
44-
private $initialStateService;
45-
/** @var ProfileManager */
46-
private $profileManager;
47-
/** @var ITimeFactory */
48-
private $timeFactory;
49-
/** @var IConfig */
50-
private $config;
51-
/** @var IL10N $l */
52-
private $l;
42+
private IDBConnection $connection;
43+
private IInitialState $initialStateService;
44+
private ProfileManager $profileManager;
45+
private ITimeFactory $timeFactory;
46+
private IConfig $config;
47+
private IL10N $l;
48+
private IURLGenerator $urlGenerator;
5349

5450
public function __construct(IDBConnection $connection,
5551
IInitialState $initialStateService,
5652
ProfileManager $profileManager,
5753
ITimeFactory $timeFactory,
54+
IURLGenerator $urlGenerator,
5855
IConfig $config,
5956
IL10N $l) {
6057
$this->connection = $connection;
@@ -63,27 +60,29 @@ public function __construct(IDBConnection $connection,
6360
$this->timeFactory = $timeFactory;
6461
$this->config = $config;
6562
$this->l = $l;
63+
$this->urlGenerator = $urlGenerator;
6664
}
6765

6866
/**
6967
* @return TemplateResponse
7068
*/
7169
public function getForm() {
72-
$parameters = [
73-
// Background jobs
74-
'backgroundjobs_mode' => $this->config->getAppValue('core', 'backgroundjobs_mode', 'ajax'),
75-
'lastcron' => $this->config->getAppValue('core', 'lastcron', false),
76-
'cronMaxAge' => $this->cronMaxAge(),
77-
'cronErrors' => $this->config->getAppValue('core', 'cronErrors'),
78-
'cli_based_cron_possible' => function_exists('posix_getpwuid'),
79-
'cli_based_cron_user' => function_exists('posix_getpwuid') ? posix_getpwuid(fileowner(\OC::$configDir . 'config.php'))['name'] : '',
80-
'profileEnabledGlobally' => $this->profileManager->isProfileEnabled(),
81-
];
70+
// Background jobs
71+
$this->initialStateService->provideInitialState('backgroundJobsMode', $this->config->getAppValue('core', 'backgroundjobs_mode', 'ajax'));
72+
$this->initialStateService->provideInitialState('lastCron', (int)$this->config->getAppValue('core', 'lastcron', '0'));
73+
$this->initialStateService->provideInitialState('cronMaxAge', $this->cronMaxAge());
74+
$this->initialStateService->provideInitialState('cronErrors', $this->config->getAppValue('core', 'cronErrors'));
75+
$this->initialStateService->provideInitialState('cliBasedCronPossible', function_exists('posix_getpwuid'));
76+
$this->initialStateService->provideInitialState('cliBasedCronUser', function_exists('posix_getpwuid') ? posix_getpwuid(fileowner(\OC::$configDir . 'config.php'))['name'] : '');
77+
$this->initialStateService->provideInitialState('backgroundJobsDocUrl', $this->urlGenerator->linkToDocs('admin-background-jobs'));
8278

79+
// Profile page
8380
$this->initialStateService->provideInitialState('profileEnabledGlobally', $this->profileManager->isProfileEnabled());
8481
$this->initialStateService->provideInitialState('profileEnabledByDefault', $this->isProfileEnabledByDefault($this->config));
8582

86-
return new TemplateResponse('settings', 'settings/admin/server', $parameters, '');
83+
return new TemplateResponse('settings', 'settings/admin/server', [
84+
'profileEnabledGlobally' => $this->profileManager->isProfileEnabled(),
85+
], '');
8786
}
8887

8988
protected function cronMaxAge(): int {

apps/settings/src/admin.js

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,6 @@ window.addEventListener('DOMContentLoaded', () => {
1414
})
1515
})
1616

17-
$('#backgroundjobs span.crondate').tooltip({ placement: 'top' })
18-
19-
$('#backgroundjobs input').change(() => {
20-
if ($(this).is(':checked')) {
21-
const mode = $(this).val()
22-
if (mode === 'ajax' || mode === 'webcron' || mode === 'cron') {
23-
OCP.AppConfig.setValue('core', 'backgroundjobs_mode', mode, {
24-
success: () => {
25-
// clear cron errors on background job mode change
26-
OCP.AppConfig.deleteKey('core', 'cronErrors')
27-
}
28-
})
29-
}
30-
}
31-
})
32-
3317
$('#shareAPIEnabled').change(() => {
3418
$('#shareAPI p:not(#enable)').toggleClass('hidden', !this.checked)
3519
})
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
<!--
2+
- @copyright 2022 Carl Schwan <[email protected]>
3+
-
4+
- @author Carl Schwan <[email protected]>
5+
-
6+
- @license GNU AGPL version 3 or any later version
7+
-
8+
- This program is free software: you can redistribute it and/or modify
9+
- it under the terms of the GNU Affero General Public License as
10+
- published by the Free Software Foundation, either version 3 of the
11+
- License, or (at your option) any later version.
12+
-
13+
- This program is distributed in the hope that it will be useful,
14+
- but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
- GNU Affero General Public License for more details.
17+
-
18+
- You should have received a copy of the GNU Affero General Public License
19+
- along with this program. If not, see <http://www.gnu.org/licenses/>.
20+
-
21+
-->
22+
23+
<template>
24+
<SettingsSection :title="t('settings', 'Background jobs')"
25+
:description="t('settings', `For the server to work properly, it's important to configure background jobs correctly. Cron is the recommended setting. Please see the documentation for more information.`)"
26+
:doc-url="backgroundJobsDocUrl">
27+
28+
<template v-if="lastCron !== 0">
29+
<span v-if="oldExecution" class="error">
30+
{{ t('settings', 'Last job execution ran {time}. Something seems wrong.', {time: relativeTime}) }}
31+
</span>
32+
33+
<span v-else-if="longExecutionNotCron" class="warning">
34+
{{ t('settings', "Some jobs haven’t been executed since {maxAgeRelativeTime}. Please consider increasing the execution frequency.", {maxAgeRelativeTime}) }}
35+
</span>
36+
37+
<span class="warning" v-else-if="longExecutionCron">
38+
{{ t('settings', "Some jobs haven’t been executed since {maxAgeRelativeTime}. Please consider switching to system cron.", {maxAgeRelativeTime}) }}
39+
</span>
40+
41+
<span v-else>
42+
{{ t('settings', 'Last job ran {relativeTime}.', {relativeTime}) }}
43+
</span>
44+
</template>
45+
46+
<span class="error" v-else>
47+
{{ t('settings', 'Background job didn’t run yet!') }}
48+
</span>
49+
50+
<CheckboxRadioSwitch type="radio"
51+
:checked.sync="backgroundJobsMode"
52+
name="backgroundJobsMode"
53+
value="ajax"
54+
class="ajaxSwitch"
55+
@update:checked="onBackgroundJobModeChanged">
56+
{{ t('settings', 'AJAX') }}
57+
</CheckboxRadioSwitch>
58+
<em>{{ t('settings', 'Execute one task with each page loaded. Use case: Single user instance.') }}</em>
59+
60+
<CheckboxRadioSwitch type="radio"
61+
:checked.sync="backgroundJobsMode"
62+
name="backgroundJobsMode"
63+
value="webcron"
64+
@update:checked="onBackgroundJobModeChanged">
65+
{{ t('settings', 'Webcron') }}
66+
</CheckboxRadioSwitch>
67+
<em>{{ t('settings', 'cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP. Use case: Very small instance (1–5 users depending on the usage).') }}</em>
68+
69+
<CheckboxRadioSwitch type="radio"
70+
:checked.sync="backgroundJobsMode"
71+
value="cron"
72+
name="backgroundJobsMode"
73+
v-if="cliBasedCronPossible"
74+
@update:checked="onBackgroundJobModeChanged">
75+
{{ t('settings', 'Cron (Recommended)') }}
76+
</CheckboxRadioSwitch>
77+
<em v-if="cliBasedCronPossible">{{ cronLabel }}</em>
78+
<em v-else>
79+
{{ t('settings', 'To run this you need the PHP POSIX extension. See {linkstart}PHP documentation{linkend} for more details.', {
80+
linkstart: '<a href="https://www.php.net/manual/en/book.posix.php">',
81+
linkend: '</a>',
82+
}) }}
83+
</em>
84+
</SettingsSection>
85+
</template>
86+
87+
<script>
88+
import { loadState } from '@nextcloud/initial-state'
89+
import { showError } from '@nextcloud/dialogs'
90+
import CheckboxRadioSwitch from '@nextcloud/vue/dist/Components/CheckboxRadioSwitch'
91+
import SettingsSection from '@nextcloud/vue/dist/Components/SettingsSection'
92+
import moment from '@nextcloud/moment'
93+
import axios from '@nextcloud/axios'
94+
import { generateOcsUrl } from '@nextcloud/router'
95+
import confirmPassword from '@nextcloud/password-confirmation'
96+
97+
const lastCron = loadState('settings', 'lastCron')
98+
const cronMaxAge = loadState('settings', 'cronMaxAge', '')
99+
const backgroundJobsMode = loadState('settings', 'backgroundJobsMode', 'cron')
100+
const cliBasedCronPossible = loadState('settings', 'cliBasedCronPossible', true)
101+
const cliBasedCronUser = loadState('settings', 'cliBasedCronUser', 'www-data')
102+
const backgroundJobsDocUrl = loadState('settings', 'backgroundJobsDocUrl')
103+
104+
export default {
105+
name: 'BackgroundJob',
106+
107+
components: {
108+
CheckboxRadioSwitch,
109+
SettingsSection,
110+
},
111+
112+
data() {
113+
return {
114+
lastCron,
115+
cronMaxAge,
116+
backgroundJobsMode,
117+
cliBasedCronPossible,
118+
cliBasedCronUser,
119+
backgroundJobsDocUrl,
120+
relativeTime: moment(lastCron * 1000).fromNow(),
121+
maxAgeRelativeTime: moment(cronMaxAge * 1000).fromNow(),
122+
}
123+
},
124+
computed: {
125+
cronLabel() {
126+
let desc = t('settings', 'Use system cron service to call the cron.php file every 5 minutes. Recommended for all instances.')
127+
if (this.cliBasedCronPossible) {
128+
desc += ' ' + t('settings', 'The cron.php needs to be executed by the system user "{user}".', { user: this.cliBasedCronUser })
129+
}
130+
return desc
131+
},
132+
oldExecution() {
133+
return Date.now() / 1000 - this.lastCron > 600
134+
},
135+
longExecutionNotCron() {
136+
return Date.now() / 1000 - this.cronMaxAge > 12 * 3600 && this.backgroundJobsMode !== 'cron'
137+
},
138+
longExecutionCron() {
139+
return Date.now() / 1000 - this.cronMaxAge > 12 * 3600 && this.backgroundJobsMode === 'cron'
140+
}
141+
},
142+
methods: {
143+
async onBackgroundJobModeChanged(backgroundJobsMode) {
144+
const url = generateOcsUrl('/apps/provisioning_api/api/v1/config/apps/{appId}/{key}', {
145+
appId: 'core',
146+
key: 'backgroundjobs_mode',
147+
})
148+
149+
await confirmPassword()
150+
151+
try {
152+
const { data } = await axios.post(url, {
153+
value: backgroundJobsMode
154+
})
155+
this.handleResponse({
156+
status: data.ocs?.meta?.status
157+
})
158+
} catch (e) {
159+
this.handleResponse({
160+
errorMessage: t('settings', 'Unable to update background job mode'),
161+
error: e,
162+
})
163+
}
164+
},
165+
async handleResponse({ status, errorMessage, error }) {
166+
if (status === 'ok') {
167+
await this.deleteError()
168+
} else {
169+
showError(errorMessage)
170+
console.error(errorMessage, error)
171+
}
172+
},
173+
async deleteError() {
174+
// clear cron errors on background job mode change
175+
const url = generateOcsUrl('/apps/provisioning_api/api/v1/config/apps/{appId}/{key}', {
176+
appId: 'core',
177+
key: 'cronErrors',
178+
})
179+
180+
await confirmPassword()
181+
182+
try {
183+
await axios.delete(url)
184+
} catch (error) {
185+
console.error(error)
186+
}
187+
}
188+
},
189+
}
190+
</script>
191+
192+
<style lang="scss" scoped>
193+
.error {
194+
margin-top: 8px;
195+
padding: 5px;
196+
border-radius: var(--border-radius);
197+
color: var(--color-primary-text);
198+
background-color: var(--color-error);
199+
width: initial;
200+
}
201+
.warning {
202+
margin-top: 8px;
203+
padding: 5px;
204+
border-radius: var(--border-radius);
205+
color: var(--color-primary-text);
206+
background-color: var(--color-warning);
207+
width: initial;
208+
}
209+
.ajaxSwitch {
210+
margin-top: 1rem;
211+
}
212+
</style>

apps/settings/src/main-admin-basic-settings.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import '@nextcloud/dialogs/styles/toast.scss'
2929
import logger from './logger'
3030

3131
import ProfileSettings from './components/BasicSettings/ProfileSettings'
32+
import BackgroundJob from './components/BasicSettings/BackgroundJob'
3233

3334
__webpack_nonce__ = btoa(getRequestToken())
3435

@@ -43,7 +44,10 @@ Vue.mixin({
4344
},
4445
})
4546

47+
const BackgroundJobView = Vue.extend(BackgroundJob)
48+
new BackgroundJobView().$mount('#vue-admin-background-job')
49+
4650
if (profileEnabledGlobally) {
4751
const ProfileSettingsView = Vue.extend(ProfileSettings)
48-
new ProfileSettingsView().$mount('.vue-admin-profile-settings')
52+
new ProfileSettingsView().$mount('#vue-admin-profile-settings')
4953
}

0 commit comments

Comments
 (0)