Browse Source

feat: Add platform pages. (#152)

main
Asklv GitHub 2 years ago
parent
commit
2a9e006095
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 737 additions and 6 deletions
  1. +2
    -0
      .gitignore
  2. +10
    -1
      server/config.docker.yaml
  3. +10
    -1
      server/config.example.yaml
  4. +10
    -1
      server/config.yaml
  5. +53
    -0
      server/internal/api/platform.go
  6. +21
    -0
      server/internal/config/config.go
  7. +1
    -0
      server/internal/middleware/hooks/gen_jwt_hook.go
  8. +26
    -0
      server/internal/params/response/platform.go
  9. +9
    -0
      server/internal/router/router.go
  10. +48
    -0
      server/internal/service/slack.go
  11. +27
    -0
      web/src/api/index/platform.ts
  12. +9
    -0
      web/src/api/response/platformModel.ts
  13. +26
    -0
      web/src/constants/index.ts
  14. +4
    -0
      web/src/style/reset-tdesign.less
  15. +9
    -0
      web/src/types/interface.d.ts
  16. +114
    -0
      web/src/views/platform/components/PlatformCard.vue
  17. +188
    -0
      web/src/views/platform/components/PlatformDialog.vue
  18. +43
    -0
      web/src/views/platform/index.less
  19. +127
    -3
      web/src/views/platform/index.vue

+ 2
- 0
.gitignore View File

@@ -356,3 +356,5 @@ dist
# config files, may contain sensitive information
dist/

# OpenKF dev config
server/config.yaml

+ 10
- 1
server/config.docker.yaml View File

@@ -64,4 +64,13 @@ openim:
secret: openkf
ip: 127.0.0.1
api_port: 10002
platform_id: 5 # web
platform_id: 5 # web

slack:
bot_token:
app_token:
app_id: # need to give string, like '1821.42'
client_id:
client_secret:
signing_secret:
verification_token:

+ 10
- 1
server/config.example.yaml View File

@@ -64,4 +64,13 @@ openim:
secret: openkf
ip: 127.0.0.1
api_port: 10002
platform_id: 5 # web
platform_id: 5 # web

slack:
bot_token:
app_token:
app_id: # need to give string, like '1821.42'
client_id:
client_secret:
signing_secret:
verification_token:

+ 10
- 1
server/config.yaml View File

@@ -64,4 +64,13 @@ openim:
secret: openkf
ip: 127.0.0.1
api_port: 10002
platform_id: 5 # web
platform_id: 5 # web

slack:
bot_token:
app_token:
app_id: # need to give string, like '1821.42'
client_id:
client_secret:
signing_secret:
verification_token:

+ 53
- 0
server/internal/api/platform.go View File

@@ -0,0 +1,53 @@
// Copyright © 2023 OpenIM open source community. All rights reserved.
//
// Licensed 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.

package api

import (
"github.com/gin-gonic/gin"

"github.com/OpenIMSDK/OpenKF/server/internal/common"
"github.com/OpenIMSDK/OpenKF/server/internal/common/response"
"github.com/OpenIMSDK/OpenKF/server/internal/service"
"github.com/OpenIMSDK/OpenKF/server/internal/utils"
)

// SlackConfig
// @Tags platform
// @Summary SlackConfig
// @Description Get slack config
// @Produce application/json
// @Security ApiKeyAuth
// @Success 200 {object} response.Response{msg=string} "Success"
// @Router /api/v1/platform/slack/config [get].
func SlackConfig(c *gin.Context) {
// TODO: Check user role.
if _, err := utils.GetUserUUID(c); err != nil {
response.FailWithCode(common.UNAUTHORIZED, c)

return
}

svc := service.NewSlackService(c)
// TODO: Add a table and save config to db.
// Now it only read from config file.
resp, err := svc.GetSlackConfig()
if err != nil {
response.FailWithCode(common.KF_RECORD_NOT_FOUND, c)

return
}

response.SuccessWithData(resp, c)
}

+ 21
- 0
server/internal/config/config.go View File

@@ -78,6 +78,15 @@ func ConfigInit(configPath string) {
ApiPort: GetIntOrDefault("openim.api_port", 10002),
PlatformID: GetIntOrDefault("openim.platform_id", 5),
},
Slack: Slack{
BotToken: GetString("slack.bot_token"),
AppToken: GetString("slack.app_token"),
AppID: GetString("slack.app_id"),
ClientID: GetString("slack.client_id"),
ClientSecret: GetString("slack.client_secret"),
SigningSecret: GetString("slack.signing_secret"),
VerificationToken: GetString("slack.verification_token"),
},
}
}

