This is an automated email from the ASF dual-hosted git repository.

likyh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new 0aae83c21 refactor(config-ui): the connections page (#4046)
0aae83c21 is described below

commit 0aae83c21fe11d90499b260dbd72a2423d97d025
Author: 青湛 <[email protected]>
AuthorDate: Wed Dec 28 21:39:14 2022 +0800

    refactor(config-ui): the connections page (#4046)
    
    * feat(config-ui): add connection status component
    
    * fix(config-ui): adjust the type for plugin config
    
    * refactor(config-ui): the connection list page
    
    * refactor(config-ui): the connection detail page
    
    * refactor(config-ui): use new connection page to replace old
---
 config-ui/src/App.js                               |  23 +-
 .../config.ts => pages/connection/form/api.ts}     |  25 +-
 config-ui/src/pages/connection/form/index.tsx      | 270 +++++++++++++++++++++
 config-ui/src/pages/connection/form/styled.ts      |  88 +++++++
 config-ui/src/pages/connection/form/use-form.ts    | 105 ++++++++
 config-ui/src/pages/connection/index.ts            |   3 +-
 .../connection/{webhook/index.ts => list/api.ts}   |   6 +-
 config-ui/src/pages/connection/list/connection.tsx | 148 +++++++++++
 config-ui/src/pages/connection/list/index.tsx      |  53 ++++
 .../config.ts => pages/connection/list/styled.ts}  |  37 ++-
 config-ui/src/plugins/types.ts                     |  21 +-
 config-ui/src/plugins/webook/config.ts             |   7 +-
 config-ui/src/store/connections/index.ts           |   1 +
 config-ui/src/store/connections/status.tsx         |  83 +++++++
 14 files changed, 823 insertions(+), 47 deletions(-)

diff --git a/config-ui/src/App.js b/config-ui/src/App.js
index 06999a60d..9fed8d79f 100644
--- a/config-ui/src/App.js
+++ b/config-ui/src/App.js
@@ -29,16 +29,14 @@ import {
   ProjectHomePage,
   ProjectDetailPage,
   ConnectionHomePage,
-  WebHookConnectionPage,
+  ConnectionListPage,
+  ConnectionFormPage,
   CreateBlueprintPage,
   BlueprintHomePage,
   BlueprintDetailPage,
   TransformationHomePage,
   TransformationDetailPage
 } from '@/pages'
