mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-09-22 08:00:53 +00:00
perf: significantly improve app store homepage loading speed (#11013)
This commit is contained in:
@@ -6,6 +6,8 @@ import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/i18n"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// @Tags App
|
||||
@@ -192,3 +194,28 @@ func (b *BaseApi) GetAppListUpdate(c *gin.Context) {
|
||||
}
|
||||
helper.SuccessWithData(c, res)
|
||||
}
|
||||
|
||||
// @Tags App
|
||||
// @Summary Get app icon by app_id
|
||||
// @Accept json
|
||||
// @Param appId path integer true "app id"
|
||||
// @Success 200 {file} file "app icon"
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /apps/icon/:appId [get]
|
||||
func (b *BaseApi) GetAppIcon(c *gin.Context) {
|
||||
appID, err := helper.GetIntParamByKey(c, "appID")
|
||||
if err != nil {
|
||||
helper.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
iconBytes, err := appService.GetAppIcon(appID)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
c.Header("Content-Type", "image/png")
|
||||
c.Header("Cache-Control", "public, max-age=31536000, immutable")
|
||||
c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat))
|
||||
c.Data(http.StatusOK, "image/png", iconBytes)
|
||||
}
|
||||
|
||||
@@ -33,16 +33,10 @@ type AppItem struct {
|
||||
Key string `json:"key"`
|
||||
ID uint `json:"id"`
|
||||
Description string `json:"description"`
|
||||
Icon string `json:"icon"`
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Resource string `json:"resource"`
|
||||
Installed bool `json:"installed"`
|
||||
Versions []string `json:"versions"`
|
||||
Limit int `json:"limit"`
|
||||
Tags []TagDTO `json:"tags"`
|
||||
Github string `json:"github"`
|
||||
Website string `json:"website"`
|
||||
Tags []string `json:"tags"`
|
||||
GpuSupport bool `json:"gpuSupport"`
|
||||
Recommend int `json:"recommend"`
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ type AppService struct {
|
||||
}
|
||||
|
||||
type IAppService interface {
|
||||
PageApp(ctx *gin.Context, req request.AppSearch) (interface{}, error)
|
||||
PageApp(ctx *gin.Context, req request.AppSearch) (*response.AppRes, error)
|
||||
GetAppTags(ctx *gin.Context) ([]response.TagDTO, error)
|
||||
GetApp(ctx *gin.Context, key string) (*response.AppDTO, error)
|
||||
GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error)
|
||||
@@ -46,13 +46,14 @@ type IAppService interface {
|
||||
GetAppUpdate() (*response.AppUpdateRes, error)
|
||||
GetAppDetailByID(id uint) (*response.AppDetailDTO, error)
|
||||
SyncAppListFromLocal(taskID string)
|
||||
GetAppIcon(appID uint) ([]byte, error)
|
||||
}
|
||||
|
||||
func NewIAppService() IAppService {
|
||||
return &AppService{}
|
||||
}
|
||||
|
||||
func (a AppService) PageApp(ctx *gin.Context, req request.AppSearch) (interface{}, error) {
|
||||
func (a AppService) PageApp(ctx *gin.Context, req request.AppSearch) (*response.AppRes, error) {
|
||||
var opts []repo.DBOption
|
||||
opts = append(opts, appRepo.OrderByRecommend())
|
||||
if req.Name != "" {
|
||||
@@ -98,7 +99,7 @@ func (a AppService) PageApp(ctx *gin.Context, req request.AppSearch) (interface{
|
||||
}
|
||||
opts = append(opts, repo.WithByIDs(appIds))
|
||||
}
|
||||
var res response.AppRes
|
||||
res := &response.AppRes{}
|
||||
|
||||
total, apps, err := appRepo.Page(req.Page, req.PageSize, opts...)
|
||||
if err != nil {
|
||||
@@ -120,12 +121,7 @@ func (a AppService) PageApp(ctx *gin.Context, req request.AppSearch) (interface{
|
||||
ID: ap.ID,
|
||||
Name: ap.Name,
|
||||
Key: ap.Key,
|
||||
Type: ap.Type,
|
||||
Icon: ap.Icon,
|
||||
Resource: ap.Resource,
|
||||
Limit: ap.Limit,
|
||||
Website: ap.Website,
|
||||
Github: ap.Github,
|
||||
GpuSupport: ap.GpuSupport,
|
||||
Recommend: ap.Recommend,
|
||||
Description: ap.GetDescription(ctx),
|
||||
@@ -135,7 +131,9 @@ func (a AppService) PageApp(ctx *gin.Context, req request.AppSearch) (interface{
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
appDTO.Tags = tags
|
||||
for _, tag := range tags {
|
||||
appDTO.Tags = append(appDTO.Tags, tag.Name)
|
||||
}
|
||||
if ap.Type == constant.RuntimePHP || ap.Type == constant.RuntimeGo || ap.Type == constant.RuntimeNode || ap.Type == constant.RuntimePython || ap.Type == constant.RuntimeJava || ap.Type == constant.RuntimeDotNet {
|
||||
details, _ := appDetailRepo.GetBy(appDetailRepo.WithAppId(ap.ID))
|
||||
var ids []uint
|
||||
@@ -1161,3 +1159,15 @@ func (a AppService) SyncAppListFromRemote(taskID string) (err error) {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a AppService) GetAppIcon(appID uint) ([]byte, error) {
|
||||
app, err := appRepo.GetFirst(repo.WithByID(appID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
iconBytes, err := base64.StdEncoding.DecodeString(app.Icon)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return iconBytes, nil
|
||||
}
|
||||
|
||||
@@ -1555,7 +1555,6 @@ func handleInstalled(appInstallList []model.AppInstall, updated bool, sync bool)
|
||||
Message: installed.Message,
|
||||
HttpPort: installed.HttpPort,
|
||||
HttpsPort: installed.HttpsPort,
|
||||
Icon: installed.App.Icon,
|
||||
AppName: installed.App.Name,
|
||||
AppKey: installed.App.Key,
|
||||
AppType: installed.App.Type,
|
||||
|
||||
@@ -22,6 +22,7 @@ func (a *AppRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
appRouter.GET("/details/:id", baseApi.GetAppDetailByID)
|
||||
appRouter.POST("/install", baseApi.InstallApp)
|
||||
appRouter.GET("/tags", baseApi.GetAppTags)
|
||||
appRouter.GET("/icon/:appID", baseApi.GetAppIcon)
|
||||
|
||||
appRouter.POST("/installed/check", baseApi.CheckAppInstalled)
|
||||
appRouter.POST("/installed/loadport", baseApi.LoadPort)
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ReqPage, CommonModel } from '.';
|
||||
export namespace App {
|
||||
export interface App extends CommonModel {
|
||||
name: string;
|
||||
icon: string;
|
||||
icon?: string;
|
||||
key: string;
|
||||
tags: Tag[];
|
||||
shortDescZh: string;
|
||||
@@ -14,8 +14,8 @@ export namespace App {
|
||||
type: string;
|
||||
status: string;
|
||||
limit: number;
|
||||
website: string;
|
||||
github: string;
|
||||
website?: string;
|
||||
github?: string;
|
||||
readme: string;
|
||||
}
|
||||
|
||||
@@ -44,9 +44,22 @@ export namespace App {
|
||||
sort: number;
|
||||
}
|
||||
|
||||
export interface AppItem {
|
||||
name: string;
|
||||
key: string;
|
||||
id: number;
|
||||
description: string;
|
||||
status: string;
|
||||
installed: boolean;
|
||||
limit: number;
|
||||
tags: string[];
|
||||
gpuSupport: boolean;
|
||||
recommend: number;
|
||||
}
|
||||
|
||||
export interface AppResPage {
|
||||
total: number;
|
||||
items: App.AppDTO[];
|
||||
items: AppItem[];
|
||||
}
|
||||
|
||||
export interface AppUpdateRes {
|
||||
|
||||
@@ -128,3 +128,9 @@ export const syncCutomAppStore = (req: App.AppStoreSync) => {
|
||||
export const getCurrentNodeCustomAppConfig = () => {
|
||||
return http.get<App.CustomAppStoreConfig>(`/custom/app/config`);
|
||||
};
|
||||
|
||||
export function getAppIconUrl(appId: number, node?: string): string {
|
||||
const baseURL = import.meta.env.VITE_API_URL as string;
|
||||
const params = node ? `?operateNode=${node}` : '';
|
||||
return `${baseURL}/apps/icon/${appId}${params}`;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<el-card>
|
||||
<div class="app-wrapper" @click="openDetail(app.key)">
|
||||
<div class="app-image">
|
||||
<el-avatar shape="square" :size="60" :src="'data:image/png;base64,' + app.icon" />
|
||||
<el-avatar shape="square" :size="60" :src="getAppIconUrl(app.id, currentNode)" />
|
||||
</div>
|
||||
<div class="app-content">
|
||||
<div class="content-top">
|
||||
@@ -23,7 +23,7 @@
|
||||
<div class="app-tags">
|
||||
<el-tag v-for="(tag, ind) in app.tags" :key="ind" type="info">
|
||||
<span>
|
||||
{{ tag.name }}
|
||||
{{ tag }}
|
||||
</span>
|
||||
</el-tag>
|
||||
<el-tag v-if="app.status === 'TakeDown'" class="p-mr-5">
|
||||
@@ -48,6 +48,10 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getAppIconUrl } from '@/api/modules/app';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
const { currentNode } = useGlobalStore();
|
||||
|
||||
defineProps({
|
||||
app: {
|
||||
type: Object,
|
||||
|
||||
@@ -107,7 +107,7 @@ const req = reactive({
|
||||
showCurrentArch: false,
|
||||
});
|
||||
|
||||
const apps = ref<App.AppDTO[]>([]);
|
||||
const apps = ref<App.AppItem[]>([]);
|
||||
const loading = ref(false);
|
||||
const canUpdate = ref(false);
|
||||
const syncing = ref(false);
|
||||
|
||||
@@ -3,7 +3,12 @@
|
||||
<div class="brief" v-loading="loadingApp">
|
||||
<div class="detail flex">
|
||||
<div class="w-12 h-12 rounded p-1 shadow-md icon">
|
||||
<img :src="app.icon" alt="App Icon" class="w-full h-full rounded" style="object-fit: contain" />
|
||||
<img
|
||||
:src="getAppIconUrl(app.id, currentNode)"
|
||||
alt="App Icon"
|
||||
class="w-full h-full rounded"
|
||||
style="object-fit: contain"
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<div class="name mb-2">
|
||||
@@ -66,17 +71,15 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getAppByKey, getAppDetail } from '@/api/modules/app';
|
||||
import { getAppByKey, getAppDetail, getAppIconUrl } from '@/api/modules/app';
|
||||
import MdEditor from 'md-editor-v3';
|
||||
import { ref } from 'vue';
|
||||
import Install from './install/index.vue';
|
||||
import { GlobalStore } from '@/store';
|
||||
import { computeSizeFromMB } from '@/utils/util';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { jumpToInstall } from '@/utils/app';
|
||||
|
||||
const globalStore = GlobalStore();
|
||||
const { isDarkTheme } = storeToRefs(globalStore);
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
const { currentNode, isDarkTheme } = useGlobalStore();
|
||||
|
||||
const app = ref<any>({});
|
||||
const appDetail = ref<any>({});
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
@click="openDetail(installed.appKey)"
|
||||
shape="square"
|
||||
:size="66"
|
||||
:src="'data:image/png;base64,' + installed.icon"
|
||||
:src="getAppIconUrl(installed.appID, currentNode)"
|
||||
/>
|
||||
</div>
|
||||
</el-col>
|
||||
@@ -417,7 +417,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { searchAppInstalled, installedOp, appInstalledDeleteCheck } from '@/api/modules/app';
|
||||
import { searchAppInstalled, installedOp, appInstalledDeleteCheck, getAppIconUrl } from '@/api/modules/app';
|
||||
import { onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
@@ -444,8 +444,8 @@ import Tags from '@/views/app-store/components/tag.vue';
|
||||
import SvgIcon from '@/components/svg-icon/svg-icon.vue';
|
||||
import MainDiv from '@/components/main-div/index.vue';
|
||||
import { routerToFileWithPath, routerToNameWithQuery } from '@/utils/router';
|
||||
import { GlobalStore } from '@/store';
|
||||
const globalStore = GlobalStore();
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
const { currentNode, isMaster, currentNodeAddr } = useGlobalStore();
|
||||
|
||||
const data = ref<any>();
|
||||
const loading = ref(false);
|
||||
@@ -762,8 +762,8 @@ const getConfig = async () => {
|
||||
defaultLink.value = res.data;
|
||||
return;
|
||||
}
|
||||
if (!globalStore.isMaster || globalStore.currentNodeAddr != '127.0.0.1') {
|
||||
defaultLink.value = globalStore.currentNodeAddr;
|
||||
if (!isMaster.value || currentNodeAddr.value != '127.0.0.1') {
|
||||
defaultLink.value = currentNodeAddr.value;
|
||||
}
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
@@ -51,7 +51,7 @@ const props = defineProps({
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const apps = ref<App.App[]>([]);
|
||||
const apps = ref<App.AppItem[]>([]);
|
||||
const appVersions = ref<string[]>([]);
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
const runtime = useVModel(props, 'modelValue', emit);
|
||||
|
||||
@@ -222,7 +222,7 @@ interface OperateRrops {
|
||||
}
|
||||
|
||||
const open = ref(false);
|
||||
const apps = ref<App.App[]>([]);
|
||||
const apps = ref<App.AppItem[]>([]);
|
||||
const runtimeForm = ref<FormInstance>();
|
||||
const loading = ref(false);
|
||||
const initParam = ref(false);
|
||||
|
||||
@@ -548,7 +548,7 @@ const appReq = reactive({
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
});
|
||||
const apps = ref<App.App[]>([]);
|
||||
const apps = ref<App.AppItem[]>([]);
|
||||
const preCheckRef = ref();
|
||||
const staticPath = ref('');
|
||||
const runtimeResource = ref('appstore');
|
||||
|
||||
Reference in New Issue
Block a user