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 (
)
@@ -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"