diff --git a/client/src/components/Apps/AppCard/AppCard.tsx b/client/src/components/Apps/AppCard/AppCard.tsx index de12f6a..1463055 100644 --- a/client/src/components/Apps/AppCard/AppCard.tsx +++ b/client/src/components/Apps/AppCard/AppCard.tsx @@ -42,7 +42,7 @@ export const AppCard = (props: Props): JSX.Element => {
{category.apps.map((app: App) => { - const redirectUrl = urlParser(app.url)[1]; + const [displayUrl, redirectUrl] = urlParser(app.url); let iconEl: JSX.Element = ; @@ -89,8 +89,11 @@ export const AppCard = (props: Props): JSX.Element => { rel="noreferrer" key={`app-${app.id}`} > - {app.icon && iconEl} - {app.name} + {app.icon && iconEl} +
+
{app.name}
+ {displayUrl} +
); })} diff --git a/client/src/components/Apps/AppGrid/AppGrid.tsx b/client/src/components/Apps/AppGrid/AppGrid.tsx index 9bef4e9..288b727 100644 --- a/client/src/components/Apps/AppGrid/AppGrid.tsx +++ b/client/src/components/Apps/AppGrid/AppGrid.tsx @@ -1,6 +1,8 @@ +import { useSelector } from 'react-redux'; import { Link } from 'react-router-dom'; import { Category } from '../../../interfaces'; +import { State } from '../../../store/reducers'; import { Message } from '../../UI'; import { AppCard } from '../AppCard/AppCard'; import classes from './AppGrid.module.css'; @@ -20,6 +22,10 @@ export const AppGrid = (props: Props): JSX.Element => { fromHomepage = false, } = props; + const { + config: { config } + } = useSelector((state: State) => state); + let apps: JSX.Element; if (categories.length) { @@ -28,7 +34,7 @@ export const AppGrid = (props: Props): JSX.Element => { } else { apps = (
- {categories.map( + {categories.filter((category : Category) => !config.hideEmptyCategories || category.apps.length > 0).map( (category: Category): JSX.Element => ( { // Get Redux action creators const dispatch = useDispatch(); - const { getCategories, setEditCategory, setEditApp } = + const { setEditCategory, setEditApp } = bindActionCreators(actionCreators, dispatch); - // Load categories if array is empty - useEffect(() => { - if (!categories.length) { - getCategories(); - } - }, []); - // Form const [modalIsOpen, setModalIsOpen] = useState(false); const [formContentType, setFormContentType] = useState(ContentType.category); diff --git a/client/src/components/Bookmarks/BookmarkGrid/BookmarkGrid.tsx b/client/src/components/Bookmarks/BookmarkGrid/BookmarkGrid.tsx index 6ed99b9..5ab2f88 100644 --- a/client/src/components/Bookmarks/BookmarkGrid/BookmarkGrid.tsx +++ b/client/src/components/Bookmarks/BookmarkGrid/BookmarkGrid.tsx @@ -1,6 +1,8 @@ +import { useSelector } from 'react-redux'; import { Link } from 'react-router-dom'; import { Category } from '../../../interfaces'; +import { State } from '../../../store/reducers'; import { Message } from '../../UI'; import { BookmarkCard } from '../BookmarkCard/BookmarkCard'; import classes from './BookmarkGrid.module.css'; @@ -20,6 +22,10 @@ export const BookmarkGrid = (props: Props): JSX.Element => { fromHomepage = false, } = props; + const { + config: { config } + } = useSelector((state: State) => state); + let bookmarks: JSX.Element; if (categories.length) { @@ -28,7 +34,7 @@ export const BookmarkGrid = (props: Props): JSX.Element => { } else { bookmarks = (
- {categories.map( + {categories.filter((category : Category) => !config.hideEmptyCategories || category.apps.length > 0).map( (category: Category): JSX.Element => ( { // Get Redux action creators const dispatch = useDispatch(); - const { getCategories, setEditCategory, setEditBookmark } = + const { setEditCategory, setEditBookmark } = bindActionCreators(actionCreators, dispatch); - // Load categories if array is empty - useEffect(() => { - if (!categories.length) { - getCategories(); - } - }, []); - // Form const [modalIsOpen, setModalIsOpen] = useState(false); const [formContentType, setFormContentType] = useState(ContentType.category); diff --git a/client/src/components/Settings/UISettings/UISettings.tsx b/client/src/components/Settings/UISettings/UISettings.tsx index 7843998..a78f475 100644 --- a/client/src/components/Settings/UISettings/UISettings.tsx +++ b/client/src/components/Settings/UISettings/UISettings.tsx @@ -283,7 +283,7 @@ export const UISettings = (): JSX.Element => { - {/* HIDE CATEGORIES */} + {/* HIDE BOOKMARKS */} + {/* HIDE EMPTY CATEGORIES */} + + + + + ); diff --git a/client/src/interfaces/Config.ts b/client/src/interfaces/Config.ts index 825535b..97523c3 100644 --- a/client/src/interfaces/Config.ts +++ b/client/src/interfaces/Config.ts @@ -16,6 +16,7 @@ export interface Config { searchSameTab: boolean; hideApps: boolean; hideBookmarks: boolean; + hideEmptyCategories: boolean; hideSearch: boolean; defaultSearchProvider: string; dockerApps: boolean; diff --git a/client/src/interfaces/Forms.ts b/client/src/interfaces/Forms.ts index 8b961d3..74244a3 100644 --- a/client/src/interfaces/Forms.ts +++ b/client/src/interfaces/Forms.ts @@ -23,6 +23,7 @@ export interface OtherSettingsForm { hideHeader: boolean; hideApps: boolean; hideBookmarks: boolean; + hideEmptyCategories: boolean; useOrdering: string; appsSameTab: boolean; bookmarksSameTab: boolean; diff --git a/client/src/store/reducers/app.ts b/client/src/store/reducers/app.ts index 6bbccea..9ebd16a 100644 --- a/client/src/store/reducers/app.ts +++ b/client/src/store/reducers/app.ts @@ -5,8 +5,6 @@ import { Action } from '../actions'; import { categoriesReducer, CategoriesState } from './category'; interface AppsState extends CategoriesState { - loading: boolean; - errors: string | undefined; categories: Category[]; categoryInEdit: Category | null; appInEdit: App | null; diff --git a/client/src/store/reducers/bookmark.ts b/client/src/store/reducers/bookmark.ts index 1a7638d..7accfe3 100644 --- a/client/src/store/reducers/bookmark.ts +++ b/client/src/store/reducers/bookmark.ts @@ -5,8 +5,6 @@ import { Action } from '../actions'; import { categoriesReducer, CategoriesState } from './category'; interface BookmarksState extends CategoriesState { - loading: boolean; - errors: string | undefined; categories: Category[]; categoryInEdit: Category | null; bookmarkInEdit: Bookmark | null; diff --git a/client/src/utility/templateObjects/configTemplate.ts b/client/src/utility/templateObjects/configTemplate.ts index b1bc91c..c59412d 100644 --- a/client/src/utility/templateObjects/configTemplate.ts +++ b/client/src/utility/templateObjects/configTemplate.ts @@ -16,6 +16,7 @@ export const configTemplate: Config = { searchSameTab: false, hideApps: false, hideBookmarks: false, + hideEmptyCategories: true, hideSearch: false, defaultSearchProvider: 'l', dockerApps: false, diff --git a/client/src/utility/templateObjects/settingsTemplate.ts b/client/src/utility/templateObjects/settingsTemplate.ts index db3e689..1a898e0 100644 --- a/client/src/utility/templateObjects/settingsTemplate.ts +++ b/client/src/utility/templateObjects/settingsTemplate.ts @@ -8,6 +8,7 @@ export const otherSettingsTemplate: OtherSettingsForm = { hideHeader: false, hideApps: false, hideBookmarks: false, + hideEmptyCategories: true, useOrdering: 'createdAt', appsSameTab: false, bookmarksSameTab: false, diff --git a/controllers/apps/docker/useDocker.js b/controllers/apps/docker/useDocker.js index 7d5d715..4b1b641 100644 --- a/controllers/apps/docker/useDocker.js +++ b/controllers/apps/docker/useDocker.js @@ -115,7 +115,7 @@ const useDocker = async (apps) => { const icons = labels['flame.icon'] ? labels['flame.icon'].split(';') : []; for (let i = 0; i < names.length; i++) { - const category = categoriesLabels[i] ? categories.find(category => category.name.toUpperCase() === categoriesLabels[i].toUpperCase()) : dockerDefaultCategory; + let category = categoriesLabels[i] ? categories.find(category => category.name.toUpperCase() === categoriesLabels[i].toUpperCase()) : dockerDefaultCategory; if (!category) { category = await createNewCategory(categoriesLabels[i]); if (category) { @@ -129,7 +129,7 @@ const useDocker = async (apps) => { name: names[i] || names[0], url: urls[i] || urls[0], icon: icons[i] || 'docker', - category: category.id, + categoryId: category.id, orderId: orders[i] || 500, }); } @@ -141,7 +141,6 @@ const useDocker = async (apps) => { await app.update({ isPinned: false }); } } - for (const item of dockerApps) { // If app already exists, update it if (apps.some((app) => app.name === item.name)) { diff --git a/controllers/apps/docker/useKubernetes.js b/controllers/apps/docker/useKubernetes.js index 2bb5f83..a70ed81 100644 --- a/controllers/apps/docker/useKubernetes.js +++ b/controllers/apps/docker/useKubernetes.js @@ -67,7 +67,7 @@ const useKubernetes = async (apps) => { const icons = annotations['flame.pawelmalak/icon'] ? annotations['flame.pawelmalak/icon'].split(';') : []; for (let i = 0; i < names.length; i++) { - const category = categoriesLabels[i] ? categories.find(category => category.name.toUpperCase() === categoriesLabels[i].toUpperCase()) : kubernetesDefaultCategory; + let category = categoriesLabels[i] ? categories.find(category => category.name.toUpperCase() === categoriesLabels[i].toUpperCase()) : kubernetesDefaultCategory; if (!category) { category = await createNewCategory(categoriesLabels[i]); if (category) { @@ -81,7 +81,7 @@ const useKubernetes = async (apps) => { name: names[i] || names[0], url: urls[i] || urls[0], icon: icons[i] || 'kubernetes', - category: category.id, + categoryId: category.id, orderId: orders[i] || 500, }); } diff --git a/controllers/apps/getAllApps.js b/controllers/apps/getAllApps.js index 846850b..59f9b60 100644 --- a/controllers/apps/getAllApps.js +++ b/controllers/apps/getAllApps.js @@ -10,12 +10,11 @@ const { useKubernetes, useDocker } = require('./docker'); // @access Public const getAllApps = asyncWrapper(async (req, res, next) => { const { - useOrdering: orderType, - dockerApps: useDockerAPI, - kubernetesApps: useKubernetesAPI, + useOrdering: orderType } = await loadConfig(); - let apps = await loadIntegrationsApps(); + // Load apps to create/update apps from integrations (Docker, Kubernetes, etc.) + await initIntegrationsApps(); // apps visibility const where = req.isAuthenticated ? {} : { isPublic: true }; @@ -44,26 +43,4 @@ const getAllApps = asyncWrapper(async (req, res, next) => { }); }); -const loadIntegrationsApps = asyncWrapper(async () => { - const { - dockerApps: useDockerAPI, - kubernetesApps: useKubernetesAPI, - } = await loadConfig(); - - let apps; - - if (useDockerAPI) { - await useDocker(apps); - } - - if (useKubernetesAPI) { - await useKubernetes(apps); - } - - return apps; -}); - -module.exports = { - getAllApps, - loadIntegrationsApps -} +module.exports = getAllApps; diff --git a/controllers/apps/getIntegrationsApps.js b/controllers/apps/getIntegrationsApps.js new file mode 100644 index 0000000..b71b608 --- /dev/null +++ b/controllers/apps/getIntegrationsApps.js @@ -0,0 +1,42 @@ +const asyncWrapper = require('../../middleware/asyncWrapper'); +const App = require('../../models/App'); +const { Sequelize } = require('sequelize'); +const loadConfig = require('../../utils/loadConfig'); + +const { useKubernetes, useDocker } = require('./docker'); + +// @desc Get all apps +// @route GET /api/integrationsApps +// @access Public +const getIntegrationsApps = asyncWrapper(async (req, res, next) => { + const { + useOrdering: orderType, + dockerApps: useDockerAPI, + kubernetesApps: useKubernetesAPI, + } = await loadConfig(); + + let apps; + + if (useDockerAPI) { + await useDocker(apps); + } + + if (useKubernetesAPI) { + await useKubernetes(apps); + } + + if (process.env.NODE_ENV === 'production') { + // Set header to fetch containers info every time + return res.status(200).setHeader('Cache-Control', 'no-store').json({ + success: true, + data: apps, + }); + } + + res.status(200).json({ + success: true, + data: apps, + }); +}); + +module.exports = getIntegrationsApps; diff --git a/controllers/apps/index.js b/controllers/apps/index.js index 91f7f00..01873b3 100644 --- a/controllers/apps/index.js +++ b/controllers/apps/index.js @@ -4,5 +4,5 @@ module.exports = { deleteApp: require('./deleteApp'), updateApp: require('./updateApp'), reorderApps: require('./reorderApps'), - getAllApps: require('./getAllApps').getAllApps, + getAllApps: require('./getAllApps'), }; diff --git a/controllers/categories/getAllCategories.js b/controllers/categories/getAllCategories.js index 3ec1cb5..8cc13ec 100644 --- a/controllers/categories/getAllCategories.js +++ b/controllers/categories/getAllCategories.js @@ -4,7 +4,7 @@ const App = require('../../models/App'); const Bookmark = require('../../models/Bookmark'); const { Sequelize } = require('sequelize'); const loadConfig = require('../../utils/loadConfig'); -const loadIntegrationsApps = require('../apps/getAllApps').loadIntegrationsApps; +const initIntegrationsApps = require('../../utils/init/initIntegrationsApps'); // @desc Get all categories // @route GET /api/categories @@ -12,8 +12,8 @@ const loadIntegrationsApps = require('../apps/getAllApps').loadIntegrationsApps; const getAllCategories = asyncWrapper(async (req, res, next) => { const { useOrdering: orderType } = await loadConfig(); - // Load apps to create apps from integrations (Docker, Kubernetes, etc.) - await loadIntegrationsApps(); + // Load apps to create/update apps from integrations (Docker, Kubernetes, etc.) + await initIntegrationsApps(); let categories; let output; diff --git a/server.js b/server.js index c6f25f9..7302fdc 100644 --- a/server.js +++ b/server.js @@ -13,6 +13,7 @@ const Sockets = require('./Sockets'); // Utils const initApp = require('./utils/init'); +const initIntegrationsApps = require('./utils/init/initIntegrationsApps'); const Logger = require('./utils/Logger'); const logger = new Logger(); @@ -22,7 +23,10 @@ const logger = new Logger(); // Init app await initApp(); await connectDB(); - await associateModels(); + await associateModels(); + + // Load apps to create/update apps from integrations (Docker, Kubernetes, etc.) + await initIntegrationsApps(); // Create server for Express API and WebSockets const server = http.createServer(); diff --git a/utils/init/initIntegrationsApps.js b/utils/init/initIntegrationsApps.js new file mode 100644 index 0000000..e04282f --- /dev/null +++ b/utils/init/initIntegrationsApps.js @@ -0,0 +1,24 @@ +const { useKubernetes, useDocker } = require('../../controllers/apps/docker'); +const asyncWrapper = require('../../middleware/asyncWrapper'); +const loadConfig = require('../loadConfig'); + +const loadIntegrationsApps = asyncWrapper(async () => { + const { + dockerApps: useDockerAPI, + kubernetesApps: useKubernetesAPI, + } = await loadConfig(); + + let apps; + + if (useDockerAPI) { + await useDocker(apps); + } + + if (useKubernetesAPI) { + await useKubernetes(apps); + } + + return apps; +}); + +module.exports = loadIntegrationsApps; diff --git a/utils/init/initialConfig.json b/utils/init/initialConfig.json index 3f7625e..a01ff1e 100644 --- a/utils/init/initialConfig.json +++ b/utils/init/initialConfig.json @@ -14,6 +14,7 @@ "searchSameTab": false, "hideApps": false, "hideBookmarks": false, + "hideEmptyCategories": true, "hideSearch": false, "defaultSearchProvider": "l", "dockerApps": false,