-import ManageIntegration from '@/pages/configure/integration/manage'
-import AddConnection from '@/pages/configure/connections/AddConnection'
-import ConfigureConnection from 
'@/pages/configure/connections/ConfigureConnection'
 
 function App() {
   return (
@@ -63,23 +61,18 @@ function App() {
         />
         <Route
           exact
-          path='/connections/webhook'
-          component={() => <WebHookConnectionPage />}
+          path='/connections/:plugin'
+          component={() => <ConnectionListPage />}
         />
         <Route
           exact
-          path='/connections/:providerId'
-          component={() => <ManageIntegration />}
+          path='/connections/:plugin/create'
+          component={() => <ConnectionFormPage />}
         />
         <Route
           exact
-          path='/connections/add/:providerId'
-          component={() => <AddConnection />}
-        />
-        <Route
-          exact
-          path='/connections/configure/:providerId/:connectionId'
-          component={() => <ConfigureConnection />}
+          path='/connections/:plugin/:cid'
+          component={() => <ConnectionFormPage />}
         />
         <Route
           exact
diff --git a/config-ui/src/plugins/webook/config.ts 
b/config-ui/src/pages/connection/form/api.ts
similarity index 55%
copy from config-ui/src/plugins/webook/config.ts
copy to config-ui/src/pages/connection/form/api.ts
index d7fa2cbae..2a249ed50 100644
--- a/config-ui/src/plugins/webook/config.ts
+++ b/config-ui/src/pages/connection/form/api.ts
@@ -16,15 +16,20 @@
  *
  */
 
-import { Plugins, PluginType } from '@/plugins'
+import { Plugins } from '@/plugins'
+import request from '@/components/utils/request'
 
-import Icon from './assets/icon.svg'
+export const testConnection = (plugin: Plugins, payload: any) =>
+  request(`/plugins/${plugin}/test`, { method: 'post', data: payload })
 
-export const WebhookConfig = {
-  plugin: Plugins.Webhook,
-  name: 'Webhook',
-  type: PluginType.Incoming_Connection,
-  icon: Icon,
-  entities: [],
-  transformation: {}
-}
+export const createConnection = (plugin: Plugins, payload: any) =>
+  request(`/plugins/${plugin}/connections`, { method: 'post', data: payload })
+
+export const getConnection = (plugin: Plugins, id: ID) =>
+  request(`/plugins/${plugin}/connections/${id}`)
+
+export const updateConnection = (plugin: Plugins, id: ID, payload: any) =>
+  request(`/plugins/${plugin}/connections/${id}`, {
+    method: 'patch',
+    data: payload
+  })
diff --git a/config-ui/src/pages/connection/form/index.tsx 
b/config-ui/src/pages/connection/form/index.tsx
new file mode 100644
index 000000000..ee5d024a3
--- /dev/null
+++ b/config-ui/src/pages/connection/form/index.tsx
@@ -0,0 +1,270 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import React, { useState, useEffect, useMemo } from 'react'
+import { useParams, useHistory } from 'react-router-dom'
+import { omit, pick } from 'lodash'
+import {
+  FormGroup,
+  InputGroup,
+  NumericInput,
+  Switch,
+  ButtonGroup,
+  Button,
+  Icon,
+  Intent,
+  Position
+} from '@blueprintjs/core'
+import { Tooltip2 } from '@blueprintjs/popover2'
+
+import { PageHeader, Card, PageLoading } from '@/components'
+import type { PluginConfigConnectionType } from '@/plugins'
+import { Plugins, PluginConfig } from '@/plugins'
+
+import { useForm } from './use-form'
+import * as S from './styled'
+
+export const ConnectionFormPage = () => {
+  const [form, setForm] = useState<Record<string, any>>({})
+  const [showRateLimit, setShowRateLimit] = useState(false)
+  const [githubTokens, setGitHubTokens] = useState<Record<string, string>>({
+    '0': '',
+    '1': ''
+  })
+
+  const history = useHistory()
+  const { plugin, cid } = useParams<{ plugin: Plugins; cid?: string }>()
+  const { loading, operating, connection, onTest, onCreate, onUpdate } =
+    useForm({ plugin, id: cid })
+
+  const {
+    name,
+    connection: { initialValues, fields }
+  } = useMemo(
+    () =>
+      PluginConfig.find(
+        (p) => p.plugin === plugin
+      ) as PluginConfigConnectionType,
+    [plugin]
+  )
+
+  useEffect(() => {
+    setForm({
+      ...form,
+      ...(initialValues ?? {}),
+      ...(connection ?? {}),
+      token: Object.values(githubTokens).filter(Boolean).join(',')
+    })
+  }, [githubTokens, initialValues, connection])
+
+  const error = useMemo(
+    () =>
+      !!(fields.filter((field) => field.required) ?? []).find(
+        (field) => !form[field.key]
+      ),
+    [form, fields]
+  )
+
+  const handleChangeGitHubToken = (key: string, token: string) => {
+    setGitHubTokens({
+      ...githubTokens,
+      [key]: token
+    })
+  }
+
+  const handleCreateToken = () => {
+    const keys = Object.keys(githubTokens)
+    setGitHubTokens({
+      ...githubTokens,
+      [+keys[keys.length - 1] + 1]: ''
+    })
+  }
+
+  const handleRemoveToken = (key: string) => {
+    setGitHubTokens(omit(githubTokens, [key]))
+  }
+
+  const handleTest = () =>
+    onTest(
+      pick(form, [
+        'endpoint',
+        'token',
+        'username',
+        'password',
+        'app_id',
+        'secret_key',
+        'proxy'
+      ])
+    )
+
+  const handleCancel = () => history.push(`/connections/${plugin}`)
+
+  const handleSave = () => (cid ? onUpdate(cid, form) : onCreate(form))
+
+  const getFormItem = ({
+    key,
+    label,
+    type,
+    required,
+    placeholder,
+    tooltip
+  }: PluginConfigConnectionType['connection']['fields']['0']) => {
+    return (
+      <FormGroup
+        key={key}
+        inline
+        label={
+          <S.Label>
+            <span>{label}</span>
+            {tooltip && (
+              <Tooltip2 position={Position.TOP} content={tooltip}>
+                <Icon icon='help' size={12} />
+              </Tooltip2>
+            )}
+          </S.Label>
+        }
+        labelFor={key}
+        labelInfo={required ? '*' : ''}
+      >
+        {type === 'github-token' && (
+          <S.GitHubToken>
+            <p>
+              Add one or more personal token(s) for authentication from you and
+              your organization members. Multiple tokens can help speed up the
+              data collection process.{' '}
+            </p>
+            <p>
+              <a
+                
href='https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token'
+                target='_blank'
+                rel='noreferrer'
+              >
+                Learn about how to create a personal access token
+              </a>
+            </p>
+            <h3>Personal Access Token(s)</h3>
+            {Object.entries(githubTokens).map(([key, value]) => (
+              <div className='token' key={key}>
+                <InputGroup
+                  placeholder='token'
+                  value={value}
+                  onChange={(e) => handleChangeGitHubToken(key, 
e.target.value)}
+                />
+                <Button
+                  minimal
+                  icon='cross'
+                  onClick={() => handleRemoveToken(key)}
+                />
+              </div>
+            ))}
+            <div className='action'>
+              <Button
+                outlined
+                small
+                intent={Intent.PRIMARY}
+                text='Another Token'
+                icon='plus'
+                onClick={handleCreateToken}
+              />
+            </div>
+          </S.GitHubToken>
+        )}
+        {type === 'text' && (
+          <InputGroup
+            placeholder={placeholder}
+            value={form[key] ?? ''}
+            onChange={(e) => setForm({ ...form, [`${key}`]: e.target.value })}
+          />
+        )}
+        {type === 'numeric' && (
+          <S.RateLimit>
+            {showRateLimit && (
+              <NumericInput
+                placeholder={placeholder}
+                onValueChange={(value) =>
+                  setForm({
+                    ...form,
+                    [key]: value
+                  })
+                }
+              />
+            )}
+            <Switch
+              checked={showRateLimit}
+              onChange={(e) =>
+                setShowRateLimit((e.target as HTMLInputElement).checked)
+              }
+            />
+          </S.RateLimit>
+        )}
+        {type === 'switch' && (
+          <Switch
+            checked={form[key] ?? false}
+            onChange={(e) =>
+              setForm({
+                ...form,
+                [key]: (e.target as HTMLInputElement).checked
+              })
+            }
+          />
+        )}
+      </FormGroup>
+    )
+  }
+
+  return (
+    <PageHeader
+      breadcrumbs={[
+        { name: 'Connections', path: '/connections' },
+        { name, path: `/connections/${plugin}` },
+        {
+          name: cid ? cid : 'Create',
+          path: `/connections/${plugin}/${cid ? cid : 'create'}`
+        }
+      ]}
+    >
+      {loading ? (
+        <PageLoading />
+      ) : (
+        <Card>
+          <S.Wrapper>
+            {fields.map((field) => getFormItem(field))}
+            <div className='footer'>
+              <Button
+                disabled={error}
+                loading={operating}
+                text='Test Connection'
+                onClick={handleTest}
+              />
+              <ButtonGroup>
+                <Button text='Cancel' onClick={handleCancel} />
+                <Button
+                  disabled={error}
+                  loading={operating}
+                  intent={Intent.PRIMARY}
+                  text='Save Connection'
+                  onClick={handleSave}
+                />
+              </ButtonGroup>
+            </div>
+          </S.Wrapper>
+        </Card>
+      )}
+    </PageHeader>
+  )
+}
diff --git a/config-ui/src/pages/connection/form/styled.ts 
b/config-ui/src/pages/connection/form/styled.ts
new file mode 100644
index 000000000..16290d2bf
--- /dev/null
+++ b/config-ui/src/pages/connection/form/styled.ts
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { Colors } from '@blueprintjs/core'
+import styled from 'styled-components'
+
+export const Wrapper = styled.div`
+  .bp4-form-group {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+
+    .bp4-label {
+      flex: 0 0 200px;
+      font-weight: 600;
+
+      .bp4-popover2-target {
+        display: inline;
+        margin: 0;
+        line-height: 1;
+        margin-left: 4px;
+      }
+
+      .bp4-text-muted {
+        color: ${Colors.RED3};
+      }
+    }
+
+    .bp4-form-content {
+      flex: auto;
+    }
+  }
+
+  .footer {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-top: 32px;
+  }
+`
+
+export const Label = styled.span`
+  display: inline-flex;
+  align-items: center;
+`
+
+export const RateLimit = styled.div`
+  display: flex;
+  align-items: center;
+
+  & > .bp4-numeric-input {
+    margin-right: 8px;
+  }
+`
+
+export const GitHubToken = styled.div`
+  p {
+    margin: 0 0 8px;
+  }
+
+  h3 {
+    margin: 0 0 8px;
+    padding: 0;
+    font-size: 14px;
+  }
+
+  .token {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 8px;
+  }
+`
diff --git a/config-ui/src/pages/connection/form/use-form.ts 
b/config-ui/src/pages/connection/form/use-form.ts
new file mode 100644
index 000000000..555403d4d
--- /dev/null
+++ b/config-ui/src/pages/connection/form/use-form.ts
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import { useState, useMemo, useEffect } from 'react'
+import { useHistory } from 'react-router-dom'
+
+import { Plugins } from '@/plugins'
+import { operator } from '@/utils'
+
+import * as API from './api'
+
+interface Props {
+  plugin: Plugins
+  id?: ID
+}
+
+export const useForm = ({ plugin, id }: Props) => {
+  const [loading, setLoading] = useState(false)
+  const [operating, setOperating] = useState(false)
+  const [connection, setConnection] = useState()
+
+  const history = useHistory()
+
+  const getConnection = async () => {
+    if (!id) return
+
+    setLoading(true)
+    try {
+      const res = await API.getConnection(plugin, id)
+      setConnection(res)
+    } finally {
+      setLoading(false)
+    }
+  }
+
+  useEffect(() => {
+    getConnection()
+  }, [])
+
+  const handleTest = async (payload: any) => {
+    const [success] = await operator(
+      () => API.testConnection(plugin, payload),
+      {
+        setOperating,
+        formatReason: () => 'Test connection link failed.'
+      }
+    )
+
+    if (success) {
+    }
+  }
+
+  const handleCreate = async (payload: any) => {
+    const [success] = await operator(
+      () => API.createConnection(plugin, payload),
+      {
+        setOperating
+      }
+    )
+
+    if (success) {
+      history.push(`/connections/${plugin}`)
+    }
+  }
+
+  const handleUpdate = async (id: ID, payload: any) => {
+    const [success] = await operator(
+      () => API.updateConnection(plugin, id, payload),
+      {
+        setOperating
+      }
+    )
+
+    if (success) {
+      history.push(`/connections/${plugin}`)
+    }
+  }
+
+  return useMemo(
+    () => ({
+      loading,
+      operating,
+      connection,
+      onTest: handleTest,
+      onCreate: handleCreate,
+      onUpdate: handleUpdate
+    }),
+    [loading, operating, connection]
+  )
+}
diff --git a/config-ui/src/pages/connection/index.ts 
b/config-ui/src/pages/connection/index.ts
index af1444500..e8f447932 100644
--- a/config-ui/src/pages/connection/index.ts
+++ b/config-ui/src/pages/connection/index.ts
@@ -17,4 +17,5 @@
  */
 
 export * from './home'
-export * from './webhook'
+export * from './list'
+export * from './form'
diff --git a/config-ui/src/pages/connection/webhook/index.ts 
b/config-ui/src/pages/connection/list/api.ts
similarity index 78%
rename from config-ui/src/pages/connection/webhook/index.ts
rename to config-ui/src/pages/connection/list/api.ts
index 66ed7d6f5..8d8e0e666 100644
--- a/config-ui/src/pages/connection/webhook/index.ts
+++ b/config-ui/src/pages/connection/list/api.ts
@@ -16,6 +16,8 @@
  *
  */
 
-import { WebHookConnection } from '@/plugins'
+import { Plugins } from '@/plugins'
+import request from '@/components/utils/request'
 
-export const WebHookConnectionPage = WebHookConnection
+export const deleteConnection = (plugin: Plugins, id: ID) =>
+  request(`/plugins/${plugin}/connections/${id}`, { method: 'delete' })
diff --git a/config-ui/src/pages/connection/list/connection.tsx 
b/config-ui/src/pages/connection/list/connection.tsx
new file mode 100644
index 000000000..33cc7460e
--- /dev/null
+++ b/config-ui/src/pages/connection/list/connection.tsx
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import React, { useState, useEffect, useMemo } from 'react'
+import { useHistory } from 'react-router-dom'
+import { ButtonGroup, Button, Intent, Position } from '@blueprintjs/core'
+import { Popover2 } from '@blueprintjs/popover2'
+
+import { Table, ColumnType } from '@/components'
+import { Plugins } from '@/plugins'
+import type { ConnectionItemType } from '@/store'
+import { useConnection, ConnectionStatus } from '@/store'
+import { operator } from '@/utils'
+
+import * as API from './api'
+import * as S from './styled'
+
+interface Props {
+  plugin: Plugins
+}
+
+export const Connection = ({ plugin }: Props) => {
+  const [operating, setOperating] = useState(false)
+
+  const history = useHistory()
+
+  const { connections, onTest, onRefresh } = useConnection()
+
+  useEffect(() => {
+    connections.map((cs) => onTest(cs))
+  }, [])
+
+  const handleRefresh = () => onRefresh()
+
+  const handleCreate = () => history.push(`/connections/${plugin}/create`)
+
+  const handleUpdate = (id: ID) =>
+    history.push(`/connections/${plugin}/${id}`)
+
+  const handleDelete = async (id: ID) => {
+    const [success] = await operator(() => API.deleteConnection(plugin, id), {
+      setOperating
+    })
+
+    if (success) {
+      onRefresh()
+    }
+  }
+
+  const columns = useMemo(
+    () =>
+      [
+        {
+          title: 'ID',
+          dataIndex: 'id',
+          key: 'id'
+        },
+        {
+          title: 'Connection Name',
+          dataIndex: 'name',
+          key: 'name'
+        },
+        {
+          title: 'Endpoint',
+          dataIndex: 'endpoint',
+          key: 'endpoint'
+        },
+        {
+          title: 'Status',
+          dataIndex: 'status',
+          key: 'status',
+          align: 'center',
+          render: (_, row) => (
+            <ConnectionStatus connection={row} onTest={onTest} />
+          )
+        },
+        {
+          title: '',
+          dataIndex: 'id',
+          key: 'action',
+          render: (id) => (
+            <ButtonGroup>
+              <Button
+                minimal
+                intent={Intent.PRIMARY}
+                icon='edit'
+                onClick={() => handleUpdate(id)}
+              />
+              <Popover2
+                position={Position.TOP}
+                content={
+                  <S.DeleteConfirm>
+                    <h3>Confirm deletion</h3>
+                    <p>Are you sure you want to delete this item?</p>
+                    <ButtonGroup>
+                      <Button
+                        loading={operating}
+                        intent={Intent.DANGER}
+                        text='Delete'
+                        onClick={() => handleDelete(id)}
+                      />
+                    </ButtonGroup>
+                  </S.DeleteConfirm>
+                }
+              >
+                <Button minimal intent={Intent.PRIMARY} icon='delete' />
+              </Popover2>
+            </ButtonGroup>
+          )
+        }
+      ] as ColumnType<ConnectionItemType>,
+    []
+  )
+
+  return (
+    <S.Wrapper>
+      <ButtonGroup className='action'>
+        <Button
+          intent={Intent.PRIMARY}
+          icon='plus'
+          text='Add Connection'
+          onClick={handleCreate}
+        />
+        <Button
+          icon='refresh'
+          text='Refresh Connections'
+          onClick={handleRefresh}
+        />
+      </ButtonGroup>
+      <Table columns={columns} dataSource={connections} />
+    </S.Wrapper>
+  )
+}
diff --git a/config-ui/src/pages/connection/list/index.tsx 
b/config-ui/src/pages/connection/list/index.tsx
new file mode 100644
index 000000000..98a46de2f
--- /dev/null
+++ b/config-ui/src/pages/connection/list/index.tsx
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import React, { useMemo } from 'react'
+import { useParams } from 'react-router-dom'
+
+import { PageHeader } from '@/components'
+import type { PluginConfigType } from '@/plugins'
+import { Plugins, PluginConfig, WebHookConnection } from '@/plugins'
+import { ConnectionContextProvider } from '@/store'
+
+import { Connection } from './connection'
+
+export const ConnectionListPage = () => {
+  const { plugin } = useParams<{ plugin: Plugins }>()
+
+  const config = useMemo(
+    () => PluginConfig.find((p) => p.plugin === plugin) as PluginConfigType,
+    [plugin]
+  )
+
+  return (
+    <ConnectionContextProvider plugins={[plugin]}>
+      <PageHeader
+        breadcrumbs={[
+          { name: 'Connections', path: '/connections' },
+          { name: config.name, path: `/connections/${plugin}` }
+        ]}
+      >
+        {plugin === Plugins.Webhook ? (
+          <WebHookConnection />
+        ) : (
+          <Connection plugin={plugin} />
+        )}
+      </PageHeader>
+    </ConnectionContextProvider>
+  )
+}
diff --git a/config-ui/src/plugins/webook/config.ts 
b/config-ui/src/pages/connection/list/styled.ts
similarity index 67%
copy from config-ui/src/plugins/webook/config.ts
copy to config-ui/src/pages/connection/list/styled.ts
index d7fa2cbae..f8b1f5507 100644
--- a/config-ui/src/plugins/webook/config.ts
+++ b/config-ui/src/pages/connection/list/styled.ts
@@ -16,15 +16,32 @@
  *
  */
 
-import { Plugins, PluginType } from '@/plugins'
+import styled from 'styled-components'
 
-import Icon from './assets/icon.svg'
+export const Wrapper = styled.div`
+  .action {
+    margin-bottom: 16px;
 
-export const WebhookConfig = {
-  plugin: Plugins.Webhook,
-  name: 'Webhook',
-  type: PluginType.Incoming_Connection,
-  icon: Icon,
-  entities: [],
-  transformation: {}
-}
+    .bp4-button + .bp4-button {
+      margin-left: 8px;
+    }
+  }
+`
+
+export const DeleteConfirm = styled.div`
+  padding: 16px 24px;
+
+  h3 {
+    margin: 0;
+    padding: 0;
+  }
+
+  p {
+    margin: 8px 0;
+  }
+
+  .bp4-button-group {
+    display: flex;
+    justify-content: flex-end;
+  }
+`
diff --git a/config-ui/src/plugins/types.ts b/config-ui/src/plugins/types.ts
index 7ce5e6570..dd4e969bc 100644
--- a/config-ui/src/plugins/types.ts
+++ b/config-ui/src/plugins/types.ts
@@ -43,12 +43,12 @@ export enum PluginType {
   Pipeline = 'pipeline'
 }
 
-export type PluginConfigType = {
+export type PluginConfigConnectionType = {
   plugin: Plugins
   name: string
-  type: PluginType
+  type: PluginType.Connection
   icon: string
-  connection?: {
+  connection: {
     initialValues?: Record<string, any>
     fields: Array<{
       key: string
@@ -59,6 +59,17 @@ export type PluginConfigType = {
       tooltip?: string
     }>
   }
-  entities?: string[]
-  transformation?: any
+  entities: string[]
+  transformation: any
 }
+
+export type PluginConfigAnotherType = {
+  plugin: Plugins
+  name: string
+  type: PluginType.Incoming_Connection | PluginType.Pipeline
+  icon: string
+}
+
+export type PluginConfigType =
+  | PluginConfigConnectionType
+  | PluginConfigAnotherType
diff --git a/config-ui/src/plugins/webook/config.ts 
b/config-ui/src/plugins/webook/config.ts
index d7fa2cbae..42b1e4f13 100644
--- a/config-ui/src/plugins/webook/config.ts
+++ b/config-ui/src/plugins/webook/config.ts
@@ -16,15 +16,14 @@
  *
  */
 
+import type { PluginConfigType } from '@/plugins'
 import { Plugins, PluginType } from '@/plugins'
 
 import Icon from './assets/icon.svg'
 
-export const WebhookConfig = {
+export const WebhookConfig: PluginConfigType = {
   plugin: Plugins.Webhook,
   name: 'Webhook',
   type: PluginType.Incoming_Connection,
-  icon: Icon,
-  entities: [],
-  transformation: {}
+  icon: Icon
 }
diff --git a/config-ui/src/store/connections/index.ts 
b/config-ui/src/store/connections/index.ts
index 5f25db57c..d2bcc20ec 100644
--- a/config-ui/src/store/connections/index.ts
+++ b/config-ui/src/store/connections/index.ts
@@ -18,3 +18,4 @@
 
 export * from './types'
 export * from './context'
+export * from './status'
diff --git a/config-ui/src/store/connections/status.tsx 
b/config-ui/src/store/connections/status.tsx
new file mode 100644
index 000000000..fa0ed6de4
--- /dev/null
+++ b/config-ui/src/store/connections/status.tsx
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+import React from 'react'
+import { Icon, Colors, Position, Intent } from '@blueprintjs/core'
+import { Tooltip2 } from '@blueprintjs/popover2'
+import styled from 'styled-components'
+
+import { Loading } from '@/components'
+
+import type { ConnectionItemType } from './types'
+import { ConnectionStatusEnum } from './types'
+
+const Wrapper = styled.div`
+  display: inline-flex;
+  align-items: center;
+
+  & > span.online {
+    color: ${Colors.GREEN3};
+  }
+
+  & > span.offline {
+    color: ${Colors.RED3};
+  }
+
+  & > span.testing {
+    color: #7497f7;
+  }
+`
+
+const STATUS_MAP = {
+  [`${ConnectionStatusEnum.NULL}`]: 'Init',
+  [`${ConnectionStatusEnum.TESTING}`]: 'Testing',
+  [`${ConnectionStatusEnum.ONLINE}`]: 'Online',
+  [`${ConnectionStatusEnum.OFFLINE}`]: 'Offline'
+}
+
+interface Props {
+  connection: ConnectionItemType
+  onTest: (connection: ConnectionItemType) => void
+}
+
+export const ConnectionStatus = ({ connection, onTest }: Props) => {
+  const { status } = connection
+
+  return (
+    <Wrapper>
+      {status === ConnectionStatusEnum.TESTING && (
+        <Loading size={14} style={{ marginRight: 4 }} />
+      )}
+      {status === ConnectionStatusEnum.OFFLINE && (
+        <Tooltip2
+          intent={Intent.PRIMARY}
+          position={Position.TOP}
+          content='Retry'
+        >
+          <Icon
+            size={14}
+            icon='repeat'
+            style={{ marginRight: 4, color: Colors.RED3, cursor: 'pointer' }}
+            onClick={() => onTest(connection)}
+          />
+        </Tooltip2>
+      )}
+      <span className={status}>{STATUS_MAP[status]}</span>
+    </Wrapper>
+  )
+}

Reply via email to