diff --git a/i18n/en.pot b/i18n/en.pot index eb700e07..635fdeb3 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,23 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2021-02-17T11:58:52.975Z\n" -"PO-Revision-Date: 2021-02-17T11:58:52.975Z\n" - -msgid "App installed successfully" -msgstr "" - -msgid "Failed to install app" -msgstr "" - -msgid "App removed successfully" -msgstr "" - -msgid "Installing app from the app hub..." -msgstr "" - -msgid "Failed to install an app from the app hub" -msgstr "" +"POT-Creation-Date: 2021-02-17T13:54:07.268Z\n" +"PO-Revision-Date: 2021-02-17T13:54:07.268Z\n" msgid "No apps found" msgstr "" @@ -71,6 +56,26 @@ msgstr "" msgid "Search installed custom apps" msgstr "" +msgid "App installed successfully" +msgstr "" + +msgid "Failed to install app: {{errorMessage}}" +msgstr "" + +msgid "Uploading..." +msgstr "" + +msgid "Upload an app to install" +msgstr "" + +msgid "Manually install an app" +msgstr "" + +msgid "" +"Installed apps have access to your DHIS2 data and metadata. Make sure you " +"trust an app before installing it." +msgstr "" + msgid "Preinstalled core apps" msgstr "" diff --git a/package.json b/package.json index 68902616..201f74c1 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,6 @@ "dependencies": { "@dhis2/app-runtime": "^2.7.0", "@dhis2/prop-types": "^2.0.3", - "d2": "^31.8.2", "moment": "2.24", "query-string": "^6.14.0", "react-router-dom": "^5.2.0", diff --git a/src/actions.js b/src/actions.js index e387a9a4..0ff2f991 100644 --- a/src/actions.js +++ b/src/actions.js @@ -1,150 +1,7 @@ -import { getInstance as getD2 } from 'd2' - -const actions = {} - -/* - * Install app from zip file - */ -actions.installApp.subscribe(params => { - const [zipFile, progressCallback] = Array.isArray(params.data) - ? params.data - : [params.data, undefined] - - getD2().then(d2 => { - d2.system - .uploadApp(zipFile, progressCallback) - .then(() => d2.system.reloadApps()) - .then(apps => { - installedAppHub.setState(apps) - - actions.showSnackbarMessage( - i18n.t('App installed successfully') - ) - actions.appInstalled( - zipFile.name.substring(0, zipFile.name.lastIndexOf('.')) - ) - actions.refreshApps() - params.complete() - }) - .catch(err => { - let message = i18n.t('Failed to install app') - if (err.message) { - message += `: ${err.message}` - } - actions.showSnackbarMessage(message) - log.error('Failed to install app:', err.message || err) - actions.refreshApps() - }) - }) -}) - -/* - * Uninstall app - */ -actions.uninstallApp.subscribe(params => { - const appKey = params.data[0] - getD2().then(d2 => { - d2.system.uninstallApp(appKey).then(() => { - actions.showSnackbarMessage(i18n.t('App removed successfully')) - actions.refreshApps() - }) - }) -}) - -/* - * Refresh the list of installed apps - */ -actions.refreshApps.subscribe(() => { - getD2().then(d2 => { - d2.system.reloadApps().then(apps => { - installedAppHub.setState(apps) - }) - }) -}) - -/* - * Load the app hub - */ -actions.loadAppHub.subscribe(async () => { - const d2 = await getD2() - const baseUrl = d2.Api.getApi().baseUrl - - const fetchOptions = { - credentials: 'include', - headers: { - 'Content-Type': 'application/json', - }, - } - - const getDhisVersion = async () => { - const response = await fetch(`${baseUrl}/system/info`, fetchOptions) - const json = await response.json() - //if we're running a dev version remove the snapshot suffix to just keep the dhis version - return json.version.replace('-SNAPSHOT', '') - } - - const version = await getDhisVersion() - - const response = await fetch( - `${baseUrl}/appHub/v1/apps?dhis_version=${version}`, - fetchOptions - ) - - const apps = await response.json() - appHubStore.setState(Object.assign(appHubStore.getState() || {}, { apps })) -}) - -/* - * Install app version from the app hub - */ -actions.installAppVersion.subscribe(params => { - const versionId = params.data[0] - const appHubState = appHubStore.getState() - appHubStore.setState( - Object.assign(appHubState, { - installing: appHubState.installing ? appHubState.installing + 1 : 1, - }) - ) - - getD2().then(d2 => { - actions.showSnackbarMessage( - i18n.t('Installing app from the app hub...') - ) - installAppVersion(versionId, d2) - .then(() => d2.system.reloadApps()) - .then(apps => { - actions.showSnackbarMessage( - i18n.t('App installed successfully') - ) - const appHubState2 = appHubStore.getState() - appHubStore.setState( - Object.assign(appHubState2, { - installing: appHubState2.installing - 1, - }) - ) - installedAppHub.setState(apps) - params.complete(apps) - }) - .catch(err => { - actions.showSnackbarMessage( - `${i18n.t('Failed to install an app from the app hub')}: ${ - err.message - }` - ) - appHubStore.setState( - Object.assign(appHubStore.getState(), { - installing: appHubStore.getState().installing - 1, - }) - ) - log.error(err) - }) - }) -}) - -function installAppVersion(uid, d2) { +function installAppVersion(versionId, d2) { const api = d2.Api.getApi() return new Promise((resolve, reject) => { - api.post(['appHub', uid].join('/'), '', { dataType: 'text' }) + api.post(['appHub', versionId].join('/'), '', { dataType: 'text' }) .then(() => { resolve() }) @@ -153,5 +10,3 @@ function installAppVersion(uid, d2) { }) }) } - -export default actions diff --git a/src/components/AppList/AppList.module.css b/src/components/AppList/AppList.module.css index aed64cd4..7febe00c 100644 --- a/src/components/AppList/AppList.module.css +++ b/src/components/AppList/AppList.module.css @@ -1,3 +1,10 @@ +.header { + font-size: 20px; + font-weight: 500; + margin-top: var(--spacers-dp24); + margin-bottom: var(--spacers-dp16); +} + .searchField { max-width: 420px; } diff --git a/src/components/AppList/index.js b/src/components/AppList/index.js index 461dadb3..ad3039dc 100644 --- a/src/components/AppList/index.js +++ b/src/components/AppList/index.js @@ -9,7 +9,6 @@ import { import React from 'react' import { useHistory } from 'react-router-dom' import { useQueryParam, StringParam, withDefault } from 'use-query-params' -import commonStyles from '../common.module.css' import AppIcon from './AppIcon' import styles from './AppList.module.css' @@ -56,7 +55,7 @@ const AppsWithUpdates = ({ label, apps }) => { } return (
-

