diff --git a/app/client/cypress/locators/ApiEditor.json b/app/client/cypress/locators/ApiEditor.json index 0dd7cdb1fe..0370ba9ae3 100644 --- a/app/client/cypress/locators/ApiEditor.json +++ b/app/client/cypress/locators/ApiEditor.json @@ -4,7 +4,7 @@ "createBlankApiCard": ".t--createBlankApiCard", "eachProviderCard": ".t--eachProviderCard", "nameOfApi": ".t--nameOfApi", - "ApiNameField": ".t--action-name-edit-field span", + "ApiNameField": ".bp3-editable-text", "addToPageBtn": ".t--addToPageBtn", "ApiDeleteBtn": ".t--apiFormDeleteBtn", "ApiRunBtn": ".t--apiFormRunBtn", diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index e9e97f1f74..b99463ddcc 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -331,7 +331,7 @@ Cypress.Commands.add("CreateAPI", apiname => { .type(apiname) .should("have.value", apiname) .blur(); - //cy.WaitAutoSave(); + cy.WaitAutoSave(); // Added because api name edit takes some time to // reflect in api sidebar after the call passes. cy.wait(2000); @@ -363,13 +363,17 @@ Cypress.Commands.add("EditApiName", apiname => { Cypress.Commands.add("WaitAutoSave", () => { // wait for save query to trigger cy.wait(200); - cy.wait("@saveQuery"); + cy.wait("@saveAction"); //cy.wait("@postExecute"); }); Cypress.Commands.add("RunAPI", () => { cy.get(ApiEditor.ApiRunBtn).click({ force: true }); - cy.wait("@postExecute"); + cy.wait("@postExecute").should( + "have.nested.property", + "response.body.responseMeta.status", + 200, + ); }); Cypress.Commands.add("SaveAndRunAPI", () => { @@ -1184,6 +1188,7 @@ Cypress.Commands.add("createAndFillApi", (url, parameters) => { cy.get(ApiEditor.ApiNameField).should("be.visible"); cy.expect(response.response.body.responseMeta.success).to.eq(true); cy.get(ApiEditor.ApiNameField) + .click() .invoke("text") .then(text => { const someText = text; diff --git a/app/client/src/api/PluginApi.ts b/app/client/src/api/PluginApi.ts index 577594c666..02202bf4e6 100644 --- a/app/client/src/api/PluginApi.ts +++ b/app/client/src/api/PluginApi.ts @@ -7,8 +7,12 @@ export interface Plugin { name: string; type: "API" | "DB"; packageName: string; + iconLocation?: string; uiComponent: "ApiEditorForm" | "RapidApiEditorForm" | "DbEditorForm"; allowUserDatasources?: boolean; + templates: Record; + responseType?: "TABLE" | "JSON"; + documentationLink?: string; } export interface DatasourceForm { diff --git a/app/client/src/assets/images/MongoDB.png b/app/client/src/assets/images/MongoDB.png deleted file mode 100644 index 16c3330342..0000000000 Binary files a/app/client/src/assets/images/MongoDB.png and /dev/null differ diff --git a/app/client/src/assets/images/MongoDB.svg b/app/client/src/assets/images/MongoDB.svg deleted file mode 100644 index fd88f327c0..0000000000 --- a/app/client/src/assets/images/MongoDB.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/app/client/src/assets/images/Mysql.png b/app/client/src/assets/images/Mysql.png deleted file mode 100644 index 603d634957..0000000000 Binary files a/app/client/src/assets/images/Mysql.png and /dev/null differ diff --git a/app/client/src/assets/images/Postgres-white.svg b/app/client/src/assets/images/Postgres-white.svg deleted file mode 100644 index bc32c7d07d..0000000000 --- a/app/client/src/assets/images/Postgres-white.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/app/client/src/assets/images/Postgres.svg b/app/client/src/assets/images/Postgres.svg deleted file mode 100644 index 74b62e6b41..0000000000 --- a/app/client/src/assets/images/Postgres.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/app/client/src/assets/images/Postgress.png b/app/client/src/assets/images/Postgress.png deleted file mode 100644 index 7e495efbac..0000000000 Binary files a/app/client/src/assets/images/Postgress.png and /dev/null differ diff --git a/app/client/src/assets/images/RestAPI.png b/app/client/src/assets/images/RestAPI.png deleted file mode 100644 index 70d31e600b..0000000000 Binary files a/app/client/src/assets/images/RestAPI.png and /dev/null differ diff --git a/app/client/src/constants/DatasourceEditorConstants.ts b/app/client/src/constants/DatasourceEditorConstants.ts index d767360862..ebc05abed3 100644 --- a/app/client/src/constants/DatasourceEditorConstants.ts +++ b/app/client/src/constants/DatasourceEditorConstants.ts @@ -1 +1,2 @@ export const DATASOURCE_CONSTANT = "DATASOURCE"; +export const APPSMITH_IP_ADDRESS = "18.223.74.85"; diff --git a/app/client/src/constants/HelpConstants.ts b/app/client/src/constants/HelpConstants.ts index 4cd9da45b0..aad2817853 100644 --- a/app/client/src/constants/HelpConstants.ts +++ b/app/client/src/constants/HelpConstants.ts @@ -87,6 +87,10 @@ export const HelpMap = { path: "/core-concepts/apis/taking-inputs-from-widgets", searchKey: "Taking Inputs from Widgets", }, + DATASOURCE_FORM: { + path: "/core-concepts/connecting-to-databases", + searchKey: "Connecting to databases", + }, }; export const HelpBaseURL = "https://docs.appsmith.com"; diff --git a/app/client/src/pages/Editor/DataSourceEditor/DBForm.tsx b/app/client/src/pages/Editor/DataSourceEditor/DBForm.tsx index 6d766a45c0..ac1099eaa4 100644 --- a/app/client/src/pages/Editor/DataSourceEditor/DBForm.tsx +++ b/app/client/src/pages/Editor/DataSourceEditor/DBForm.tsx @@ -2,11 +2,6 @@ import React from "react"; import styled from "styled-components"; import _ from "lodash"; import { DATASOURCE_DB_FORM } from "constants/forms"; -import { REST_PLUGIN_PACKAGE_NAME } from "constants/ApiEditorConstants"; -import { - PLUGIN_PACKAGE_MONGO, - PLUGIN_PACKAGE_POSTGRES, -} from "constants/QueryEditorConstants"; import { Spinner } from "@blueprintjs/core"; import { DATA_SOURCES_EDITOR_URL } from "constants/routes"; import Collapsible from "./Collapsible"; @@ -14,18 +9,17 @@ import history from "utils/history"; import FormLabel from "components/editorComponents/FormLabel"; import { Icon } from "@blueprintjs/core"; import FormTitle from "./FormTitle"; -import ImageAlt from "assets/images/placeholder-image.svg"; -import Postgres from "assets/images/Postgress.png"; -import MongoDB from "assets/images/MongoDB.png"; -import RestTemplateImage from "assets/images/RestAPI.png"; import { ControlProps } from "components/formControls/BaseControl"; import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper"; +import CollapsibleHelp from "components/designSystems/appsmith/help/CollapsibleHelp"; import FormControlFactory from "utils/FormControlFactory"; +import { HelpBaseURL, HelpMap } from "constants/HelpConstants"; import Button from "components/editorComponents/Button"; import { Datasource } from "api/DatasourcesApi"; import { reduxForm, InjectedFormProps, Field } from "redux-form"; import { BaseButton } from "components/designSystems/blueprint/ButtonComponent"; +import { APPSMITH_IP_ADDRESS } from "constants/DatasourceEditorConstants"; interface DatasourceDBEditorProps { onSave: (formValues: Datasource) => void; @@ -42,6 +36,7 @@ interface DatasourceDBEditorProps { loadingFormConfigs: boolean; formConfig: []; isNewDatasource: boolean; + pluginImage: string; } interface DatasourceDBEditorState { @@ -106,6 +101,17 @@ const LoadingContainer = styled(CenteredWrapper)` height: 50%; `; +const StyledOpenDocsIcon = styled(Icon)` + svg { + width: 12px; + height: 18px; + } +`; + +const CollapsibleWrapper = styled.div` + width: max-content; +`; + class DatasourceDBEditor extends React.Component< Props, DatasourceDBEditorState @@ -181,19 +187,6 @@ class DatasourceDBEditor extends React.Component< return !_.isEmpty(errors); }; - getImageSrc = (pluginPackage: string) => { - switch (pluginPackage) { - case PLUGIN_PACKAGE_POSTGRES: - return Postgres; - case PLUGIN_PACKAGE_MONGO: - return MongoDB; - case REST_PLUGIN_PACKAGE_NAME: - return RestTemplateImage; - default: - return ImageAlt; - } - }; - render() { const { loadingFormConfigs, formConfig } = this.props; const content = this.renderDataSourceConfigForm(formConfig); @@ -287,7 +280,6 @@ class DatasourceDBEditor extends React.Component< renderDataSourceConfigForm = (sections: any) => { const { - selectedPluginPackage, isSaving, applicationId, pageId, @@ -321,16 +313,26 @@ class DatasourceDBEditor extends React.Component<
- + + + + {`Whitelist the IP ${APPSMITH_IP_ADDRESS} on your database instance to connect to it. `} + + {"Read more "} + + + + {!_.isNil(sections) ? _.map(sections, this.renderMainSection) : undefined} diff --git a/app/client/src/pages/Editor/DataSourceEditor/DatasourceHome.tsx b/app/client/src/pages/Editor/DataSourceEditor/DatasourceHome.tsx index 7780488519..312c26d3b1 100644 --- a/app/client/src/pages/Editor/DataSourceEditor/DatasourceHome.tsx +++ b/app/client/src/pages/Editor/DataSourceEditor/DatasourceHome.tsx @@ -3,18 +3,12 @@ import styled from "styled-components"; import { connect } from "react-redux"; import { initialize } from "redux-form"; import { Card, Spinner } from "@blueprintjs/core"; -import { getDatasourcePlugins } from "selectors/entitiesSelector"; +import { + getDatasourcePlugins, + getPluginImages, +} from "selectors/entitiesSelector"; import { Plugin } from "api/PluginApi"; import { DATASOURCE_DB_FORM } from "constants/forms"; -import ImageAlt from "assets/images/placeholder-image.svg"; -import Postgres from "assets/images/Postgress.png"; -import MongoDB from "assets/images/MongoDB.png"; -import RestTemplateImage from "assets/images/RestAPI.png"; -import { REST_PLUGIN_PACKAGE_NAME } from "constants/ApiEditorConstants"; -import { - PLUGIN_PACKAGE_POSTGRES, - PLUGIN_PACKAGE_MONGO, -} from "constants/QueryEditorConstants"; import { selectPlugin, createDatasourceFromForm, @@ -120,6 +114,7 @@ interface ReduxDispatchProps { interface ReduxStateProps { plugins: Plugin[]; currentApplication: UserApplication; + pluginImages: Record; } type Props = ReduxStateProps & DatasourceHomeScreenProps & ReduxDispatchProps; @@ -138,21 +133,8 @@ class DatasourceHomeScreen extends React.Component { }); }; - getImageSrc = (packageName: string) => { - switch (packageName) { - case PLUGIN_PACKAGE_POSTGRES: - return Postgres; - case PLUGIN_PACKAGE_MONGO: - return MongoDB; - case REST_PLUGIN_PACKAGE_NAME: - return RestTemplateImage; - default: - return ImageAlt; - } - }; - render() { - const { plugins, isSaving } = this.props; + const { plugins, isSaving, pluginImages } = this.props; return ( @@ -175,7 +157,7 @@ class DatasourceHomeScreen extends React.Component { onClick={() => this.goToCreateDatasource(plugin.id)} > Datasource @@ -193,6 +175,7 @@ class DatasourceHomeScreen extends React.Component { const mapStateToProps = (state: AppState): ReduxStateProps => { return { + pluginImages: getPluginImages(state), plugins: getDatasourcePlugins(state), currentApplication: getCurrentApplication(state), }; diff --git a/app/client/src/pages/Editor/DataSourceEditor/index.tsx b/app/client/src/pages/Editor/DataSourceEditor/index.tsx index 2a9d868b6f..19c4f9a809 100644 --- a/app/client/src/pages/Editor/DataSourceEditor/index.tsx +++ b/app/client/src/pages/Editor/DataSourceEditor/index.tsx @@ -2,7 +2,12 @@ import React from "react"; import { connect } from "react-redux"; import { getFormValues, submit } from "redux-form"; import { AppState } from "reducers"; -import { getPluginPackageFromId } from "selectors/entitiesSelector"; +import _ from "lodash"; +import { + getPluginPackageFromId, + getPluginImages, + getDatasource, +} from "selectors/entitiesSelector"; import { updateDatasource, testDatasource, @@ -19,7 +24,6 @@ import { RouteComponentProps } from "react-router"; interface ReduxStateProps { formData: Datasource; selectedPluginPackage: string; - currentPluginId: string; isSaving: boolean; currentApplication: UserApplication; isTesting: boolean; @@ -27,6 +31,8 @@ interface ReduxStateProps { loadingFormConfigs: boolean; isDeleting: boolean; newDatasource: string; + pluginImages: Record; + pluginId: string; } type Props = ReduxStateProps & @@ -60,12 +66,15 @@ class DataSourceEditor extends React.Component { isDeleting, deleteDatasource, newDatasource, + pluginImages, + pluginId, } = this.props; return ( {datasourceId ? ( { } } -const mapStateToProps = (state: AppState): ReduxStateProps => { +const mapStateToProps = (state: AppState, props: any): ReduxStateProps => { const { datasourcePane } = state.ui; const { datasources, plugins } = state.entities; + const datasource = getDatasource(state, props.match.params.datasourceId); const { formConfigs, loadingFormConfigs } = plugins; const formData = getFormValues(DATASOURCE_DB_FORM)(state) as Datasource; return { + pluginImages: getPluginImages(state), formData, + pluginId: _.get(datasource, "pluginId", ""), selectedPluginPackage: getPluginPackageFromId( state, datasourcePane.selectedPlugin, ), isSaving: datasources.loading, isDeleting: datasources.isDeleting, - currentPluginId: datasourcePane.selectedPlugin, currentApplication: getCurrentApplication(state), isTesting: datasources.isTesting, formConfig: formConfigs[datasourcePane.selectedPlugin] || [], diff --git a/app/client/src/pages/Editor/DataSourceSidebar.tsx b/app/client/src/pages/Editor/DataSourceSidebar.tsx index 8a5f5c831e..9f228e878d 100644 --- a/app/client/src/pages/Editor/DataSourceSidebar.tsx +++ b/app/client/src/pages/Editor/DataSourceSidebar.tsx @@ -10,8 +10,7 @@ import { Colors } from "constants/Colors"; import TreeDropdown from "components/editorComponents/actioncreator/TreeDropdown"; import { BaseTextInput } from "components/designSystems/appsmith/TextInputComponent"; import { getDataSources } from "selectors/editorSelectors"; -import { getPlugins } from "selectors/entitiesSelector"; -import { Plugin } from "api/PluginApi"; +import { getPluginImages } from "selectors/entitiesSelector"; import { initDatasourcePane, storeDatastoreRefs, @@ -22,16 +21,7 @@ import { ControlIcons } from "icons/ControlIcons"; import { theme } from "constants/DefaultTheme"; import { selectPlugin } from "actions/datasourceActions"; import { fetchPluginForm } from "actions/pluginActions"; -import ImageAlt from "assets/images/placeholder-image.svg"; -import Postgres from "assets/images/Postgress.png"; -import MongoDB from "assets/images/MongoDB.png"; -import RestTemplateImage from "assets/images/RestAPI.png"; import { DATA_SOURCES_EDITOR_URL } from "constants/routes"; -import { REST_PLUGIN_PACKAGE_NAME } from "constants/ApiEditorConstants"; -import { - PLUGIN_PACKAGE_POSTGRES, - PLUGIN_PACKAGE_MONGO, -} from "constants/QueryEditorConstants"; import { AppState } from "reducers"; import { Datasource } from "api/DatasourcesApi"; import Fuse from "fuse.js"; @@ -48,7 +38,7 @@ interface ReduxDispatchProps { interface ReduxStateProps { dataSources: Datasource[]; - plugins: Plugin[]; + pluginImages: Record; datastoreRefs: Record; formConfigs: Record; drafts: Record; @@ -253,22 +243,6 @@ class DataSourceSidebar extends React.Component { return search ? fuse.search(search) : dataSources; }; - getImageSource = (pluginId: string) => { - const { plugins } = this.props; - const plugin = plugins.find(plugin => plugin.id === pluginId); - - switch (plugin?.packageName) { - case REST_PLUGIN_PACKAGE_NAME: - return RestTemplateImage; - case PLUGIN_PACKAGE_MONGO: - return MongoDB; - case PLUGIN_PACKAGE_POSTGRES: - return Postgres; - default: - return ImageAlt; - } - }; - renderItem = () => { const { match: { @@ -277,6 +251,7 @@ class DataSourceSidebar extends React.Component { datastoreRefs, deleteDatasource, drafts, + pluginImages, } = this.props; const filteredList = this.getSearchFilteredList(); @@ -292,7 +267,7 @@ class DataSourceSidebar extends React.Component { > @@ -365,7 +340,7 @@ const mapStateToProps = (state: AppState): ReduxStateProps => { return { formConfigs: state.entities.plugins.formConfigs, dataSources: getDataSources(state), - plugins: getPlugins(state), + pluginImages: getPluginImages(state), datastoreRefs: state.ui.datasourcePane.datasourceRefs, drafts, }; diff --git a/app/client/src/pages/Editor/QueryEditor/Form.tsx b/app/client/src/pages/Editor/QueryEditor/Form.tsx index b895d8a62f..f86d5dde89 100644 --- a/app/client/src/pages/Editor/QueryEditor/Form.tsx +++ b/app/client/src/pages/Editor/QueryEditor/Form.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from "react"; +import React, { useState } from "react"; import { formValueSelector, InjectedFormProps, reduxForm } from "redux-form"; import CheckboxField from "components/editorComponents/form/fields/CheckboxField"; import styled, { createGlobalStyle } from "styled-components"; @@ -10,6 +10,7 @@ import { OptionTypeBase, SingleValueProps, } from "react-select"; +import _ from "lodash"; import history from "utils/history"; import { DATA_SOURCES_EDITOR_URL } from "constants/routes"; import TemplateMenu from "./TemplateMenu"; @@ -19,7 +20,6 @@ import DropdownField from "components/editorComponents/form/fields/DropdownField import { BaseButton } from "components/designSystems/blueprint/ButtonComponent"; import { Datasource } from "api/DatasourcesApi"; import { QUERY_EDITOR_FORM_NAME } from "constants/forms"; -import { PLUGIN_PACKAGE_POSTGRES } from "constants/QueryEditorConstants"; import { Colors } from "constants/Colors"; import JSONViewer from "./JSONViewer"; import Table from "./Table"; @@ -28,13 +28,23 @@ import { connect } from "react-redux"; import { AppState } from "reducers"; import ActionNameEditor from "components/editorComponents/ActionNameEditor"; import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField"; -import { EditorModes } from "components/editorComponents/CodeEditor/EditorConfig"; +import { + EditorModes, + EditorSize, +} from "components/editorComponents/CodeEditor/EditorConfig"; +import CollapsibleHelp from "components/designSystems/appsmith/help/CollapsibleHelp"; +import { HelpBaseURL, HelpMap } from "constants/HelpConstants"; +import { + getPluginResponseTypes, + getPluginDocumentationLinks, +} from "selectors/entitiesSelector"; const QueryFormContainer = styled.div` - font-size: 20px; padding: 20px 32px; width: 100%; - max-height: 93vh; + display: flex; + flex-direction: column; + height: calc(100vh - ${props => props.theme.headerHeight}); a { font-size: 14px; line-height: 20px; @@ -45,7 +55,7 @@ const QueryFormContainer = styled.div` border-radius: 4px; border: 1px solid #d0d7dd; font-size: 14px; - height: calc(100vh / 3); + height: calc(100vh / 4); } .statementTextArea { font-size: 14px; @@ -82,17 +92,6 @@ const ActionButton = styled(BaseButton)` } `; -const ResponseContainer = styled.div` - margin-top: 20px; -`; - -const ResponseContent = styled.div` - height: calc( - 100vh - (100vh / 3) - 175px - ${props => props.theme.headerHeight} - ); - overflow: auto; -`; - const DropdownSelect = styled.div` font-size: 14px; `; @@ -194,6 +193,27 @@ const StyledCheckbox = styled(CheckboxField)` } `; +const StyledOpenDocsIcon = styled(Icon)` + svg { + width: 12px; + height: 18px; + } +`; + +const NameWrapper = styled.div` + width: 39%; + display: flex; + justify-content: space-between; + input { + margin: 0; + box-sizing: border-box; + } +`; + +const CollapsibleWrapper = styled.div` + width: 200px; +`; + type QueryFormProps = { onDeleteClick: () => void; onRunClick: () => void; @@ -203,10 +223,9 @@ type QueryFormProps = { dataSources: Datasource[]; DATASOURCES_OPTIONS: any; executedQueryData: { - body: Record[]; + body: Record[] | string; }; applicationId: string; - selectedPluginPackage: string | undefined; runErrorMessage: string | undefined; pageId: string; location: { @@ -216,6 +235,9 @@ type QueryFormProps = { type ReduxProps = { actionName: string; + responseType: string | undefined; + pluginId: string; + documentationLink: string | undefined; }; export type StateAndRouteProps = QueryFormProps & ReduxProps; @@ -235,32 +257,28 @@ const QueryEditorForm: React.FC = (props: Props) => { applicationId, dataSources, executedQueryData, - selectedPluginPackage, createTemplate, runErrorMessage, + pluginId, + responseType, + documentationLink, } = props; const [showTemplateMenu, setMenuVisibility] = useState(true); - - const isSQL = selectedPluginPackage === PLUGIN_PACKAGE_POSTGRES; - const isNewQuery = - new URLSearchParams(window.location.search).get("new") === "true"; - let queryOutput: { - body: Record[]; - } = { body: [] }; - const inputEl = useRef(); + let error = runErrorMessage; + let output: Record[] | null = null; if (executedQueryData) { - if (isSQL && executedQueryData.body.length) { - queryOutput = executedQueryData; + if (_.isString(executedQueryData.body)) { + error = executedQueryData.body; + } else { + output = executedQueryData.body; } } - useEffect(() => { - if (isNewQuery) { - inputEl.current?.select(); - } - }, [isNewQuery]); + const isSQL = responseType === "TABLE"; + const isNewQuery = + new URLSearchParams(window.location.search).get("new") === "true"; const MenuList = (props: MenuListComponentProps<{ children: Node }>) => { return ( @@ -316,7 +334,9 @@ const QueryEditorForm: React.FC = (props: Props) => {
- + + + = (props: Props) => {

@@ -376,55 +398,68 @@ const QueryEditorForm: React.FC = (props: Props) => { )} -

+ +

Query Statement

- {isSQL ? ( - - PostgreSQL docs - - ) : ( - - Mongo docs - + + {documentationLink && ( + + + + {"Documentation "} + + + + )}
- {isNewQuery && showTemplateMenu && selectedPluginPackage ? ( + + {isNewQuery && showTemplateMenu && pluginId ? ( { setMenuVisibility(false); createTemplate(templateString); }} - selectedPluginPackage={selectedPluginPackage} + pluginId={pluginId} /> ) : isSQL ? ( - +
+ +
) : ( - +
+ +
)} = (props: Props) => { )} - {runErrorMessage && ( + {error && ( <>

Query error

- {runErrorMessage} + {error} )} - {executedQueryData && dataSources.length && ( - + {!error && output && dataSources.length && ( + <>

- {executedQueryData.body.length - ? "Query response" - : "No data records to display"} + {output.length ? "Query response" : "No data records to display"}

- - {isSQL ? ( - - ) : ( - - - - )} - + {isSQL ?
: } + )} ); @@ -483,8 +509,15 @@ const QueryEditorForm: React.FC = (props: Props) => { const valueSelector = formValueSelector(QUERY_EDITOR_FORM_NAME); const mapStateToProps = (state: AppState) => { const actionName = valueSelector(state, "name"); + const pluginId = valueSelector(state, "datasource.pluginId"); + const responseTypes = getPluginResponseTypes(state); + const documentationLinks = getPluginDocumentationLinks(state); + return { actionName, + pluginId, + responseType: responseTypes[pluginId], + documentationLink: documentationLinks[pluginId], }; }; diff --git a/app/client/src/pages/Editor/QueryEditor/JSONViewer.tsx b/app/client/src/pages/Editor/QueryEditor/JSONViewer.tsx index 441f47a26d..4e83fbb909 100644 --- a/app/client/src/pages/Editor/QueryEditor/JSONViewer.tsx +++ b/app/client/src/pages/Editor/QueryEditor/JSONViewer.tsx @@ -11,6 +11,10 @@ const OutputContainer = styled.div` padding: 6px; `; +const ResponseContent = styled.div` + overflow: auto; +`; + const Record = styled(Card)` margin: 5px; `; @@ -41,24 +45,28 @@ class JSONOutput extends React.Component { if (!src.length) { return ( - - - - - + + + + + + + ); } return ( - - {src.map((record, index) => { - return ( - - - - ); - })} - + + + {src.map((record, index) => { + return ( + + + + ); + })} + + ); } } diff --git a/app/client/src/pages/Editor/QueryEditor/QueryHomeScreen.tsx b/app/client/src/pages/Editor/QueryEditor/QueryHomeScreen.tsx index ffab105bc1..8b67dee76c 100644 --- a/app/client/src/pages/Editor/QueryEditor/QueryHomeScreen.tsx +++ b/app/client/src/pages/Editor/QueryEditor/QueryHomeScreen.tsx @@ -3,24 +3,16 @@ import styled from "styled-components"; import { Icon, Card, Spinner } from "@blueprintjs/core"; import { connect } from "react-redux"; import { AppState } from "reducers"; -import ImageAlt from "assets/images/placeholder-image.svg"; -import Postgres from "assets/images/Postgress.png"; -import MongoDB from "assets/images/MongoDB.png"; import { createNewQueryName } from "utils/AppsmithUtils"; -import { Plugin } from "api/PluginApi"; import { - getPlugins, getPluginIdsOfPackageNames, + getPluginImages, } from "selectors/entitiesSelector"; import { ActionDataState } from "reducers/entityReducers/actionsReducer"; import { Datasource } from "api/DatasourcesApi"; import history from "utils/history"; import { createActionRequest } from "actions/actionActions"; -import { - PLUGIN_PACKAGE_MONGO, - PLUGIN_PACKAGE_POSTGRES, - PLUGIN_PACKAGE_DBS, -} from "constants/QueryEditorConstants"; +import { PLUGIN_PACKAGE_DBS } from "constants/QueryEditorConstants"; import { Page } from "constants/ReduxActionConstants"; import { QUERY_EDITOR_URL_WITH_SELECTED_PAGE_ID, @@ -131,8 +123,8 @@ type QueryHomeScreenProps = { replace: (data: string) => void; push: (data: string) => void; }; - plugins: Plugin[]; pages: Page[]; + pluginImages: Record; }; class QueryHomeScreen extends React.Component { @@ -161,23 +153,6 @@ class QueryHomeScreen extends React.Component { } }; - getImageSrc = (dataSource: Datasource) => { - const { plugins } = this.props; - const { pluginId } = dataSource; - const plugin = plugins.find( - (plugin: { id: string }) => plugin.id === pluginId, - ); - - switch (plugin?.packageName) { - case PLUGIN_PACKAGE_MONGO: - return MongoDB; - case PLUGIN_PACKAGE_POSTGRES: - return Postgres; - default: - return ImageAlt; - } - }; - render() { const { dataSources, @@ -187,15 +162,9 @@ class QueryHomeScreen extends React.Component { history, location, isCreating, + pluginImages, } = this.props; - const validDataSources: Array = []; - dataSources.forEach(dataSource => { - if (pluginIds?.includes(dataSource.pluginId)) { - validDataSources.push(dataSource); - } - }); - const queryParams: string = location.search; const destinationPageId = new URLSearchParams(location.search).get( "importTo", @@ -224,11 +193,8 @@ class QueryHomeScreen extends React.Component { interactive={false} className="eachDatasourceCard" onClick={() => { - if (validDataSources.length) { - this.handleCreateNewQuery( - validDataSources[0].id, - queryParams, - ); + if (dataSources.length) { + this.handleCreateNewQuery(dataSources[0].id, queryParams); } else { history.push(DATA_SOURCES_EDITOR_URL(applicationId, pageId)); } @@ -237,7 +203,7 @@ class QueryHomeScreen extends React.Component {

Blank Query

- {validDataSources.map(dataSource => { + {dataSources.map(dataSource => { return ( { } > Datasource @@ -271,7 +237,7 @@ class QueryHomeScreen extends React.Component { const mapStateToProps = (state: AppState) => ({ pluginIds: getPluginIdsOfPackageNames(state, PLUGIN_PACKAGE_DBS), - plugins: getPlugins(state), + pluginImages: getPluginImages(state), actions: state.entities.actions, pages: state.entities.pageList.pages, }); diff --git a/app/client/src/pages/Editor/QueryEditor/Table.tsx b/app/client/src/pages/Editor/QueryEditor/Table.tsx index 0c9283ac26..5f69de7c76 100644 --- a/app/client/src/pages/Editor/QueryEditor/Table.tsx +++ b/app/client/src/pages/Editor/QueryEditor/Table.tsx @@ -1,5 +1,8 @@ import React from "react"; -import { TableWrapper } from "components/designSystems/appsmith/TableStyledWrappers"; +import { + TableWrapper, + CellWrapper, +} from "components/designSystems/appsmith/TableStyledWrappers"; import { useTable, useFlexLayout } from "react-table"; import styled from "styled-components"; @@ -7,6 +10,10 @@ interface TableProps { data: Record[]; } +/* + * 310 = height of the table header + rest of the items (excluding editor height) + * 100vh /4 = height of the editor + */ const StyledTableWrapped = styled(TableWrapper)` width: 100%; height: auto; @@ -16,7 +23,7 @@ const StyledTableWrapped = styled(TableWrapper)` overflow: auto; height: auto; max-height: calc( - 100vh - (100vh / 3) - 230px - ${props => props.theme.headerHeight} + 100vh - (100vh / 4) - 310px - ${props => props.theme.headerHeight} ); } } @@ -94,7 +101,9 @@ const Table = (props: TableProps) => { data-rowindex={index} data-colindex={cellIndex} > - {cell.render("Cell")} + + {cell.render("Cell")} + ); })} diff --git a/app/client/src/pages/Editor/QueryEditor/TemplateMenu.tsx b/app/client/src/pages/Editor/QueryEditor/TemplateMenu.tsx index 58dac8cc7e..88fab82ae0 100644 --- a/app/client/src/pages/Editor/QueryEditor/TemplateMenu.tsx +++ b/app/client/src/pages/Editor/QueryEditor/TemplateMenu.tsx @@ -1,6 +1,8 @@ import React from "react"; import styled from "styled-components"; -import Templates from "./Templates"; +import { connect } from "react-redux"; +import { AppState } from "reducers"; +import { getPluginTemplates } from "selectors/entitiesSelector"; const Container = styled.div` display: flex; @@ -42,10 +44,14 @@ const Row = styled.div` interface TemplateMenuProps { createTemplate: (template: any) => void; - selectedPluginPackage: string; + pluginId: string; } -type Props = TemplateMenuProps; +type ReduxProps = { + allPluginTemplates: Record; +}; + +type Props = TemplateMenuProps & ReduxProps; class TemplateMenu extends React.Component { nameInput!: HTMLDivElement | null; @@ -54,17 +60,18 @@ class TemplateMenu extends React.Component { this.nameInput?.focus(); } - fetchTemplate = (queryType: React.ReactText) => { - const { selectedPluginPackage } = this.props; - const allTemplates = Templates[selectedPluginPackage]; + fetchTemplate = (queryType: string) => { + const { pluginId, allPluginTemplates } = this.props; + const pluginTemplates = allPluginTemplates[pluginId]; - if (allTemplates) { - return allTemplates[queryType]; + if (pluginTemplates) { + return pluginTemplates[queryType]; } }; render() { - const { createTemplate } = this.props; + const { createTemplate, allPluginTemplates, pluginId } = this.props; + const pluginTemplates = allPluginTemplates[pluginId]; return ( { Press enter to start with a blank state or select a template.
- { - const template = this.fetchTemplate("create"); - createTemplate(template); - e.stopPropagation(); - }} - > - - Create - - { - const template = this.fetchTemplate("read"); - createTemplate(template); - e.stopPropagation(); - }} - > - - Read - - { - const template = this.fetchTemplate("delete"); - createTemplate(template); - e.stopPropagation(); - }} - > - - Delete - - { - const template = this.fetchTemplate("update"); - createTemplate(template); - e.stopPropagation(); - }} - > - - Update - + {Object.entries(pluginTemplates).map(template => { + const templateKey = template[0]; + + return ( + { + const template = this.fetchTemplate(templateKey); + createTemplate(template); + e.stopPropagation(); + }} + > + + + {templateKey.charAt(0).toUpperCase() + + templateKey.slice(1).toLowerCase()} + + + ); + })}
); } } -export default TemplateMenu; +const mapStateToProps = (state: AppState) => { + return { + allPluginTemplates: getPluginTemplates(state), + }; +}; + +export default connect(mapStateToProps)(TemplateMenu); diff --git a/app/client/src/pages/Editor/QueryEditor/Templates.tsx b/app/client/src/pages/Editor/QueryEditor/Templates.tsx deleted file mode 100644 index bc5e45d842..0000000000 --- a/app/client/src/pages/Editor/QueryEditor/Templates.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { - PLUGIN_PACKAGE_MONGO, - PLUGIN_PACKAGE_POSTGRES, -} from "constants/QueryEditorConstants"; - -const Templates: Record = { - [PLUGIN_PACKAGE_MONGO]: { - create: { - insert: "users", - documents: [ - { - name: "{{Input1.text}}", - email: "{{Input2.text}}", - gender: "{{Dropdown2.selectedOptionValue}}", - }, - ], - }, - read: { - find: "users", - filter: { id: { $gte: "{{Input1.text}}" } }, - sort: { id: 1 }, - limit: 10, - }, - delete: { - delete: "users", - deletes: [{ q: { id: "{{Table1.selectedRow.id}}" } }], - }, - update: { - update: "users", - updates: [ - { - q: { id: "{{Table1.selectedRow.id}}" }, - u: { - name: "{{Input1.text}}", - email: "{{Input2.text}}", - }, - }, - ], - }, - }, - [PLUGIN_PACKAGE_POSTGRES]: { - create: `INSERT INTO users(name, gender) -VALUES ('{{Dropdown1.selectedOptionValue}}', '{{Input2.text}}');`, - read: - "SELECT * FROM users where name like '%{{Input1.text}}%' ORDER BY id LIMIT 10", - delete: `DELETE FROM users WHERE id={{Table1.selectedRow.id}}`, - update: `UPDATE users -Set status='{{Dropdown1.selectedOptionValue}}' -WHERE id={{Table1.selectedRow.id}};`, - }, -}; - -export default Templates; diff --git a/app/client/src/pages/Editor/QueryEditor/helpers.ts b/app/client/src/pages/Editor/QueryEditor/helpers.ts deleted file mode 100644 index 445fbb6cea..0000000000 --- a/app/client/src/pages/Editor/QueryEditor/helpers.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Plugin } from "api/PluginApi"; -import { - PLUGIN_PACKAGE_MONGO, - PLUGIN_PACKAGE_POSTGRES, -} from "constants/QueryEditorConstants"; -import ImageAlt from "assets/images/placeholder-image.svg"; -import Postgres from "assets/images/Postgress.png"; -import MongoDB from "assets/images/MongoDB.png"; - -export const getPluginImage = (plugins: Plugin[], pluginId?: string) => { - const plugin = plugins.find(plugin => plugin.id === pluginId); - switch (plugin?.packageName) { - case PLUGIN_PACKAGE_MONGO: - return MongoDB; - case PLUGIN_PACKAGE_POSTGRES: - return Postgres; - default: - return ImageAlt; - } -}; diff --git a/app/client/src/pages/Editor/QueryEditor/index.tsx b/app/client/src/pages/Editor/QueryEditor/index.tsx index aa28a7a5b1..010955f54f 100644 --- a/app/client/src/pages/Editor/QueryEditor/index.tsx +++ b/app/client/src/pages/Editor/QueryEditor/index.tsx @@ -2,7 +2,6 @@ import React from "react"; import { RouteComponentProps } from "react-router"; import { connect } from "react-redux"; import { getFormValues, change } from "redux-form"; -import _ from "lodash"; import styled from "styled-components"; import { QueryEditorRouteParams } from "constants/routes"; import QueryEditorForm from "./Form"; @@ -16,15 +15,15 @@ import { Datasource } from "api/DatasourcesApi"; import { QueryPaneReduxState } from "reducers/uiReducers/queryPaneReducer"; import { getPluginIdsOfPackageNames, - getPluginPackageFromDatasourceId, getPlugins, + getPluginImages, + getDBDatasources, } from "selectors/entitiesSelector"; import { PLUGIN_PACKAGE_DBS, QUERY_BODY_FIELD, } from "constants/QueryEditorConstants"; import { QueryAction } from "entities/Action"; -import { getPluginImage } from "pages/Editor/QueryEditor/helpers"; import Spinner from "components/editorComponents/Spinner"; import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper"; @@ -52,10 +51,10 @@ type ReduxStateProps = { runErrorMessage: Record; pluginIds: Array | undefined; executedQueryData: any; - selectedPluginPackage: string | undefined; isCreating: boolean; isMoving: boolean; isCopying: boolean; + pluginImages: Record; }; type StateAndRouteProps = RouteComponentProps; @@ -82,9 +81,9 @@ class QueryEditor extends React.Component { match: { params: { queryId }, }, + pluginImages, pluginIds, executedQueryData, - selectedPluginPackage, isCreating, isMoving, isCopying, @@ -107,17 +106,10 @@ class QueryEditor extends React.Component { } const { isRunning, isDeleting } = queryPane; - const validDataSources: Array = []; - dataSources.forEach(dataSource => { - if (pluginIds?.includes(dataSource.pluginId)) { - validDataSources.push(dataSource); - } - }); - - const DATASOURCES_OPTIONS = validDataSources.map(dataSource => ({ + const DATASOURCES_OPTIONS = dataSources.map(dataSource => ({ label: dataSource.name, value: dataSource.id, - image: getPluginImage(this.props.plugins, dataSource.pluginId), + image: pluginImages[dataSource.pluginId], })); return ( @@ -134,7 +126,6 @@ class QueryEditor extends React.Component { dataSources={dataSources} createTemplate={createTemplate} DATASOURCES_OPTIONS={DATASOURCES_OPTIONS} - selectedPluginPackage={selectedPluginPackage} executedQueryData={executedQueryData[queryId]} runErrorMessage={runErrorMessage[queryId]} /> @@ -156,21 +147,16 @@ class QueryEditor extends React.Component { const mapStateToProps = (state: AppState): ReduxStateProps => { const { runErrorMessage } = state.ui.queryPane; const formData = getFormValues(QUERY_EDITOR_FORM_NAME)(state) as QueryAction; - const datasourceId = _.get(formData, "datasource.id"); - const selectedPluginPackage = getPluginPackageFromDatasourceId( - state, - datasourceId, - ); return { + pluginImages: getPluginImages(state), plugins: getPlugins(state), runErrorMessage, pluginIds: getPluginIdsOfPackageNames(state, PLUGIN_PACKAGE_DBS), - dataSources: getDataSources(state), + dataSources: getDBDatasources(state), executedQueryData: state.ui.queryPane.runQuerySuccessData, queryPane: state.ui.queryPane, formData, - selectedPluginPackage, isCreating: state.ui.apiPane.isCreating, isMoving: state.ui.apiPane.isMoving, isCopying: state.ui.apiPane.isCopying, diff --git a/app/client/src/pages/Editor/QuerySidebar.tsx b/app/client/src/pages/Editor/QuerySidebar.tsx index b8a8006659..e8c92b062f 100644 --- a/app/client/src/pages/Editor/QuerySidebar.tsx +++ b/app/client/src/pages/Editor/QuerySidebar.tsx @@ -9,8 +9,6 @@ import EditorSidebar from "pages/Editor/EditorSidebar"; import { QUERY_CONSTANT } from "constants/QueryEditorConstants"; import { QueryEditorRouteParams } from "constants/routes"; import { Datasource } from "api/DatasourcesApi"; -import { getPluginImage } from "pages/Editor/QueryEditor/helpers"; -import { Plugin } from "api/PluginApi"; import { createActionRequest, moveActionRequest, @@ -18,7 +16,7 @@ import { deleteAction, } from "actions/actionActions"; import { changeQuery, initQueryPane } from "actions/queryPaneActions"; -import { getQueryActions, getPlugins } from "selectors/entitiesSelector"; +import { getQueryActions, getPluginImages } from "selectors/entitiesSelector"; import { getNextEntityName } from "utils/AppsmithUtils"; import { getDataSources } from "selectors/editorSelectors"; import { QUERY_EDITOR_URL_WITH_SELECTED_PAGE_ID } from "constants/routes"; @@ -53,7 +51,7 @@ const StyledImage = styled.img` `; interface ReduxStateProps { - plugins: Plugin[]; + pluginImages: Record; queries: ActionDataState; apiPane: ApiPaneReduxState; actions: ActionDataState; @@ -128,10 +126,12 @@ class QuerySidebar extends React.Component { }; renderItem = (query: RestAction) => { + const { pluginImages } = this.props; + return ( @@ -168,7 +168,7 @@ class QuerySidebar extends React.Component { } const mapStateToProps = (state: AppState): ReduxStateProps => ({ - plugins: getPlugins(state), + pluginImages: getPluginImages(state), queries: getQueryActions(state), apiPane: state.ui.apiPane, actions: state.entities.actions, diff --git a/app/client/src/sagas/QueryPaneSagas.ts b/app/client/src/sagas/QueryPaneSagas.ts index 869c58de19..0b969f59d5 100644 --- a/app/client/src/sagas/QueryPaneSagas.ts +++ b/app/client/src/sagas/QueryPaneSagas.ts @@ -20,7 +20,6 @@ import { } from "selectors/editorSelectors"; import { initialize } from "redux-form"; import { AppState } from "reducers"; -import { QUERY_CONSTANT } from "constants/QueryEditorConstants"; import { changeQuery } from "actions/queryPaneActions"; import { getAction } from "selectors/entitiesSelector"; import { RestAction } from "entities/Action"; diff --git a/app/client/src/selectors/entitiesSelector.ts b/app/client/src/selectors/entitiesSelector.ts index 2308048895..e8c308ce2e 100644 --- a/app/client/src/selectors/entitiesSelector.ts +++ b/app/client/src/selectors/entitiesSelector.ts @@ -9,6 +9,7 @@ import { createSelector } from "reselect"; import { Datasource } from "api/DatasourcesApi"; import { Action } from "entities/Action"; import { find } from "lodash"; +import ImageAlt from "assets/images/placeholder-image.svg"; export const getEntities = (state: AppState): AppState["entities"] => state.entities; @@ -124,6 +125,23 @@ export const getDatasourceDraft = (state: AppState, id: string) => { export const getPlugins = (state: AppState) => state.entities.plugins.list; +export const getDBPlugins = createSelector(getPlugins, plugins => + plugins.filter(plugin => plugin.type === QUERY_CONSTANT), +); + +export const getDBDatasources = createSelector( + getDBPlugins, + getEntities, + (dbPlugins, entities) => { + const datasources = entities.datasources.list; + const dbPluginIds = dbPlugins.map(plugin => plugin.id); + + return datasources.filter(datasource => + dbPluginIds.includes(datasource.pluginId), + ); + }, +); + export const getQueryName = (state: AppState, actionId: string): string => { const action = state.entities.actions.find((action: ActionData) => { return action.config.id === actionId; @@ -137,6 +155,7 @@ export const getQueryActions = (state: AppState): ActionDataState => { return action.config.pluginType === QUERY_CONSTANT; }); }; + const getCurrentPageId = (state: AppState) => state.entities.pageList.currentPageId; @@ -144,6 +163,49 @@ export const getDatasourcePlugins = createSelector(getPlugins, plugins => { return plugins.filter(plugin => plugin?.allowUserDatasources ?? true); }); +export const getPluginImages = createSelector(getPlugins, plugins => { + const pluginImages: Record = {}; + + plugins.forEach(plugin => { + pluginImages[plugin.id] = plugin?.iconLocation ?? ImageAlt; + }); + + return pluginImages; +}); + +export const getPluginTemplates = createSelector(getPlugins, plugins => { + const pluginTemplates: Record = {}; + + plugins.forEach(plugin => { + pluginTemplates[plugin.id] = plugin.templates; + }); + + return pluginTemplates; +}); + +export const getPluginResponseTypes = createSelector(getPlugins, plugins => { + const pluginResponseTypes: Record = {}; + + plugins.forEach(plugin => { + pluginResponseTypes[plugin.id] = plugin.responseType; + }); + + return pluginResponseTypes; +}); + +export const getPluginDocumentationLinks = createSelector( + getPlugins, + plugins => { + const pluginDocumentationLinks: Record = {}; + + plugins.forEach(plugin => { + pluginDocumentationLinks[plugin.id] = plugin.documentationLink; + }); + + return pluginDocumentationLinks; + }, +); + export const getActionsForCurrentPage = createSelector( getCurrentPageId, getActions, @@ -162,6 +224,7 @@ export const getActionResponses = createSelector(getActions, actions => { return responses; }); + export const getAction = ( state: AppState, actionId: string, diff --git a/deploy/aws/base-install.sh b/deploy/aws/base-install.sh new file mode 100755 index 0000000000..b3560c9daa --- /dev/null +++ b/deploy/aws/base-install.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +set -o errexit + +if [[ $EUID > 0 ]]; then + echo "Please run with sudo." >&2 + exit 1 +fi + +install_package() { + sudo apt-get -y update --quiet + sudo apt-get install -y ntp bc python3-pip --quiet + pip3 install boto3 + sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common virtualenv python3-setuptools --quiet + + # Installing docker + sudo apt-get -y --quiet install gnupg-agent + curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - + sudo add-apt-repository \ + "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ + $(lsb_release -cs) \ + stable" + + sudo apt-get -y update --quiet + sudo apt-get -y install docker-ce docker-ce-cli containerd.io --quiet + + # Installing docker compose + if [ ! -f /usr/bin/docker-compose ];then + echo "Installing docker-compose" + sudo curl -L "https://github.com/docker/compose/releases/download/1.26.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose + sudo chmod +x /usr/local/bin/docker-compose + sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose + fi + +} + +install_package + +#Download boot.sh and schedule at boot time. +app_path="/home/ubuntu/appsmith" +script_path="script" +boot_script_path=$app_path/$script_path +boot_file_name="boot.sh" +config_ssl_file_name="configure-ssl.sh" +mkdir -p $boot_script_path +sudo chown -R ubuntu:ubuntu $app_path +cd $boot_script_path + +sudo curl -O https://raw.githubusercontent.com/appsmithorg/appsmith/release/deploy/configure-ssl.sh +sudo chown ubuntu:ubuntu $boot_script_path/$config_ssl_file_name && sudo chmod +x $boot_script_path/$config_ssl_file_name + +sudo curl -O https://raw.githubusercontent.com/appsmithorg/appsmith/feature/deploy-script/deploy/aws/boot.sh +sudo chown ubuntu:ubuntu $boot_script_path/$boot_file_name && sudo chmod +x $boot_script_path/$boot_file_name + +USER="ubuntu" +CRON_FILE="/var/spool/cron/crontabs/$USER" +echo "@reboot /bin/bash $boot_script_path/$boot_file_name" >> $CRON_FILE +sudo chmod 0600 $CRON_FILE diff --git a/deploy/aws/boot.sh b/deploy/aws/boot.sh new file mode 100755 index 0000000000..651629d053 --- /dev/null +++ b/deploy/aws/boot.sh @@ -0,0 +1,96 @@ +#!/bin/bash + +set -o errexit +# Check if Lock File exists, if not create it and set trap on exit +if { set -C; 2>/dev/null >/home/ubuntu/.appsmith.lock; }; then + trap "rm -f /home/ubuntu/.appsmith.lock" EXIT +else + exit +fi + +start_docker() { + if [ `sudo systemctl is-active docker.service` == "inactive" ];then + echo "Starting docker" + sudo systemctl start docker.service + fi +} + +# generate random string +generate_random_string() { + value=`cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 10 | head -n 1` + echo $value +} + +start_docker + +install_dir="/home/ubuntu/appsmith" + +if [ ! -d $install_dir ];then + mkdir -p $install_dir +fi +chown -R ubuntu:ubuntu $install_dir + +mongo_host="mongo" +mongo_database="appsmith" +mongo_root_user=$( generate_random_string ) +mongo_root_password=$( generate_random_string ) +user_encryption_password=$( generate_random_string ) +user_encryption_salt=$( generate_random_string ) + +custom_domain="" +NGINX_SSL_CMNT="" +if [[ -z $custom_domain ]]; then + NGINX_SSL_CMNT="#" +fi + +script_dir="/script" +mkdir -p "$install_dir/$script_dir" +chown -R ubuntu:ubuntu "$install_dir/$script_dir" + +cd $install_dir/$script_dir +mkdir -p template +cd template +echo $PWD +curl -O https://raw.githubusercontent.com/appsmithorg/appsmith/release/deploy/template/docker-compose.yml.sh +curl -O https://raw.githubusercontent.com/appsmithorg/appsmith/release/deploy/template/init-letsencrypt.sh.sh +curl -O https://raw.githubusercontent.com/appsmithorg/appsmith/release/deploy/template/mongo-init.js.sh +curl -O https://raw.githubusercontent.com/appsmithorg/appsmith/release/deploy/template/docker.env.sh +curl -O https://raw.githubusercontent.com/appsmithorg/appsmith/release/deploy/template/nginx_app.conf.sh +curl -O https://raw.githubusercontent.com/appsmithorg/appsmith/release/deploy/template/encryption.env.sh +cd .. +echo $PWD + +# Role - Folder +for directory_name in nginx certbot mongo/db opa/config +do + if [[ ! -d "$install_dir/data/$directory_name" ]];then + mkdir -p "$install_dir/data/$directory_name" + fi +done + +echo "Generating the configuration files from the templates" +. ./template/nginx_app.conf.sh +. ./template/docker-compose.yml.sh +. ./template/mongo-init.js.sh +. ./template/docker.env.sh +. ./template/encryption.env.sh + +declare -A fileInfo + +fileInfo[/data/nginx/app.conf.template]="nginx_app.conf" +fileInfo[/docker-compose.yml]="docker-compose.yml" +fileInfo[/data/mongo/init.js]="mongo-init.js" +fileInfo[/docker.env]="docker.env" +fileInfo[/encryption.env]="encryption.env" + +for f in ${!fileInfo[@]} +do + mv -f ${fileInfo[$f]} $install_dir/$f +done + +cd $install_dir +echo "Pull Images: $PWD" +sudo docker-compose pull + +echo "docker compose $PWD" +sudo docker-compose -f docker-compose.yml up -d --remove-orphans diff --git a/deploy/aws/configure-ssl.sh b/deploy/aws/configure-ssl.sh new file mode 100755 index 0000000000..e643fbbbef --- /dev/null +++ b/deploy/aws/configure-ssl.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -o errexit + +read -p 'Enter your domain / subdomain name (example.com / app.example.com): ' custom_domain + +NGINX_SSL_CMNT="" +install_dir="/home/ubuntu/appsmith" +TEMPLATE_PATH="$install_dir/script/template" + +. $TEMPLATE_PATH/nginx_app.conf.sh +. $TEMPLATE_PATH/init-letsencrypt.sh.sh + +chmod 0755 init-letsencrypt.sh + +mv -f app.conf $install_dir/data/nginx/app.conf +mv -f init-letsencrypt.sh $install_dir/init-letsencrypt.sh + +cd $install_dir +sudo ./init-letsencrypt.sh diff --git a/deploy/template/docker-compose.yml.sh b/deploy/template/docker-compose.yml.sh index 9012839ad2..9f00c285d1 100644 --- a/deploy/template/docker-compose.yml.sh +++ b/deploy/template/docker-compose.yml.sh @@ -4,7 +4,7 @@ if [ ! -f docker-compose.yml ]; then touch docker-compose.yml fi -cat > docker-compose.yml << EOF +cat >| docker-compose.yml << EOF version: "3.7" services: diff --git a/deploy/template/docker.env.sh b/deploy/template/docker.env.sh index cdf1cd1d4b..932f41e28b 100644 --- a/deploy/template/docker.env.sh +++ b/deploy/template/docker.env.sh @@ -4,7 +4,7 @@ if [ ! -f docker-compose.yml ]; then touch docker-compose.yml fi -cat > docker.env << EOF +cat >| docker.env << EOF # Read our documentation on how to configure these features # https://docs.appsmith.com/v/v1.1/enabling-3p-services diff --git a/deploy/template/encryption.env.sh b/deploy/template/encryption.env.sh index 78b449578e..27c64b9237 100644 --- a/deploy/template/encryption.env.sh +++ b/deploy/template/encryption.env.sh @@ -4,8 +4,8 @@ if [ ! -f encryption.env ]; then touch encryption.env fi -cat > encryption.env << EOF +cat >| encryption.env << EOF APPSMITH_ENCRYPTION_PASSWORD=$user_encryption_password APPSMITH_ENCRYPTION_SALT=$user_encryption_salt -EOF \ No newline at end of file +EOF diff --git a/deploy/template/init-letsencrypt.sh.sh b/deploy/template/init-letsencrypt.sh.sh index a723c992f9..095e6a6280 100755 --- a/deploy/template/init-letsencrypt.sh.sh +++ b/deploy/template/init-letsencrypt.sh.sh @@ -6,7 +6,7 @@ fi -cat > init-letsencrypt.sh << EOF +cat >| init-letsencrypt.sh << EOF #!/bin/bash if ! [ -x "\$(command -v docker-compose)" ]; then diff --git a/deploy/template/mongo-init.js.sh b/deploy/template/mongo-init.js.sh index ae9ae395c9..b88b0bfef9 100644 --- a/deploy/template/mongo-init.js.sh +++ b/deploy/template/mongo-init.js.sh @@ -6,7 +6,7 @@ fi -cat > mongo-init.js << EOF +cat >| mongo-init.js << EOF let error = false print("**** Going to start Mongo seed ****") diff --git a/deploy/template/nginx_app.conf.sh b/deploy/template/nginx_app.conf.sh index e543ff9f66..51c4f81010 100644 --- a/deploy/template/nginx_app.conf.sh +++ b/deploy/template/nginx_app.conf.sh @@ -5,7 +5,7 @@ if [ ! -f nginx_app.conf ]; then fi # This template file is different from the others because of the sub_filter commands in the Nginx configuration -# Those variables are substituted inside the Docker container for appsmith-editor during bootup. +# Those variables are substituted inside the Docker container for appsmith-editor during bootup. # Hence we wish to prevent environment substitution here. # Relevant variables will be replaced at the end of this file via sed command @@ -26,7 +26,7 @@ $NGINX_SSL_CMNT server_name $custom_domain ; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; - + location / { try_files $uri /index.html =404; @@ -49,7 +49,7 @@ $NGINX_SSL_CMNT server_name $custom_domain ; location /f { proxy_pass https://cdn.optimizely.com/; } - + location /api { proxy_pass http://appsmith-internal-server:8080; } @@ -101,7 +101,7 @@ $NGINX_SSL_CMNT $NGINX_SSL_CMNT location /f { $NGINX_SSL_CMNT proxy_pass https://cdn.optimizely.com/; $NGINX_SSL_CMNT } -$NGINX_SSL_CMNT +$NGINX_SSL_CMNT $NGINX_SSL_CMNT location /api { $NGINX_SSL_CMNT proxy_pass http://appsmith-internal-server:8080; $NGINX_SSL_CMNT } @@ -115,7 +115,7 @@ $NGINX_SSL_CMNT proxy_pass http://appsmith-internal-server:8080; $NGINX_SSL_CMNT } $NGINX_SSL_CMNT $NGINX_SSL_CMNT } -' > nginx_app.conf +' >| nginx_app.conf if [[ "$OSTYPE" == "darwin"* ]]; then sed -i '' "s/\$NGINX_SSL_CMNT/$NGINX_SSL_CMNT/g" nginx_app.conf @@ -123,4 +123,4 @@ if [[ "$OSTYPE" == "darwin"* ]]; then else sed -i "s/\$NGINX_SSL_CMNT/$NGINX_SSL_CMNT/g" nginx_app.conf sed -i "s/\$custom_domain/$custom_domain/g" nginx_app.conf -fi \ No newline at end of file +fi