@@ -90,6 +99,7 @@ type config struct {
Minio Minio
Email Email
OpenIM OpenIM
Slack Slack
}

// App config.
@@ -161,3 +171,14 @@ type OpenIM struct {
ApiPort int `mapstructure:"api_port"`
PlatformID int `mapstructure:"platform_id"`
}

// Slack Bot config.
type Slack struct {
BotToken string `mapstructure:"bot_token"`
AppToken string `mapstructure:"app_token"`
AppID string `mapstructure:"app_id"`
ClientID string `mapstructure:"client_id"`
ClientSecret string `mapstructure:"client_secret"`
SigningSecret string `mapstructure:"signing_secret"`
VerificationToken string `mapstructure:"verification_token"`
}

+ 1
- 0
server/internal/middleware/hooks/gen_jwt_hook.go View File

@@ -43,6 +43,7 @@ func (h *JWT) Patterns() []string {
"/api/v1/user/*",
"/api/v1/community/*",
"/api/v1/admin/*",
"/api/v1/platform/*",
}
}



+ 26
- 0
server/internal/params/response/platform.go View File

@@ -0,0 +1,26 @@
// Copyright © 2023 OpenIM open source community. All rights reserved.
//
// Licensed 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.

package responseparams

// SlackConfigResponse slack config response.
type SlackConfigResponse struct {
BotToken string `json:"bot_token"`
AppToken string `json:"app_token"`
AppID string `json:"app_id"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
SigningSecret string `json:"signing_secret"`
VerificationToken string `json:"verification_token"`
}

+ 9
- 0
server/internal/router/router.go View File

@@ -117,6 +117,15 @@ func InitRouter() *gin.Engine {
command.POST("/callbackOfflinePushCommand", api.OfflinePush)
command.POST("/callbackOnlinePushCommand", api.OnlinePush)
}

// Platform callback api
platform := apiv1.Group("/platform")
{
slack := platform.Group("/slack")
{
slack.GET("/config", api.SlackConfig)
}
}
}

return r


+ 48
- 0
server/internal/service/slack.go View File

@@ -0,0 +1,48 @@
// Copyright © 2023 OpenIM open source community. All rights reserved.
//
// Licensed 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.

package service

import (
"github.com/OpenIMSDK/OpenKF/server/internal/config"
responseparams "github.com/OpenIMSDK/OpenKF/server/internal/params/response"
"github.com/gin-gonic/gin"
)

// SlackService bot service.
type SlackService struct {
Service
}

// NewSlackService return new service with gin context.
func NewSlackService(c *gin.Context) *SlackService {
return &SlackService{
Service: Service{
ctx: c,
},
}
}

// GetSlackConfig get slack config.
func (svc *SlackService) GetSlackConfig() (*responseparams.SlackConfigResponse, error) {
return &responseparams.SlackConfigResponse{
BotToken: config.Config.Slack.BotToken,
AppToken: config.Config.Slack.AppToken,
AppID: config.Config.Slack.AppID,
ClientID: config.Config.Slack.ClientID,
ClientSecret: config.Config.Slack.ClientSecret,
SigningSecret: config.Config.Slack.SigningSecret,
VerificationToken: config.Config.Slack.VerificationToken,
}, nil
}

+ 27
- 0
web/src/api/index/platform.ts View File

@@ -0,0 +1,27 @@
// Copyright © 2023 OpenIM open source community. All rights reserved.
//
// Licensed 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 { request } from '@/utils/request';
import { GetSlackConfigResponse } from '@/api/response/platformModel';

const API = {
SlackConfig: '/platform/slack/config',
};

// get slack info
export function getSlackConfig() {
return request.get<GetSlackConfigResponse>({
url: API.SlackConfig,
});
}

+ 9
- 0
web/src/api/response/platformModel.ts View File

@@ -0,0 +1,9 @@
export interface GetSlackConfigResponse {
bot_token: string,
app_token: string,
app_id: string,
client_id: string,
client_secret: string,
signing_secret: string,
verification_token: string,
}

+ 26
- 0
web/src/constants/index.ts View File

@@ -26,3 +26,29 @@ export enum ContentTypeEnum {
FormURLEncoded = 'application/x-www-form-urlencoded;charset=UTF-8',
FormData = 'multipart/form-data;charset=UTF-8',
}

// Platform type
export enum PlatformType {
Slack = 'Slack',
Web = 'Web',
}

// Define support platforms
export const PLATFORMS = [
{
order: 1,
avatar: 'https://github.com/OpenIMSDK/OpenKF/assets/47499836/73b94766-9968-4b66-b0b6-cc7a6ebfda69',
name: PlatformType.Slack,
description: 'Slack is a new way to communicate with your customer. It\'s faster, better organized, and more secure than email.',
is_enable: true,
tags: ['Slack', 'LLM'],
},
{
order: 2,
avatar: 'https://github.com/OpenIMSDK/OpenKF/assets/47499836/13292e53-68df-46f8-948c-1296ba3bf330',
name: PlatformType.Web,
description: 'Web is a basic way to communicate with your customer. It\'s light and easy to intergrate with your products.',
is_enable: false,
tags: ['Web', 'AI'],
}
];

+ 4
- 0
web/src/style/reset-tdesign.less View File

@@ -1,4 +1,8 @@
// reset for openkf
.t-menu__logo {
min-height: var(--td-comp-size-xxxxxl);
}

.kf-config-input {
.t-input {
border-radius: var(--td-radius-large);


+ 9
- 0
web/src/types/interface.d.ts View File

@@ -22,3 +22,12 @@ export interface MenuRoute {
name: string;
icon: string;
}

export interface Platform {
order: number;
avatar: string;
name: string;
description: string;
is_enable: boolean;
tags: string[];
}

+ 114
- 0
web/src/views/platform/components/PlatformCard.vue View File

@@ -0,0 +1,114 @@
<script setup lang="ts">
import { MoreIcon, ShopIcon } from 'tdesign-icons-vue-next';
import type { Platform } from '@/types/interface';
import { PropType } from 'vue';

// eslint-disable-next-line
const props = defineProps({
platform: {
type: Object as PropType<Platform>,
}
});
const emit = defineEmits(['manage-platform']);
const handleClickManage = (name: string) => {
emit('manage-platform', name);
};
</script>

<template>
<t-card theme="poster2" :bordered="false">
<template #avatar>
<t-avatar size="56px">
<template #icon>
<t-image
shape="circle"
:src="platform!.avatar"
:style="{ width: '120px', height: '120px' }"
/>
</template>
</t-avatar>
</template>
<template #status>
<t-tag :theme="platform!.is_enable ? 'success' : 'default'" :disabled="!platform!.is_enable">{{
platform!.is_enable ? 'On' : 'Off'
}}</t-tag>
</template>
<template #content>
<p class="list-card-item_detail--name">{{ platform!.name }}</p>
<p class="list-card-item_detail--desc">{{ platform!.description }}</p>
</template>
<template #footer>
<t-tag v-for="item in platform!.tags" :key="item" shape="mark" theme="primary" style="margin-left: 5px;">{{ item }}</t-tag>
</template>
<template #actions>
<t-dropdown
:disabled="!platform!.is_enable"
trigger="click"
:options="[
{
content: 'Manage',
value: 'manage',
onClick: () => handleClickManage(platform!.name),
},
]"
>
<t-button theme="default" :disabled="!platform?.is_enable" shape="square" variant="text">
<more-icon />
</t-button>
</t-dropdown>
</template>
</t-card>
</template>

<script lang="ts">
export default {
name: 'PlatformCard',
};
</script>
<style lang="less" scoped>
.list-card {
height: 100%;

&-operation {
display: flex;
justify-content: space-between;
margin-bottom: var(--td-comp-margin-xxl);

.search-input {
width: 360px;
}
}

&-item {
padding: var(--td-comp-paddingTB-xl) var(--td-comp-paddingTB-xl);

:deep(.t-card__header) {
padding: 0;
}

:deep(.t-card__body) {
padding: 0;
margin-top: var(--td-comp-margin-xxl);
margin-bottom: var(--td-comp-margin-xxl);
}

:deep(.t-card__footer) {
padding: 0;
}
}

&-pagination {
padding: var(--td-comp-paddingTB-xl) var(--td-comp-paddingTB-xl);
}

&-loading {
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
}
</style>

+ 188
- 0
web/src/views/platform/components/PlatformDialog.vue View File

@@ -0,0 +1,188 @@
<script setup lang="ts">
import { reactive, onMounted } from 'vue';
import { MessagePlugin } from 'tdesign-vue-next';
import { PlatformType } from '@/constants';
import { getSlackConfig } from '@/api/index/platform';

const emit = defineEmits(['close'])
const props = defineProps({
confirmVisible: Boolean,
selectPlatformType: String,
});
const onCancel = () => {
console.log('cancel');
emit('close', false);
};

onMounted(async () => {
console.log(props.selectPlatformType)
if (props.selectPlatformType == PlatformType.Slack) {
try {
const res = await getSlackConfig();
slackConfig.bot_token = res.bot_token;
slackConfig.app_token = res.app_token;
slackConfig.app_id = res.app_id;
slackConfig.client_id = res.client_id;
slackConfig.client_secret = res.client_secret;
slackConfig.signing_secret = res.signing_secret;
slackConfig.verification_token = res.verification_token;
} catch (e) {
console.log(e);
MessagePlugin.error('Get slack config error!');
}
}
});

// slack config
const slackConfig = reactive({
bot_token: '',
app_token: '',
app_id: '',
client_id: '',
client_secret: '',
signing_secret: '',
verification_token: '',
});

const onInstall = () => {
// generate slack install link.
const slackInstall = `https://slack.com/oauth/v2/authorize?client_id=${slackConfig.client_id}&install_redirect=oauth&scope=app_mentions:read,chat:write,users:read`
emit('close', false);
window.open(slackInstall);
};

