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
13 changes: 10 additions & 3 deletions kolibri/core/content/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ def _should_proxy_request(self, request):
def _get_request_headers(self, request):
return {
"Accept": request.META.get("HTTP_ACCEPT"),
"Accept-Encoding": request.META.get("HTTP_ACCEPT_ENCODING"),
# Don't proxy client's accept headers as it may include br for brotli
# that we cannot rely on having decompression for available on the server.
"Accept-Language": request.META.get("HTTP_ACCEPT_LANGUAGE"),
"Content-Type": request.META.get("CONTENT_TYPE"),
"If-None-Match": request.META.get("HTTP_IF_NONE_MATCH", ""),
Expand Down Expand Up @@ -154,6 +155,9 @@ def _hande_proxied_request(self, request):
response = requests.get(
remote_url, params=qs, headers=self._get_request_headers(request)
)
if response.status_code == 404:
raise Http404("Remote resource not found")
response.raise_for_status()
# If Etag is set on the response we have returned here, any further Etag will not be modified
# by the django etag decorator, so this should allow us to transparently proxy the remote etag.
try:
Expand Down Expand Up @@ -1721,6 +1725,9 @@ def kolibri_studio_status(self, request, **kwargs):
if resp.status_code == 404:
raise requests.ConnectionError("Kolibri Studio URL is incorrect!")
else:
return Response({"status": "online"})
data = resp.json()
data["available"] = True
data["status"] = "online"
return Response(data)
except requests.ConnectionError:
return Response({"status": "offline"})
return Response({"status": "offline", "available": False})
37 changes: 35 additions & 2 deletions kolibri/plugins/learn/assets/src/composables/useDevices.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,52 @@
*/

import { computed, getCurrentInstance, ref } from 'kolibri.lib.vueCompositionApi';
import { NetworkLocationResource } from 'kolibri.resources';
import { NetworkLocationResource, RemoteChannelResource } from 'kolibri.resources';
import { get, set } from '@vueuse/core';
import useMinimumKolibriVersion from 'kolibri.coreVue.composables.useMinimumKolibriVersion';
import { KolibriStudioId } from '../constants';
import { learnStrings } from '../views/commonLearnStrings';
import plugin_data from 'plugin_data';

// The refs are defined in the outer scope so they can be used as a shared store
const currentDevice = ref(null);

const KolibriStudioDeviceData = {
id: KolibriStudioId,
instance_id: KolibriStudioId,
base_url: plugin_data.studio_baseurl,
get device_name() {
return learnStrings.$tr('kolibriLibrary');
},
};

const { isMinimumKolibriVersion } = useMinimumKolibriVersion(0, 16, 0);

function fetchDevices() {
return NetworkLocationResource.list().then(devices => {
return Promise.all([
RemoteChannelResource.getKolibriStudioStatus(),
NetworkLocationResource.list(),
]).then(([studioResponse, devices]) => {
const studio = studioResponse.data;
devices = devices.filter(device => isMinimumKolibriVersion(device.kolibri_version));
if (studio.available && isMinimumKolibriVersion(studio.kolibri_version || '0.15.0')) {
return [
{
...studio,
...KolibriStudioDeviceData,
},
...devices,
];
}
return devices;
});
}

export function setCurrentDevice(id) {
if (id === KolibriStudioId) {
set(currentDevice, KolibriStudioDeviceData);
return Promise.resolve(KolibriStudioDeviceData);
}
return NetworkLocationResource.fetchModel({ id }).then(device => {
set(currentDevice, device);
return device;
Expand Down
45 changes: 9 additions & 36 deletions kolibri/plugins/learn/assets/src/modules/recommended/handlers.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { get } from '@vueuse/core';
import { ContentNodeResource, RemoteChannelResource } from 'kolibri.resources';
import { ContentNodeResource } from 'kolibri.resources';
import samePageCheckGenerator from 'kolibri.utils.samePageCheckGenerator';
import { PageNames, KolibriStudioId } from '../../constants';
import { PageNames } from '../../constants';
import useChannels from '../../composables/useChannels';
import { setCurrentDevice } from '../../composables/useDevices';
import useLearnerResources from '../../composables/useLearnerResources';
import { searchKeys } from '../../composables/useSearch';
import plugin_data from 'plugin_data';

const { channels, fetchChannels } = useChannels();

Expand Down Expand Up @@ -78,39 +77,13 @@ function _showLibrary(store, query, channels, baseurl) {
}

export function showLibrary(store, query, deviceId = null) {
/**
* ToDo: remove if block.
* Currently the channels & contentnode browser apis in studio
* are not able to load content using the the studio base url.
* Once studio is updated, this function will need to be refactored
* to use the else block code only.
*
* The if block is meant for UI viualization purposes only
* during development
*/
if (deviceId === KolibriStudioId) {
RemoteChannelResource.getKolibriStudioStatus().then(({ data }) => {
if (data.status === 'online') {
RemoteChannelResource.fetchCollection().then(channels => {
//This is a hack to return kolibri channels.
store.commit('SET_ROOT_NODES', channels);

store.commit('CORE_SET_PAGE_LOADING', false);
store.commit('CORE_SET_ERROR', null);
store.commit('SET_PAGE_NAME', PageNames.LIBRARY);
return Promise.resolve();
});
}
});
} else {
if (deviceId) {
return setCurrentDevice(deviceId).then(device => {
const baseurl = deviceId === KolibriStudioId ? plugin_data.studio_baseurl : device.base_url;
return fetchChannels({ baseurl }).then(channels => {
return _showLibrary(store, query, channels, baseurl);
});
if (deviceId) {
return setCurrentDevice(deviceId).then(device => {
const baseurl = device.base_url;
return fetchChannels({ baseurl }).then(channels => {
return _showLibrary(store, query, channels, baseurl);
});
}
return _showLibrary(store, query, get(channels));
});
}
return _showLibrary(store, query, get(channels));
}
13 changes: 11 additions & 2 deletions kolibri/plugins/learn/assets/src/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { setClasses, setResumableContentNodes } from '../composables/useLearnerR
import { setContentNodeProgress } from '../composables/useContentNodeProgress';
import { showTopicsTopic, showTopicsContent } from '../modules/topicsTree/handlers';
import { showLibrary } from '../modules/recommended/handlers';
import { PageNames, ClassesPageNames } from '../constants';
import { PageNames, ClassesPageNames, KolibriStudioId } from '../constants';
import LibraryPage from '../views/LibraryPage';
import HomePage from '../views/HomePage';
import TopicsPage from '../views/TopicsPage';
Expand Down Expand Up @@ -45,7 +45,7 @@ function hydrateHomePage() {
});
}

const optionalDeviceIdPathSegment = '/:deviceId([a-f0-9]{32}|kolibri-studio)?';
const optionalDeviceIdPathSegment = `/:deviceId([a-f0-9]{32}|${KolibriStudioId})?`;

export default [
{
Expand Down Expand Up @@ -205,6 +205,15 @@ export default [
name: PageNames.EXPLORE_LIBRARIES,
path: '/explore_libraries',
component: ExploreLibrariesPage,
handler: () => {
if (!get(isUserLoggedIn)) {
router.replace({ name: PageNames.LIBRARY });
return;
}
if (unassignedContentGuard()) {
return unassignedContentGuard();
}
},
},
{
path: '*',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,6 @@
{{ $tr('showingLibraries') }}
</p>
</div>
<LibraryItem
v-if="showKolibriLibrary"
:key="kolibriStudioId"
:deviceId="kolibriStudioId"
:deviceName="learnString('kolibriLibrary')"
deviceIcon="cloud"
:channels="kolibriLibraryChannels"
:showDescription="true"
:totalChannels="totalChannels"
:pinIcon="getPinIcon(true)"
/>
<LibraryItem
v-for="device in pinnedDevices"
:key="device['instance_id']"
Expand All @@ -35,7 +24,7 @@
:deviceIcon="getDeviceIcon(device)"
:channels="device.channels"
:totalChannels="device['total_channels']"
:pinIcon="getPinIcon(isPinned(device['instance_id']))"
:pinIcon="getPinIcon(true)"
@togglePin="handlePinToggle"
/>
<div v-if="areMoreDevicesAvailable">
Expand All @@ -54,7 +43,7 @@
:deviceIcon="getDeviceIcon(device)"
:channels="device.channels"
:totalChannels="device['total_channels']"
:pinIcon="getPinIcon(isPinned(device['instance_id']))"
:pinIcon="getPinIcon(false)"
@togglePin="handlePinToggle"
/>
<KButton
Expand All @@ -75,7 +64,6 @@
import ImmersivePage from 'kolibri.coreVue.components.ImmersivePage';
import commonCoreStrings from 'kolibri.coreVue.mixins.commonCoreStrings';
import { crossComponentTranslator } from 'kolibri.utils.i18n';
import { RemoteChannelResource } from 'kolibri.resources';
import commonLearnStrings from '../commonLearnStrings';
import useChannels from '../../composables/useChannels';
import useDevices from '../../composables/useDevices';
Expand Down Expand Up @@ -108,9 +96,6 @@
data() {
return {
networkDevices: [],
kolibriLibraryChannels: [],
totalChannels: 0,
isKolibriLibraryLoaded: false,
moreDevices: [],
usersPins: [],
};
Expand All @@ -131,11 +116,12 @@
this.moreDevices.length > 0 && this.moreDevices.length < this.unpinnedDevices?.length
);
},
kolibriStudioId() {
return KolibriStudioId;
},
networkDevicesWithChannels() {
return this.networkDevices.filter(device => device.channels?.length > 0);
return this.networkDevices.filter(
device =>
device.channels?.length > 0 &&
(device.instance_id !== KolibriStudioId || this.isSuperuser)
);
},
pageHeaderStyle() {
return {
Expand All @@ -150,12 +136,12 @@
},
pinnedDevices() {
return this.networkDevicesWithChannels.filter(netdev => {
return this.usersPinsDeviceIds.includes(netdev.instance_id);
return (
this.usersPinsDeviceIds.includes(netdev.instance_id) ||
netdev.instance_id === KolibriStudioId
);
});
},
showKolibriLibrary() {
return this.isSuperuser && this.isKolibriLibraryLoaded;
},
unpinnedDevices() {
return this.networkDevicesWithChannels.filter(netdev => {
return !this.usersPinsDeviceIds.includes(netdev.instance_id);
Expand All @@ -171,19 +157,6 @@
});
});

RemoteChannelResource.getKolibriStudioStatus().then(({ data }) => {
if (data.status === 'online') {
RemoteChannelResource.fetchCollection()
.then(channels => {
this.isKolibriLibraryLoaded = true;
this.kolibriLibraryChannels = channels.slice(0, 4);
this.totalChannels = channels.length;
})
.catch(() => {
this.isKolibriLibraryLoaded = true;
});
}
});
this.fetchDevices().then(devices => {
this.networkDevices = devices;
for (const device of this.networkDevices) {
Expand All @@ -199,9 +172,6 @@
});
},
methods: {
isPinned(instance_id) {
return this.usersPinsDeviceIds.includes(instance_id);
},
createPin(instance_id) {
return this.createPinForUser(instance_id).then(response => {
const id = response.id;
Expand Down