{label}

+

{label}

) @@ -71,14 +70,14 @@ const AllApps = ({ label, apps }) => { if (apps.length === 0) { return ( <> -

{i18n.t('No apps found')}

+

{i18n.t('No apps found')}

No apps match your criteria

) } return ( <> -

{label}

+

{label}

) diff --git a/src/components/ManualInstall/ManualInstall.module.css b/src/components/ManualInstall/ManualInstall.module.css new file mode 100644 index 00000000..48e9ed7f --- /dev/null +++ b/src/components/ManualInstall/ManualInstall.module.css @@ -0,0 +1,19 @@ +.header { + font-size: 20px; + font-weight: 500; + margin-top: 0; + margin-bottom: var(--spacers-dp16); +} + +.warning { + font-size: 16px; + color: var(--colors-grey700); +} + +.uploadBtn { + margin-top: var(--spacers-dp16); +} + +.hiddenForm { + display: none; +} diff --git a/src/components/ManualInstall/index.js b/src/components/ManualInstall/index.js index bf5ed70a..9c2fef71 100644 --- a/src/components/ManualInstall/index.js +++ b/src/components/ManualInstall/index.js @@ -1,5 +1,87 @@ -import React from 'react' +import { useAlert, useConfig } from '@dhis2/app-runtime' +import i18n from '@dhis2/d2-i18n' +import { Button } from '@dhis2/ui' +import React, { useState, useRef } from 'react' +import styles from './ManualInstall.module.css' -const ManualInstall = () => null +const uploadApp = (baseUrl, file) => { + const data = new FormData() + data.append('file', file) + return fetch(`${baseUrl}/api/apps`, { + method: 'post', + body: data, + credentials: 'include', + }).then(res => { + if (status >= 300) { + throw new Error(res.statusText) + } + }) +} + +const UploadButton = () => { + const { baseUrl } = useConfig() + const [isUploading, setIsUploading] = useState(false) + const successAlert = useAlert(i18n.t('App installed successfully'), { + success: true, + }) + const errorAlert = useAlert( + ({ error }) => + i18n.t('Failed to install app: {{errorMessage}}', { + errorMessage: error.message, + nsSeparator: '|', + }), + { critical: true } + ) + const formEl = useRef(null) + const inputEl = useRef(null) + const handleClick = () => { + inputEl.current.click() + } + const handleUpload = async event => { + setIsUploading(true) + try { + await uploadApp(baseUrl, event.target.files[0]) + formEl.current.reset() + successAlert.show() + } catch (error) { + errorAlert.show({ error }) + } + setIsUploading(false) + } + + return ( + <> +
+ +
+ + + ) +} + +const ManualInstall = () => ( + <> +

{i18n.t('Manually install an app')}

+

+ {i18n.t( + 'Installed apps have access to your DHIS2 data and metadata. Make sure you trust an app before installing it.' + )} +

+ + +) export default ManualInstall diff --git a/src/components/common.module.css b/src/components/common.module.css deleted file mode 100644 index 3516191b..00000000 --- a/src/components/common.module.css +++ /dev/null @@ -1,6 +0,0 @@ -.h1 { - font-size: 20px; - font-weight: 500; - margin-top: var(--spacers-dp24); - margin-bottom: var(--spacers-dp16); -} diff --git a/yarn.lock b/yarn.lock index 41ceb6df..3157574c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4839,13 +4839,6 @@ cyclist@^1.0.1: resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= -d2@^31.8.2: - version "31.8.2" - resolved "https://registry.yarnpkg.com/d2/-/d2-31.8.2.tgz#1b0f47a3686c01ecaa805d3dc44f056626d9baaf" - integrity sha512-eRmEf7+OslcdVSF4oFCBFBOwd2FEB8829xNqCz79PK49WkhYIy5c/cwEkwZd2sD5bJyeCyNoi3p4/42wC0AZ1Q== - dependencies: - isomorphic-fetch "^2.2.1" - d@1, d@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" @@ -5351,13 +5344,6 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= -encoding@^0.1.11: - version "0.1.12" - resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" - integrity sha1-U4tm8+5izRq1HsMjgp0flIDHS+s= - dependencies: - iconv-lite "~0.4.13" - encoding@^0.1.12: version "0.1.13" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" @@ -7107,7 +7093,7 @@ i18next@^10.3: resolved "https://registry.yarnpkg.com/i18next/-/i18next-10.6.0.tgz#90ffd9f9bc617f34b9a12e037260f524445f7684" integrity sha1-kP/Z+bxhfzS5oS4DcmD1JERfdoQ= -iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13: +iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -7657,7 +7643,7 @@ is-root@2.1.0: resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" integrity sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg== -is-stream@^1.0.1, is-stream@^1.1.0: +is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -7762,13 +7748,6 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= -isomorphic-fetch@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" - dependencies: - node-fetch "^1.0.1" - whatwg-fetch ">=0.10.0" - isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -9650,13 +9629,6 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" -node-fetch@^1.0.1: - version "1.7.2" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.2.tgz#c54e9aac57e432875233525f3c891c4159ffefd7" - dependencies: - encoding "^0.1.11" - is-stream "^1.0.1" - node-forge@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" @@ -14349,10 +14321,6 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: dependencies: iconv-lite "0.4.24" -whatwg-fetch@>=0.10.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" - whatwg-fetch@^3.4.1: version "3.5.0" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.5.0.tgz#605a2cd0a7146e5db141e29d1c62ab84c0c4c868"