</script>

<template>
<t-dialog
width="50%"
destroyOnClose
:visible="confirmVisible"
:on-close="onCancel"
:on-cancel="onCancel"
confirm-btn="Install"
cancel-btn="Cancel"
@confirm="onInstall"
>
<template #header>
<div class="config-title" style="padding: 0px;">
<h2 class="welcome">Bot</h2>
<h1 class="sub-title">
Add a new custom service LLM Bot, with your local config.
</h1>
</div>
</template>
<template #body>
<t-list v-if="selectPlatformType == PlatformType.Slack" :name="PlatformType.Slack" :split="true" class="t-list-wrapper">
<t-list-item>
<t-list-item-meta title="BotToken" description="Slack bot token" />
<template #action>
<t-input
type="input"
class="kf-config-input"
placeholder="bot_token"
clearable
autoWidth
readonly
align="center"
v-model="slackConfig.bot_token" />
</template>
</t-list-item>
<t-list-item>
<t-list-item-meta title="AppToken" description="Slack app token" />
<template #action>
<t-input
type="text"
class="kf-config-input"
placeholder="app_token"
clearable
autoWidth
readonly
align="center"
v-model="slackConfig.app_token" />
</template>
</t-list-item>
<t-list-item>
<t-list-item-meta title="AppID" description="Slack app id" />
<template #action>
<t-input
type="text"
class="kf-config-input"
placeholder="app_id"
clearable
autoWidth
readonly
align="center"
v-model="slackConfig.app_id" />
</template>
</t-list-item>
<t-list-item>
<t-list-item-meta title="ClientId" description="Slack client id" />
<template #action>
<t-input
type="text"
class="kf-config-input"
placeholder="client_id"
clearable
autoWidth
readonly
align="center"
v-model="slackConfig.client_id" />
</template>
</t-list-item>
<t-list-item>
<t-list-item-meta title="ClientSecret" description="Slack client secret" />
<template #action>
<t-input
type="password"
class="kf-config-input"
placeholder="client_secret"
clearable
autoWidth
readonly
align="center"
v-model="slackConfig.client_secret" />
</template>
</t-list-item>
<t-list-item>
<t-list-item-meta title="SigningSecret" description="Slack signing secret" />
<template #action>
<t-input
type="password"
class="kf-config-input"
placeholder="signing_secret"
clearable
autoWidth
readonly
align="center"
v-model="slackConfig.signing_secret" />
</template>
</t-list-item>
<t-list-item>
<t-list-item-meta title="VerificationToken" description="Slack verification token" />
<template #action>
<t-input
type="text"
class="kf-config-input"
placeholder="verification_token"
clearable
autoWidth
readonly
align="center"
v-model="slackConfig.verification_token" />
</template>
</t-list-item>
</t-list>
</template>
</t-dialog>
</template>

