| @@ -356,3 +356,5 @@ dist | |||
| # config files, may contain sensitive information | |||
| dist/ | |||
| # OpenKF dev config | |||
| server/config.yaml | |||
| @@ -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: | |||
| @@ -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: | |||
| @@ -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: | |||
| @@ -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) | |||
| } | |||
| @@ -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"` | |||
| } | |||
| @@ -43,6 +43,7 @@ func (h *JWT) Patterns() []string { | |||
| "/api/v1/user/*", | |||
| "/api/v1/community/*", | |||
| "/api/v1/admin/*", | |||
| "/api/v1/platform/*", | |||
| } | |||
| } | |||
| @@ -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"` | |||
| } | |||
| @@ -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 | |||
| @@ -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 | |||
| } | |||
| @@ -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, | |||
| }); | |||
| } | |||
| @@ -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,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'], | |||
| } | |||
| ]; | |||
| @@ -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); | |||
| @@ -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[]; | |||
| } | |||
| @@ -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> | |||
| @@ -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> | |||
| @@ -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); | |||
| } | |||
| @@ -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> | |||