Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose cloud accounts management endpoints #979

Merged
merged 6 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface DatabasePurplinProps {
function DatabasePurplin({ database }: DatabasePurplinProps) {
return (
<div className="relative">
<div className="-ml-12 -mb-14 flex w-24 items-center justify-center rounded-3xl bg-komiser-200 p-4">
<div className="-mb-14 -ml-12 flex w-24 items-center justify-center rounded-3xl bg-komiser-200 p-4">
<Image
src={`/assets/img/database/${
database === 'postgres' ? 'postgresql' : 'sqlite'
Expand Down
2 changes: 1 addition & 1 deletion dashboard/components/onboarding-wizard/PurplinCloud.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function PurplinCloud({ provider }: { provider: Provider }) {
width={500}
height={120}
/>
<div className="absolute top-[53%] left-[48%] -translate-x-1/2 -translate-y-1/2 transform rounded-full">
<div className="absolute left-[48%] top-[53%] -translate-x-1/2 -translate-y-1/2 transform rounded-full">
<Image
src={ProviderCls.providerImg(provider) as string}
alt={`${provider} Logo`}
Expand Down
10 changes: 5 additions & 5 deletions dashboard/components/onboarding-wizard/SelectInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ function SelectInput({
return (
<div className="relative">
<div
className="pointer-events-none absolute right-4
bottom-[1.15rem] text-komiser-600 transition-all"
className="pointer-events-none absolute bottom-[1.15rem]
right-4 text-komiser-600 transition-all"
>
{icon}
</div>
Expand All @@ -49,7 +49,7 @@ function SelectInput({
{ 'outline-2 outline-primary': isOpen }
)}
>
<div className="pointer-events-none flex w-full appearance-none items-center gap-2 rounded bg-white pt-[0.75rem] pb-[0.75rem] pl-4 pr-16 text-sm text-black-900">
<div className="pointer-events-none flex w-full appearance-none items-center gap-2 rounded bg-white pb-[0.75rem] pl-4 pr-16 pt-[0.75rem] text-sm text-black-900">
{displayValues[index].icon && displayValues[index].icon}
{displayValues[index].label}
</div>
Expand All @@ -61,15 +61,15 @@ function SelectInput({
onClick={toggle}
className="fixed inset-0 z-20 hidden animate-fade-in bg-transparent opacity-0 sm:block"
></div>
<div className="absolute top-[96px] z-[21] max-h-52 w-full overflow-hidden overflow-y-auto rounded-lg border border-black-130 bg-white py-2 px-3 shadow-lg">
<div className="absolute top-[96px] z-[21] max-h-52 w-full overflow-hidden overflow-y-auto rounded-lg border border-black-130 bg-white px-3 py-2 shadow-lg">
<div className="flex w-full flex-col gap-1">
{values.map((item, idx) => {
const isActive = value === item;
return (
<button
key={idx}
className={classNames(
'flex items-center rounded py-2 px-3 text-left text-sm text-black-400 hover:bg-black-150',
'flex items-center rounded px-3 py-2 text-left text-sm text-black-400 hover:bg-black-150',
{ 'bg-komiser-150': isActive }
)}
onClick={() => handleClick(item)}
Expand Down
2 changes: 1 addition & 1 deletion dashboard/components/select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ function Select({
onClick={toggle}
className="fixed inset-0 z-20 hidden animate-fade-in bg-transparent opacity-0 sm:block"
></div>
<div className="absolute top-[66px] z-[21] max-h-52 w-full overflow-hidden overflow-y-auto rounded-lg border border-black-130 bg-white py-2 px-3 shadow-lg">
<div className="absolute top-[66px] z-[21] max-h-52 w-full overflow-hidden overflow-y-auto rounded-lg border border-black-130 bg-white px-3 py-2 shadow-lg">
<div className="flex w-full flex-col gap-1">
{values.map((item, idx) => {
const isActive = value === item;
Expand Down
2 changes: 1 addition & 1 deletion dashboard/pages/onboarding/choose-cloud.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export default function ChooseCloud() {
width={500}
height={120}
/>
<div className="absolute top-[53%] left-[48%] -translate-x-1/2 -translate-y-1/2 transform rounded-full">
<div className="absolute left-[48%] top-[53%] -translate-x-1/2 -translate-y-1/2 transform rounded-full">
<Image
src={ProviderCls.providerImg(provider) as string}
alt={`${provider} Logo`}
Expand Down
105 changes: 105 additions & 0 deletions handlers/accounts_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package handlers

import (
"context"
"encoding/json"
"net/http"

"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"github.com/tailwarden/komiser/models"
)

func (handler *ApiHandler) IsOnboardedHandler(c *gin.Context) {
output := struct {
Onboarded bool `json:"onboarded"`
}{
Onboarded: false,
}

accounts := make([]models.Account, 0)
err := handler.db.NewRaw("SELECT * FROM accounts").Scan(handler.ctx, &accounts)
if err != nil {
logrus.WithError(err).Error("scan failed")
c.JSON(http.StatusInternalServerError, gin.H{"error": "scan failed"})
return
}

if len(accounts) > 0 {
output.Onboarded = true
}

c.JSON(http.StatusOK, output)
}

func (handler *ApiHandler) ListCloudAccountsHandler(c *gin.Context) {
accounts := make([]models.Account, 0)
err := handler.db.NewRaw("SELECT * FROM accounts").Scan(handler.ctx, &accounts)
if err != nil {
logrus.WithError(err).Error("scan failed")
c.JSON(http.StatusInternalServerError, gin.H{"error": "scan failed"})
return
}

c.JSON(http.StatusOK, accounts)
}

func (handler *ApiHandler) NewCloudAccountHandler(c *gin.Context) {
var account models.Account

err := json.NewDecoder(c.Request.Body).Decode(&account)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

result, err := handler.db.NewInsert().Model(&account).Exec(context.Background())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

accountId, _ := result.LastInsertId()
account.Id = accountId

if handler.telemetry {
handler.analytics.TrackEvent("creating_alert", map[string]interface{}{
"type": len(account.Credentials),
"provider": account.Provider,
})
}

c.JSON(http.StatusCreated, account)
}

func (handler *ApiHandler) DeleteCloudAccountHandler(c *gin.Context) {
accountId := c.Param("id")

account := new(models.Account)
_, err := handler.db.NewDelete().Model(account).Where("id = ?", accountId).Exec(handler.ctx)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

c.JSON(http.StatusOK, gin.H{"message": "account has been deleted"})
}

func (handler *ApiHandler) UpdateCloudAccountHandler(c *gin.Context) {
accountId := c.Param("id")

var account models.Account
err := json.NewDecoder(c.Request.Body).Decode(&account)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

_, err = handler.db.NewUpdate().Model(&account).Column("name", "provider", "credentials").Where("id = ?", accountId).Exec(handler.ctx)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

c.JSON(http.StatusOK, account)
}
6 changes: 6 additions & 0 deletions internal/api/v1/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ func Endpoints(ctx context.Context, telemetry bool, analytics utils.Analytics, d
router.POST("/alerts/test", api.TestEndpointHandler)

router.GET("/telemetry", api.TelemetryHandler)
router.GET("/is_onboarded", api.IsOnboardedHandler)

router.GET("/cloud_accounts", api.ListCloudAccountsHandler)
router.POST("/cloud_accounts", api.NewCloudAccountHandler)
router.DELETE("/cloud_accounts/:id", api.DeleteCloudAccountHandler)
router.PUT("/cloud_accounts/:id", api.UpdateCloudAccountHandler)

router.NoRoute(gin.WrapH(http.FileServer(assetFS())))

Expand Down
Loading