Skip to content

Commit

Permalink
Merge pull request #240 from nemunaire/integrations
Browse files Browse the repository at this point in the history
ui: Add interface to manage integrations
  • Loading branch information
ddvk authored Nov 17, 2024
2 parents cb1e4ea + 2937e8e commit 743916c
Show file tree
Hide file tree
Showing 9 changed files with 626 additions and 2 deletions.
4 changes: 2 additions & 2 deletions internal/integrations/integrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ func GetIntegrationProvider(storer storage.UserStorer, uid, integrationid string
continue
}
switch intg.Provider {
case webdavProvider:
return newWebDav(intg), nil
case dropboxProvider:
return newDropbox(intg), nil
case localfsProvider:
return newLocalFS(intg), nil
case webdavProvider:
return newWebDav(intg), nil
}
}
return nil, fmt.Errorf("integration not found or no implmentation (only webdav) %s", integrationid)
Expand Down
139 changes: 139 additions & 0 deletions internal/ui/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const (
browserIDContextKey = "browserID"
isSync15Key = "sync15"
docIDParam = "docid"
intIDParam = "intid"
uiLogger = "[ui] "
ui10 = " [10] "
useridParam = "userid"
Expand Down Expand Up @@ -510,3 +511,141 @@ func (app *ReactAppWrapper) createUser(c *gin.Context) {
}
c.Status(http.StatusCreated)
}

func (app *ReactAppWrapper) listIntegrations(c *gin.Context) {
uid := c.GetString(userIDContextKey)

user, err := app.userStorer.GetUser(uid)
if err != nil {
log.Error(err)
c.AbortWithStatus(http.StatusUnauthorized)
return
}

c.JSON(http.StatusOK, user.Integrations)
}

func (app *ReactAppWrapper) createIntegration(c *gin.Context) {
int := model.IntegrationConfig{}
if err := c.ShouldBindJSON(&int); err != nil {
log.Error(err)
badReq(c, err.Error())
return
}

uid := c.GetString(userIDContextKey)

user, err := app.userStorer.GetUser(uid)
if err != nil {
log.Error(err)
c.AbortWithStatus(http.StatusUnauthorized)
return
}

int.ID = uuid.NewString()
user.Integrations = append(user.Integrations, int)

err = app.userStorer.UpdateUser(user)

if err != nil {
log.Error("error updating user", err)
c.AbortWithStatus(http.StatusInternalServerError)
return
}

c.JSON(http.StatusOK, int)
}

func (app *ReactAppWrapper) getIntegration(c *gin.Context) {
uid := c.GetString(userIDContextKey)

intid := common.ParamS(intIDParam, c)

user, err := app.userStorer.GetUser(uid)
if err != nil {
log.Error(err)
c.AbortWithStatus(http.StatusUnauthorized)
return
}

for _, integration := range user.Integrations {
if integration.ID == intid {
c.JSON(http.StatusOK, integration)
return
}
}

c.AbortWithStatus(http.StatusNotFound)
}

func (app *ReactAppWrapper) updateIntegration(c *gin.Context) {
int := model.IntegrationConfig{}
if err := c.ShouldBindJSON(&int); err != nil {
log.Error(err)
badReq(c, err.Error())
return
}

uid := c.GetString(userIDContextKey)

intid := common.ParamS(intIDParam, c)

user, err := app.userStorer.GetUser(uid)
if err != nil {
log.Error(err)
c.AbortWithStatus(http.StatusUnauthorized)
return
}

for idx, integration := range user.Integrations {
if integration.ID == intid {
int.ID = integration.ID
user.Integrations[idx] = int

err = app.userStorer.UpdateUser(user)

if err != nil {
log.Error("error updating user", err)
c.AbortWithStatus(http.StatusInternalServerError)
return
}

c.JSON(http.StatusOK, int)
return
}
}

c.AbortWithStatus(http.StatusNotFound)
}