<script lang="ts">
export default {
name: 'PlatformDialog',
};
</script>

<style lang="less" scoped>
@import url('../index.less');
</style>

+ 43
- 0
web/src/views/platform/index.less View File

@@ -0,0 +1,43 @@
.light {
&.platform-wrapper {
background-color: var(--td-bg-color-page);
}
}

.dark {
&.platform-wrapper {
background-color: var(--td-bg-color-page);
}
}

.platform-wrapper {
min-height: 100vh;
display: flex;
flex-direction: column;
background-size: cover;
background-position: 100%;
position: relative;
}

.platform-header {
position: relative;
padding: var(--td-comp-paddingTB-l) var(--td-comp-paddingLR-xl);
border-bottom: 1px solid var(--td-component-border);
background-color: var(--td-font-white-1);
height: var(--td-comp-size-xxxxxl);
.welcome {
font-size: var(--td-font-size-title-large);
font-weight: 700;
}
.sub-title {
font-size: var(--td-font-size-body-small);
font-weight: 400;
}
}

.t-list-wrapper {
border: 1px solid var(--td-component-border);
border-radius: var(--td-radius-large);
margin-bottom: var(--td-comp-margin-l);
}

+ 127
- 3
web/src/views/platform/index.vue View File

@@ -1,7 +1,131 @@
<script setup lang="ts"></script>
<script setup lang="ts">
import { ref } from 'vue';
import { Icon, RefreshIcon } from 'tdesign-icons-vue-next';
import PlatformCard from './components/PlatformCard.vue';
import PlatformDialog from './components/PlatformDialog.vue';
import { PLATFORMS } from '@/constants'
import { MessagePlugin } from 'tdesign-vue-next';
import { PlatformType } from '@/constants';

