- mail_settings_controller for setting smtp settings - added view ror rjecting dataset for editor - added new model AppConfig for stroing appwide config values - better validate_chesum.ts command with process chunking - added vue3 apps 'BasicSettings' like email, profile settings - started with 2 multilingual capabilities - npm updates
This commit is contained in:
parent
010bead723
commit
b06ccae603
67 changed files with 7820 additions and 1463 deletions
191
resources/js/apps/settings/basic_settings/BackgroundJob.vue
Normal file
191
resources/js/apps/settings/basic_settings/BackgroundJob.vue
Normal file
|
@ -0,0 +1,191 @@
|
|||
<template>
|
||||
<NcSettingsSection :name="t('settings', 'Background jobs')" :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.`,
|
||||
)" :doc-url="backgroundJobsDocUrl">
|
||||
|
||||
<template v-if="lastCron !== 0">
|
||||
<NcNoteCard v-if="oldExecution" type="danger">
|
||||
{{ t('settings', `Last job execution ran {time}. Something seems wrong.`, {
|
||||
time: relativeTime,
|
||||
timestamp: lastCron
|
||||
}) }}
|
||||
</NcNoteCard>
|
||||
|
||||
<!-- <NcNoteCard v-else-if="longExecutionCron" type="warning">
|
||||
{{ t('settings', `Some jobs have not been executed since {maxAgeRelativeTime}. Please consider increasing the execution frequency.`, {maxAgeRelativeTime}) }}
|
||||
</NcNoteCard> -->
|
||||
|
||||
|
||||
<NcNoteCard v-else type="success">
|
||||
{{ t('settings', 'Last job ran {relativeTime}.', { relativeTime }) }}
|
||||
</NcNoteCard>
|
||||
</template>
|
||||
|
||||
<NcNoteCard v-else type="danger">
|
||||
'Background job did not run yet!'
|
||||
</NcNoteCard>
|
||||
|
||||
|
||||
</NcSettingsSection>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { usePage } from '@inertiajs/vue3';
|
||||
import { loadState } from '@/utils/initialState';
|
||||
import { showError } from '@/utils/toast';
|
||||
// import { generateOcsUrl } from '@nextcloud/router';
|
||||
// import { confirmPassword } from '@nextcloud/password-confirmation';
|
||||
import axios from 'axios';
|
||||
import dayjs from '@/utils/dayjs';
|
||||
|
||||
import NcNoteCard from '@/Components/NCNoteCard.vue';
|
||||
import NcSettingsSection from '@/Components/NcSettingsSection.vue';
|
||||
import { translate as t } from '@/utils/tethyscloud-l10n';
|
||||
// import { useLocaleStore } from '@/Stores/locale';
|
||||
|
||||
// import '@nextcloud/password-confirmation/dist/style.css';
|
||||
|
||||
// const lastCron: number = 1723807502; //loadState('settings', 'lastCron'); //1723788607
|
||||
const cronMaxAge: number = 1724046901;//loadState('settings', 'cronMaxAge', 0); //''
|
||||
const backgroundJobsMode: string = loadState('settings', 'backgroundJobsMode', 'cron'); //cron
|
||||
const cliBasedCronPossible = loadState('settings', 'cliBasedCronPossible', true); //true
|
||||
const cliBasedCronUser = loadState('settings', 'cliBasedCronUser', 'www-data'); //www-data
|
||||
const backgroundJobsDocUrl: string = loadState('settings', 'backgroundJobsDocUrl'); //https://docs.nextcloud.com/server/29/go.php?to=admin-background-jobs
|
||||
|
||||
// await loadTranslations('settings');
|
||||
|
||||
export default {
|
||||
name: 'BackgroundJob',
|
||||
|
||||
components: {
|
||||
NcSettingsSection,
|
||||
NcNoteCard,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
// lastCron: 0,
|
||||
cronMaxAge: cronMaxAge,
|
||||
backgroundJobsMode: backgroundJobsMode,
|
||||
cliBasedCronPossible: cliBasedCronPossible,
|
||||
cliBasedCronUser: cliBasedCronUser,
|
||||
backgroundJobsDocUrl: backgroundJobsDocUrl,
|
||||
// relativeTime: dayjs(this.lastCron * 1000).fromNow(),
|
||||
// maxAgeRelativeTime: dayjs(cronMaxAge * 1000).fromNow(),
|
||||
t: t,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
lastCron(): number {
|
||||
return usePage().props.lastCron as number;
|
||||
|
||||
},
|
||||
relativeTime() {
|
||||
return dayjs.unix(this.lastCron).fromNow(); // Calculate relative time for lastCron
|
||||
},
|
||||
maxAgeRelativeTime() {
|
||||
return dayjs.unix(this.cronMaxAge).fromNow(); // Calculate relative time for cronMaxAge
|
||||
},
|
||||
cronLabel() {
|
||||
let desc = 'Use system cron service to call the cron.php file every 5 minutes.';
|
||||
if (this.cliBasedCronPossible) {
|
||||
desc +=
|
||||
'<br>' +
|
||||
'The cron.php needs to be executed by the system account "{user}".', { user: this.cliBasedCronUser };
|
||||
} else {
|
||||
desc +=
|
||||
'<br>' +
|
||||
|
||||
'The PHP POSIX extension is required. See {linkstart}PHP documentation{linkend} for more details.',
|
||||
{
|
||||
linkstart:
|
||||
'<a target="_blank" rel="noreferrer nofollow" class="external" href="https://www.php.net/manual/en/book.posix.php">',
|
||||
linkend: '</a>',
|
||||
}
|
||||
}
|
||||
return desc;
|
||||
},
|
||||
oldExecution() {
|
||||
return (dayjs().unix() - this.lastCron) > 600; // older than 10 minutes
|
||||
},
|
||||
|
||||
longExecutionCron() {
|
||||
//type of cron job and greater than 24h
|
||||
// let test = dayjs.unix(this.cronMaxAge).format('YYYY-MM-DD HH:mm:ss');
|
||||
return (dayjs().unix() - this.cronMaxAge) > 24 * 3600 && this.backgroundJobsMode === 'cron';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async onBackgroundJobModeChanged(backgroundJobsMode: string) {
|
||||
const url = generateOcsUrl('/apps/provisioning_api/api/v1/config/apps/{appId}/{key}', {
|
||||
appId: 'core',
|
||||
key: 'backgroundjobs_mode',
|
||||
});
|
||||
|
||||
// await confirmPassword();
|
||||
|
||||
try {
|
||||
const { data } = await axios.post(url, {
|
||||
value: backgroundJobsMode,
|
||||
});
|
||||
this.handleResponse({
|
||||
status: data.ocs?.meta?.status,
|
||||
});
|
||||
} catch (e) {
|
||||
this.handleResponse({
|
||||
errorMessage: t('settings', 'Unable to update background job mode'),
|
||||
error: e,
|
||||
});
|
||||
}
|
||||
},
|
||||
async handleResponse({ status, errorMessage, error }) {
|
||||
if (status === 'ok') {
|
||||
await this.deleteError();
|
||||
} else {
|
||||
showError(errorMessage);
|
||||
console.error(errorMessage, error);
|
||||
}
|
||||
},
|
||||
async deleteError() {
|
||||
// clear cron errors on background job mode change
|
||||
const url = generateOcsUrl('/apps/provisioning_api/api/v1/config/apps/{appId}/{key}', {
|
||||
appId: 'core',
|
||||
key: 'cronErrors',
|
||||
});
|
||||
|
||||
// await confirmPassword();
|
||||
|
||||
try {
|
||||
await axios.delete(url);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.error {
|
||||
margin-top: 8px;
|
||||
padding: 5px;
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--color-primary-element-text);
|
||||
background-color: var(--color-error);
|
||||
width: initial;
|
||||
}
|
||||
|
||||
.warning {
|
||||
margin-top: 8px;
|
||||
padding: 5px;
|
||||
border-radius: var(--border-radius);
|
||||
color: var(--color-primary-element-text);
|
||||
background-color: var(--color-warning);
|
||||
width: initial;
|
||||
}
|
||||
|
||||
.ajaxSwitch {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
</style>
|
198
resources/js/apps/settings/basic_settings/MailSettings.vue
Normal file
198
resources/js/apps/settings/basic_settings/MailSettings.vue
Normal file
|
@ -0,0 +1,198 @@
|
|||
<template>
|
||||
<NcSettingsSection :name="t('settings', 'Email server')" :description="t(
|
||||
'settings',
|
||||
`It is important to set up this server to be able to send emails, like for password reset and notifications.`,
|
||||
)" :doc-url="linkToDocs('admin-email')">
|
||||
<NotificationBar v-if="flash.message" color="success" :icon="mdiAlertBoxOutline">
|
||||
{{ flash.message }}
|
||||
</NotificationBar>
|
||||
|
||||
<!-- from address for sendmail -->
|
||||
<!-- <div v-show="form.mail_smtp_mode === 'sendmail'" class="flex flex-col space-y-2">
|
||||
<label for="mail_sendmailmode" class="font-medium">{{ t('settings', 'Sendmail mode') }}</label>
|
||||
<FormControl name="mail_sendmailmode" id="mail_sendmailmode" v-model="form.mailSendmailmode"
|
||||
:options="mailSendmailmodeOptions">
|
||||
</FormControl>
|
||||
</div> -->
|
||||
|
||||
<!-- <form id="mail_general_settings_form" @submit.prevent="saveMailSettingsForm" class="space-y-6">
|
||||
<p>
|
||||
<span id="mail_settings_msg" class="text-red-500">{{ message }}</span>
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col space-y-2">
|
||||
<label for="mail_smtpmode" class="font-medium">{{ t('settings', 'Send mode') }}</label>
|
||||
<FormControl type="select" name="mail_smtpmode" id="mail_smtpmode" v-model="form.mail_smtp_mode"
|
||||
@change="handleModeChange" :options="mailSmtpmodeOptions">
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div v-show="form.mail_smtp_mode === 'smtp'" class="flex flex-col space-y-2">
|
||||
<label for="mail_smtpsecure" class="font-medium">{{ t('settings', 'Encryption') }}</label>
|
||||
<FormControl type="select" name="mail_smtpsecure" id="mail_smtpsecure" v-model="form.mail_smtpsecure"
|
||||
:options="mailSmtpsecureOptions">
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col space-y-2">
|
||||
<label for="mail_from_address" class="font-medium">{{ t('settings', 'From address') }}</label>
|
||||
<div class="flex space-x-2">
|
||||
<FormControl type="text" name="mail_from_address" id="mail_from_address"
|
||||
v-model="form.mail_from_address" :placeholder="t('settings', 'Email')" class="flex-grow" />
|
||||
<span class="text-gray-600">@</span>
|
||||
<FormControl type="text" name="mail_domain" id="mail_domain" placeholder="example.com"
|
||||
v-model="form.mail_domain" class="flex-grow" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-show="form.mail_smtp_mode === 'smtp'" class="flex flex-col space-y-2">
|
||||
<label for="mail_smtphost" class="font-medium">{{ t('settings', 'Server address') }}</label>
|
||||
<div class="flex space-x-2">
|
||||
<FormControl type="text" name="mail_smtphost" id="mail_smtphost" placeholder="smtp.example.com"
|
||||
v-model="form.mail_smtphost" class="flex-grow" />
|
||||
<span class="text-gray-600">:</span>
|
||||
<FormControl type="text" inputmode="numeric" name="mail_smtpport" id="mail_smtpport"
|
||||
:placeholder="t('settings', 'Port')" v-model="form.mail_smtpport" class="flex-shrink" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-show="form.mail_smtp_mode === 'smtp'" class="flex flex-col space-y-2">
|
||||
<label for="mail_smtpauthtype" class="font-medium">{{ t('settings', 'Authentication') }}</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<input type="checkbox" name="mail_smtpauth" id="mail_smtpauth" v-model="form.mail_smtpauth"
|
||||
class="form-checkbox" />
|
||||
<label for="mail_smtpauth" class="font-medium">{{ t('settings', 'Authentication required') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BaseButtons>
|
||||
<BaseButton type="submit" color="info" label="Submit" :class="{ 'opacity-25': form.processing }"
|
||||
:disabled="form.processing == true" />
|
||||
</BaseButtons>
|
||||
|
||||
</form> -->
|
||||
|
||||
<!-- <form id="mail_credentials_settings" class="mt-6">
|
||||
<div v-show="form.mail_smtpauth && form.mail_smtp_mode === 'smtp'" class="flex flex-col space-y-2">
|
||||
<label for="mail_smtpname" class="font-medium">{{ t('settings', 'Credentials') }}</label>
|
||||
<FormControl type="text" name="mail_smtpname" id="mail_smtpname"
|
||||
:placeholder="t('settings', 'SMTP Login')" v-model="mail_smtpname" />
|
||||
<FormControl type="password" name="mail_smtppassword" id="mail_smtppassword"
|
||||
placeholder="t('settings', 'SMTP Password')" v-model="mail_smtppassword" />
|
||||
<input id="mail_credentials_settings_submit" type="button" :value="t('settings', 'Save')"
|
||||
@click="storeCredentialsForm" class="mt-2 btn btn-primary" />
|
||||
</div>
|
||||
</form> -->
|
||||
|
||||
<div class="mt-6">
|
||||
<em>{{ t('settings', 'Test and verify email settings') }}</em>
|
||||
<BaseButtons>
|
||||
<BaseButton type="submit" color="info" name="testemail" id="testemail"
|
||||
:label="t('settings', 'Test email')" @click="sendTestEmail" :small="true" />
|
||||
</BaseButtons>
|
||||
<span id="sendtestmail_msg" class="text-blue-500 ml-4">{{ testMailMessage }}</span>
|
||||
</div>
|
||||
|
||||
</NcSettingsSection>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import axios from 'axios';
|
||||
import { ref, ComputedRef, computed } from 'vue';
|
||||
import { useForm, usePage } from '@inertiajs/vue3';
|
||||
import NcSettingsSection from '@/Components/NcSettingsSection.vue';
|
||||
import { translate as t } from '@/utils/tethyscloud-l10n';
|
||||
import BaseButton from '@/Components/BaseButton.vue';
|
||||
import BaseButtons from '@/Components/BaseButtons.vue';
|
||||
// import FormControl from '@/Components/FormControl.vue';
|
||||
import { stardust } from '@eidellev/adonis-stardust/client';
|
||||
import NotificationBar from '@/Components/NotificationBar.vue';
|
||||
import { mdiAlertBoxOutline } from '@mdi/js';
|
||||
import Notification from '@/utils/toast';
|
||||
|
||||
const flash: ComputedRef<any> = computed(() => {
|
||||
// let test = usePage();
|
||||
// console.log(test);
|
||||
return usePage().props.flash;
|
||||
});
|
||||
|
||||
|
||||
const message = ref('');
|
||||
const testMailMessage = ref('');
|
||||
|
||||
const form = useForm({
|
||||
mail_smtp_mode: 'smtp',
|
||||
mail_smtpsecure: '',
|
||||
mailSendmailmode: 'smtp',
|
||||
mail_from_address: '',
|
||||
mail_domain: '',
|
||||
mail_smtphost: '',
|
||||
mail_smtpport: '',
|
||||
mail_smtpauthtype: 'LOGIN',
|
||||
mail_smtpauth: false,
|
||||
});
|
||||
|
||||
const mail_smtpname = ref('');
|
||||
const mail_smtppassword = ref('');
|
||||
|
||||
const mailSmtpmodeOptions = ref({
|
||||
'smtp': 'SMTP',
|
||||
'sendmail': 'Sendmail',
|
||||
});
|
||||
|
||||
const mailSmtpsecureOptions = ref({
|
||||
'': 'None/STARTTLS',
|
||||
ssl: 'SSL',
|
||||
});
|
||||
|
||||
|
||||
// const mailSendmailmodeOptions = ref({
|
||||
// smtp: 'smtp (-bs)',
|
||||
// pipe: 'pipe (-t -i)',
|
||||
// });
|
||||
|
||||
const handleModeChange = () => {
|
||||
// Logic to update visibility based on selected mailSmtpmode
|
||||
};
|
||||
|
||||
|
||||
const saveMailSettingsForm = async () => {
|
||||
// Logic to save the email server settings
|
||||
await form.post(stardust.route('settings.mail.store'));
|
||||
};
|
||||
const storeCredentialsForm = () => {
|
||||
// Logic to save the email credentials
|
||||
};
|
||||
|
||||
const sendTestEmail = async () => {
|
||||
// Logic to send a test email
|
||||
try {
|
||||
const response = await axios.post(stardust.route('settings.mail.send'));
|
||||
|
||||
if (response.data && response.data.success) {
|
||||
Notification.showSuccess(response.data.message);
|
||||
}
|
||||
} catch (error) {
|
||||
Notification.showError(error.message)
|
||||
}
|
||||
// ;
|
||||
// if (response.data.success) {
|
||||
// // responseMessage.value = response.data.message;
|
||||
// Notification.showSuccess(response.data.message);
|
||||
// } else {
|
||||
// // responseMessage.value = 'An error occurred.';
|
||||
// Notification.showError('An error occurred.')
|
||||
// }
|
||||
};
|
||||
|
||||
const linkToDocs = (page: string) => {
|
||||
return `https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/${page}.html`;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,78 @@
|
|||
<template>
|
||||
<div id="profile-settings" class="section">
|
||||
<h2 class="inlineblock">
|
||||
{{ t('settings', 'Profile') }}
|
||||
</h2>
|
||||
|
||||
<p class="settings-hint">
|
||||
{{ t('settings', 'Enable or disable profile by default for new accounts.') }}
|
||||
</p>
|
||||
|
||||
<NcCheckboxRadioSwitch type="switch" :checked.sync="initialProfileEnabledByDefault"
|
||||
@update:checked="onProfileDefaultChange">
|
||||
{{ t('settings', 'Enable') }}
|
||||
</NcCheckboxRadioSwitch>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
// import { loadState } from '@nextcloud/initial-state'
|
||||
import { loadState } from '@/utils/initialState';
|
||||
import { showError } from '@/utils/toast.js';
|
||||
|
||||
import { saveProfileDefault } from '../../service/ProfileService.js'
|
||||
import { validateBoolean } from '../../utils/validate.js'
|
||||
import logger from '../../logger.ts'
|
||||
|
||||
// import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
|
||||
|
||||
const profileEnabledByDefault = loadState('settings', 'profileEnabledByDefault', true)
|
||||
|
||||
export default {
|
||||
name: 'ProfileSettings',
|
||||
|
||||
components: {
|
||||
NcCheckboxRadioSwitch,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
initialProfileEnabledByDefault: profileEnabledByDefault,
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async onProfileDefaultChange(isEnabled: boolean) {
|
||||
if (validateBoolean(isEnabled)) {
|
||||
await this.updateProfileDefault(isEnabled)
|
||||
}
|
||||
},
|
||||
|
||||
async updateProfileDefault(isEnabled: boolean) {
|
||||
try {
|
||||
const responseData = await saveProfileDefault(isEnabled)
|
||||
this.handleResponse({
|
||||
isEnabled,
|
||||
status: responseData.ocs?.meta?.status,
|
||||
})
|
||||
} catch (e) {
|
||||
this.handleResponse({
|
||||
errorMessage: t('settings', 'Unable to update profile default setting'),
|
||||
error: e,
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
handleResponse({ isEnabled, status, errorMessage, error }) {
|
||||
if (status === 'ok') {
|
||||
this.initialProfileEnabledByDefault = isEnabled
|
||||
} else {
|
||||
showError(errorMessage)
|
||||
logger.error(errorMessage, error)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css" scoped></style>
|
Loading…
Add table
editor.link_modal.header
Reference in a new issue