func (app *ReactAppWrapper) deleteIntegration(c *gin.Context) {
uid := c.GetString(userIDContextKey)

intid := common.ParamS(intIDParam, c)

user, err := app.userStorer.GetUser(uid)
if err != nil {
log.Error(err)
c.AbortWithStatus(http.StatusUnauthorized)
return
}

for idx, integration := range user.Integrations {
if integration.ID == intid {
user.Integrations = append(user.Integrations[:idx], user.Integrations[idx+1:]...)

err = app.userStorer.UpdateUser(user)

if err != nil {
log.Error("error updating user", err)
c.AbortWithStatus(http.StatusInternalServerError)
return
}

c.Status(http.StatusAccepted)
return
}
}

c.AbortWithStatus(http.StatusNotFound)
}
7 changes: 7 additions & 0 deletions internal/ui/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ func (app *ReactAppWrapper) RegisterRoutes(router *gin.Engine) {
auth.POST("folders", app.createFolder)
auth.GET("documents/:docid/metadata", app.getDocumentMetadata)

// integrations
auth.GET("integrations", app.listIntegrations)
auth.POST("integrations", app.createIntegration)
auth.GET("integrations/:intid", app.getIntegration)
auth.PUT("integrations/:intid", app.updateIntegration)
auth.DELETE("integrations/:intid", app.deleteIntegration)

//admin
admin := auth.Group("")
admin.Use(app.adminMiddleware())
Expand Down
2 changes: 2 additions & 0 deletions ui/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import Login from "./pages/Login";
import Home from "./pages/Home";
import Connect from "./pages/Connect";
import Documents from "./pages/Documents";
import Integrations from "./pages/Integrations";
import Profile from "./pages/Profile";
import Admin from "./pages/Admin";
import NoMatch from "./pages/404";
Expand All @@ -35,6 +36,7 @@ export default function App() {
<PrivateRoute exact path="/" component={Home} />
<PrivateRoute path="/documents" component={Documents} />
<PrivateRoute path="/connect" component={Connect} />
<PrivateRoute path="/integrations" component={Integrations} />
<PrivateRoute path="/profile" component={Profile} />
<PrivateRoute path="/admin" roles={[Role.Admin]} component={Admin} />

Expand Down
5 changes: 5 additions & 0 deletions ui/src/components/Navigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ const NavigationBar = () => {
Documents
</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link as={NavLink} to="/integrations">
Integrations
</Nav.Link>
</Nav.Item>
<Nav.Item>
<Nav.Link as={NavLink} to="/connect">
Connect
Expand Down
172 changes: 172 additions & 0 deletions ui/src/pages/Integrations/IntegrationModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import React, { useState } from "react";
import Form from "react-bootstrap/Form";
import { Button, Card } from "react-bootstrap";
import apiService from "../../services/api.service";

import { Alert } from "react-bootstrap";

export default function IntegrationModal(params) {
const { integration, onSave, headerText, onClose } = params;

const [formErrors, setFormErrors] = useState({});
const [integrationForm, setIntegrationForm] = useState({
name: integration?.Name,
provider: integration?.Provider,
email: integration?.email,
username: integration?.Username,
password: integration?.Password,
address: integration?.Address,
insecure: integration?.Insecure,
accesstoken: integration?.Accesstoken,
path: integration?.Path,
});

function handleChange({ target }) {
setIntegrationForm({ ...integrationForm, [target.name]: target.value });
}

function formIsValid() {
const _errors = {};

if (!integrationForm.name) _errors.error = "name is required";

setFormErrors(_errors);

return Object.keys(_errors).length === 0;
}

async function handleSubmit(event) {
event.preventDefault();

if (!formIsValid()) return;

try {
await apiService.updateintegration({
id: integration.ID,
name: integrationForm.name,
provider: integrationForm.provider,
username: integrationForm.username,
password: integrationForm.password,
address: integrationForm.address,
insecure: integrationForm.insecure,
accesstoken: integrationForm.accesstoken,
path: integrationForm.path,
});
onSave();
} catch (e) {
setFormErrors({ error: e.toString() });
}
}

if (!integration) return null;
return (
<Form onSubmit={handleSubmit}>
<Card>
<Card.Header>
<span>{headerText}</span>
</Card.Header>
<Card.Body>
<div>
<Alert variant="danger" hidden={!formErrors.error}>
<Alert.Heading>An Error Occurred</Alert.Heading>
{formErrors.error}
</Alert>

<Form.Label>IntegrationID</Form.Label>
<Form.Control
className="font-weight-bold"
placeholder=""
value={integration.ID}
disabled
/>

<Form.Label>Provider</Form.Label>
<Form.Control
as="select"
name="provider"
value={integrationForm.provider}
onChange={handleChange}
>
<option value="localfs">Directory in file system</option>
<option value="webdav">WebDAV</option>
<option value="dropbox">Dropbox</option>
</Form.Control>

<Form.Label>Name</Form.Label>
<Form.Control
placeholder="Integration name"
value={integrationForm.name}
name="name"
onChange={handleChange}
/>

{integrationForm.provider === "webdav" && (
<>
<Form.Label>Address</Form.Label>
<Form.Control
placeholder="Server URL"
value={integrationForm.address}
name="address"
onChange={handleChange}
/>
</>
)}
{integrationForm.provider === "webdav" && (
<>
<Form.Label>Username</Form.Label>
<Form.Control
placeholder="Username"
value={integrationForm.username}
name="username"
onChange={handleChange}
/>
</>
)}
{integrationForm.provider === "webdav" && (
<>
<Form.Label>Password</Form.Label>
<Form.Control
type="password"
placeholder="Password"
value={integrationForm.password}
name="password"
onChange={handleChange}
/>
</>
)}

{integrationForm.provider === "localfs" && (
<>
<Form.Label>Path</Form.Label>
<Form.Control
placeholder="Path"
value={integrationForm.path}
name="path"
onChange={handleChange}
/>
</>
)}

{integrationForm.provider === "dropbox" && (
<>
<Form.Label>Access Token</Form.Label>
<Form.Control
placeholder="Access Token"
value={integrationForm.accesstoken}
name="accesstoken"
onChange={handleChange}
/>
</>
)}
</div>
</Card.Body>
<Card.Footer style={{ display: "flex", flex: "10", gap: "15px" }}>
<Button variant="primary" type="submit">
Save
</Button>
<Button variant="secondary" onClick={onClose}>Cancel</Button>
</Card.Footer>
</Card>
</Form>
);
}
Loading

0 comments on commit 743916c

Please sign in to comment.