const isDark = ref(false);
const platforms = ref(PLATFORMS);
const confirmVisible = ref(false);
const selectPlatformType = ref(PlatformType.Slack);

const checkUpdate = () => {
console.log('check update');
MessagePlugin.info('TODO...');
};

const handleManagePlatform = (name: PlatformType) => {
confirmVisible.value = true;
selectPlatformType.value = name;
};
const onCancel = () => {
confirmVisible.value = false;
};

</script>

<template>
<div>platform</div>
<div :class="[isDark ? 'dark' : 'light', 'platform-wrapper']">
<div class="platform-header ">
<t-row align="middle">
<t-col span="10">
<h2 class="welcome">Platform</h2>
<h1 class="sub-title">
Check and use various platform access.
</h1>
</t-col>
<t-col span="2" align="center">
<!-- TODO: check OpenKF version and update. -->
<t-tooltip content="Check OpenKF Version [TODO...]" >
<t-button theme="default" variant="outline" @click="checkUpdate">
<template #icon><RefreshIcon /></template>
Check Update
</t-button>
</t-tooltip>
</t-col>
</t-row>
</div>

<div class="list-card-item">
<t-row :gutter="[16, 16]">
<t-col
v-for="item in platforms"
:key="item.order"
:lg="4"
:xs="6"
:xl="3"
>
<platform-card
class="list-card-item"
:platform="item"
@manage-platform="handleManagePlatform(item.name)"
/>
</t-col>
</t-row>
</div>

<!-- Do not support delete function -->
<platform-dialog
v-model:visible="confirmVisible"
:selectPlatformType="selectPlatformType"
@close="onCancel" />
</div>
</template>

<style lang="scss" scoped></style>
<script lang="ts">
export default {
name: 'PlatformIndex',
};
</script>

<style lang="less" scoped>
@import url('./index.less');

.list-card {
height: 100%;

&-operation {
display: flex;
justify-content: space-between;
margin-bottom: var(--td-comp-margin-xxl);

.search-input {
width: 360px;
}
}

&-item {
padding: var(--td-comp-paddingTB-xl) var(--td-comp-paddingTB-xl);

:deep(.t-card__header) {
padding: 0;
}

:deep(.t-card__body) {
padding: 0;
margin-top: var(--td-comp-margin-xxl);
margin-bottom: var(--td-comp-margin-xxl);
}

:deep(.t-card__footer) {
padding: 0;
}
}

&-pagination {
padding: var(--td-comp-paddingTB-xl) var(--td-comp-paddingTB-xl);
}

&-loading {
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
}
</style>

Loading…
Cancel
Save