Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update device settings page to better handle server reload and force page refresh for settings changes #11403

Merged
12 changes: 10 additions & 2 deletions kolibri/plugins/device/assets/src/composables/useDeviceRestart.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@
*/

import { ref } from 'kolibri.lib.vueCompositionApi';
import heartbeat from 'kolibri.heartbeat';
import client from 'kolibri.client';
import clientFactory from 'kolibri.utils.clientFactory';
import urls from 'kolibri.urls';
import plugin_data from 'plugin_data';

// The refs are defined in the outer scope so they can be used as a shared store
const restarting = ref(false);
const canRestart = plugin_data.canRestart;

// Use this for checking if the device is restarting to avoid triggering
// the connection error detection.
const baseClient = clientFactory();

// POST to /api/device/devicerestart
export function restartDevice() {
return client({
Expand All @@ -20,7 +26,7 @@ export function restartDevice() {
}

export function isDeviceRestarting() {
return client({
return baseClient({
url: urls['kolibri:core:devicerestart'](),
})
.then(resp => Boolean(resp.data))
Expand All @@ -32,6 +38,7 @@ function restart() {
return Promise.reject('Device restart is not supported with current server configuration');
}
restarting.value = true;
heartbeat.stopPolling();
let statusPromise = restartDevice();
const checkStatus = expectedStatus => {
return statusPromise.then(status => {
Expand All @@ -48,7 +55,8 @@ function restart() {
// First wait for the device to be restarting
return checkStatus(true).then(() => {
// Then wait for it to have finished restarting
checkStatus(false).then(() => {
return checkStatus(false).then(() => {
heartbeat.startPolling();
restarting.value = false;
});
});
Expand Down
3 changes: 0 additions & 3 deletions kolibri/plugins/device/assets/src/modules/deviceInfo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@ export default {
getDataLoading(state) {
return state.dataLoading;
},
canRestart() {
return plugin_data.canRestart;
},
isRemoteContent() {
return plugin_data.isRemoteContent;
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

<KModal
:title="$tr('serverRestart')"
:submitText="coreString('continueAction')"
:cancelText="coreString('cancelAction')"
:submitText="restarting ? null : coreString('continueAction')"
:cancelText="restarting ? null : coreString('cancelAction')"
@submit="handleSubmit"
@cancel="$emit('cancel')"
>
Expand All @@ -13,6 +13,11 @@
<p class="description">
{{ getMessage() }}
</p>
<template v-if="restarting">
&nbsp;
<KCircularLoader />
&nbsp;
</template>
<div v-if="changedSetting === 'add' && path.writable === true">
<KCheckbox
:checked="confirmationChecked"
Expand All @@ -37,13 +42,17 @@
props: {
changedSetting: {
type: String, // primary, remove, add, plugins
required: true,
default: null,
},
path: {
type: Object,
required: false,
default: null,
},
restarting: {
type: Boolean,
default: false,
},
},
data() {
return {
Expand All @@ -52,6 +61,9 @@
},
methods: {
getMessage() {
if (!this.changedSetting) {
return this.$tr('serverRestartDescription');
}
let message = '';
switch (this.changedSetting) {
case 'primary':
Expand Down
46 changes: 33 additions & 13 deletions kolibri/plugins/device/assets/src/views/DeviceSettingsPage/api.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,60 @@
import isEqual from 'lodash/isEqual';
import pickBy from 'lodash/pickBy';
import client from 'kolibri.client';
import urls from 'kolibri.urls';

const url = urls['kolibri:core:devicesettings']();

const _dataCache = {};

export function getDeviceSettings() {
return client({ url }).then(({ data }) => {
Object.assign(_dataCache, data);
return {
languageId: data.language_id,
landingPage: data.landing_page,
allowGuestAccess: data.allow_guest_access,
allowLearnerUnassignedResourceAccess: data.allow_learner_unassigned_resource_access,
allowPeerUnlistedChannelImport: data.allow_peer_unlisted_channel_import,
allowOtherBrowsersToConnect: data.allow_other_browsers_to_connect,
extraSettings: data.extra_settings,
extraSettings: {
// Destructure the extra_settings object
// to ensure we are returning a novel object
// from the one stored in the _dataCache
...data.extra_settings,
},
primaryStorageLocation: data.primary_storage_location,
secondaryStorageLocations: data.secondary_storage_locations,
// Spread the secondary storage locations array to ensure
// we are returning a novel array from the one stored in the _dataCache
secondaryStorageLocations: [...data.secondary_storage_locations],
};
});
}

// PATCH to /api/device/devicesettings with a new settings
export function saveDeviceSettings(settings) {
const serverSettings = {
language_id: settings.languageId,
landing_page: settings.landingPage,
allow_guest_access: settings.allowGuestAccess,
allow_learner_unassigned_resource_access: settings.allowLearnerUnassignedResourceAccess,
allow_peer_unlisted_channel_import: settings.allowPeerUnlistedChannelImport,
allow_other_browsers_to_connect: settings.allowOtherBrowsersToConnect,
extra_settings: settings.extraSettings,
primary_storage_location: settings.primaryStorageLocation,
secondary_storage_locations: settings.secondaryStorageLocations,
};
const data = pickBy(serverSettings, (value, key) => !isEqual(value, _dataCache[key]));
if (Object.keys(data).length === 0) {
return Promise.resolve(false);
}
return client({
url,
method: 'PATCH',
data: {
language_id: settings.languageId,
landing_page: settings.landingPage,
allow_guest_access: settings.allowGuestAccess,
allow_learner_unassigned_resource_access: settings.allowLearnerUnassignedResourceAccess,
allow_peer_unlisted_channel_import: settings.allowPeerUnlistedChannelImport,
allow_other_browsers_to_connect: settings.allowOtherBrowsersToConnect,
extra_settings: settings.extraSettings,
primary_storage_location: settings.primaryStorageLocation,
secondary_storage_locations: settings.secondaryStorageLocations,
},
data,
}).then(response => {
Object.assign(_dataCache, response.data); // Update the cache
return true;
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,22 +144,22 @@
:text="$tr('changeLocation')"
:primary="true"
appearance="basic-link"
:disabled="!multipleWritablePaths || isRemoteContent"
:disabled="!multipleWritablePaths || isRemoteContent || !canRestart"
:class="{ 'disabled': !multipleWritablePaths }"
@click="showChangePrimaryLocationModal = true"
/>
</p>
<KButton
v-if="browserLocationMatchesServerURL && (secondaryStorageLocations.length === 0)"
v-if="secondaryStorageLocations.length === 0"
:text="$tr('addLocation')"
:disabled="isRemoteContent"
:disabled="isRemoteContent || !canRestart"
appearance="raised-button"
secondary
@click="showAddStorageLocationModal = true"
/>
</div>

<div v-show="browserLocationMatchesServerURL && (secondaryStorageLocations.length > 0)">
<div v-show="secondaryStorageLocations.length > 0">
<h2>
{{ $tr('secondaryStorage') }}
</h2>
Expand All @@ -173,7 +173,7 @@
hasDropdown
secondary
appearance="raised-button"
:disabled="isRemoteContent"
:disabled="isRemoteContent || !canRestart"
:text="coreString('optionsLabel')"
>
<template #menu>
Expand Down Expand Up @@ -336,6 +336,11 @@
@submit="handleServerRestart"
/>

<ServerRestartModal
v-if="restarting"
:restarting="true"
/>

</KPageContainer>
</DeviceAppBarPage>

Expand Down Expand Up @@ -391,7 +396,7 @@
},
mixins: [commonCoreStrings, commonDeviceStrings],
setup() {
const { restart } = useDeviceRestart();
const { canRestart, restart, restarting } = useDeviceRestart();
const { plugins, fetchPlugins, togglePlugin } = usePlugins();
const dataPlugins = ref(null);

Expand Down Expand Up @@ -419,7 +424,14 @@
return !unchanged;
}

return { restart, dataPlugins, checkPluginChanges, checkAndTogglePlugins };
return {
canRestart,
restart,
restarting,
dataPlugins,
checkPluginChanges,
checkAndTogglePlugins,
};
},
data() {
return {
Expand Down Expand Up @@ -458,8 +470,8 @@
};
},
computed: {
...mapGetters(['isAppContext', 'isPageLoading']),
...mapGetters('deviceInfo', ['canRestart', 'isRemoteContent']),
...mapGetters(['isAppContext', 'isPageLoading', 'snackbarIsVisible']),
...mapGetters('deviceInfo', ['isRemoteContent']),
pageTitle() {
return this.deviceString('deviceManagementTitle');
},
Expand Down Expand Up @@ -488,12 +500,6 @@
storageLocationOptions() {
return [this.$tr('addStorageLocation'), this.$tr('removeStorageLocation')];
},
browserLocationMatchesServerURL() {
return (
window.location.hostname.includes('127.0.0.1') ||
window.location.hostname.includes('localhost')
);
},
notEnoughFreeSpace() {
return this.freeSpace === 0;
},
Expand Down Expand Up @@ -761,6 +767,8 @@
} = this.getContentSettings();
this.getExtraSettings();

const pluginsChanged = this.checkPluginChanges();

this.checkAndTogglePlugins();

this.saveDeviceSettings({
Expand All @@ -774,15 +782,36 @@
secondaryStorageLocations: this.secondaryStorageLocations,
primaryStorageLocation: this.primaryStorageLocation,
})
.then(() => {
this.$store.dispatch('createSnackbar', this.$tr('saveSuccessNotification'));
this.showRestartModal = false;
if (this.restartSetting !== null) {
this.restart();
this.restartSetting = null;
.then(didSave => {
didSave = didSave || pluginsChanged;
if (didSave) {
this.$store.commit('CORE_CREATE_SNACKBAR', {
text: this.$tr('saveSuccessNotification'),
autoDismiss: true,
duration: 2000,
});
this.showRestartModal = false;
if (this.canRestart && this.restartSetting !== null) {
this.restartSetting = null;
return this.restart().then(() => didSave);
}
}
return didSave;
})
.then(shouldReload => {
if (shouldReload) {
if (this.snackbarIsVisible) {
const unwatch = this.$watch('snackbarIsVisible', () => {
unwatch && unwatch();
window.location.reload();
});
} else {
window.location.reload();
}
}
})
.catch(() => {
.catch(err => {
console.error(err);
this.$store.dispatch('createSnackbar', this.$tr('saveFailureNotification'));
});
},
Expand Down