package handler import ( "fmt" "io" "strconv" "strings" "github.com/jumpserver/koko/pkg/common" "github.com/jumpserver/koko/pkg/config" "github.com/jumpserver/koko/pkg/model" "github.com/jumpserver/koko/pkg/service" "github.com/jumpserver/koko/pkg/utils" ) func NewAssetPagination(term *utils.Terminal, assets []model.Asset) *AssetPagination { assetPage := &AssetPagination{term: term, assets: assets} assetPage.Initial() return assetPage } type AssetPagination struct { term *utils.Terminal assets []model.Asset page *common.Pagination currentData []model.Asset } func (p *AssetPagination) Initial() { pageData := make([]interface{}, len(p.assets)) for i, v := range p.assets { pageData[i] = v } pageSize := p.getPageSize() p.page = common.NewPagination(pageData, pageSize) firstPageData := p.page.GetPageData(1) p.currentData = make([]model.Asset, len(firstPageData)) for i, item := range firstPageData { p.currentData[i] = item.(model.Asset) } } func (p *AssetPagination) getPageSize() int { var ( pageSize int minHeight = 8 // 分页显示的最小高度 ) _, height := p.term.GetSize() switch config.GetConf().AssetListPageSize { case "auto": pageSize = height - minHeight case "all": pageSize = len(p.assets) default: if value, err := strconv.Atoi(config.GetConf().AssetListPageSize); err == nil { pageSize = value } else { pageSize = height - minHeight } } if pageSize <= 0 { pageSize = 1 } return pageSize } func (p *AssetPagination) Start() []model.Asset { p.term.SetPrompt(": ") defer p.term.SetPrompt("Opt> ") for { // 总数据小于page size,则显示所有资产且退出 if p.page.PageSize() >= p.page.TotalCount() { p.currentData = p.assets p.displayPageAssets() return []model.Asset{} } p.displayPageAssets() p.displayTipsInfo() line, err := p.term.ReadLine() if err != nil { return []model.Asset{} } pageSize := p.getPageSize() p.page.SetPageSize(pageSize) line = strings.TrimSpace(line) switch len(line) { case 0, 1: switch strings.ToLower(line) { case "p": if !p.page.HasPrev() { continue } prePageData := p.page.GetPrevPageData() if len(p.currentData) != len(prePageData) { p.currentData = make([]model.Asset, len(prePageData)) } for i, item := range prePageData { p.currentData[i] = item.(model.Asset) } case "", "n": if !p.page.HasNext() { continue } nextPageData := p.page.GetNextPageData() if len(p.currentData) != len(nextPageData) { p.currentData = make([]model.Asset, len(nextPageData)) } for i, item := range nextPageData { p.currentData[i] = item.(model.Asset) } case "b", "q": return []model.Asset{} default: if indexID, err := strconv.Atoi(line); err == nil { if indexID > 0 && indexID <= len(p.currentData) { return []model.Asset{p.currentData[indexID-1]} } } } default: if indexID, err := strconv.Atoi(line); err == nil { if indexID > 0 && indexID <= len(p.currentData) { return []model.Asset{p.currentData[indexID-1]} } } } } } func (p *AssetPagination) displayPageAssets() { Labels := []string{getI18nFromMap("ID"), getI18nFromMap("Hostname"), getI18nFromMap("IP"), getI18nFromMap("Comment")} fields := []string{"ID", "hostname", "IP", "comment"} data := make([]map[string]string, len(p.currentData)) for i, j := range p.currentData { row := make(map[string]string) row["ID"] = strconv.Itoa(i + 1) row["hostname"] = j.Hostname row["IP"] = j.IP comments := make([]string, 0) for _, item := range strings.Split(strings.TrimSpace(j.Comment), "\r\n") { if strings.TrimSpace(item) == "" { continue } comments = append(comments, strings.ReplaceAll(strings.TrimSpace(item), " ", ",")) } row["comment"] = strings.Join(comments, "|") data[i] = row } w, _ := p.term.GetSize() caption := fmt.Sprintf(getI18nFromMap("AssetTableCaption"), p.page.CurrentPage(), p.page.PageSize(), p.page.TotalPage(), p.page.TotalCount(), ) caption = utils.WrapperString(caption, utils.Green) table := common.WrapperTable{ Fields: fields, Labels: Labels, FieldsSize: map[string][3]int{ "ID": {0, 0, 5}, "hostname": {0, 8, 0}, "IP": {0, 15, 40}, "comment": {0, 0, 0}, }, Data: data, TotalSize: w, Caption: caption, TruncPolicy: common.TruncMiddle, } table.Initial() _, _ = p.term.Write([]byte(utils.CharClear)) _, _ = p.term.Write([]byte(table.Display())) } func (p *AssetPagination) displayTipsInfo() { displayAssetPaginationTipsInfo(p.term) } func NewUserPagination(term *utils.Terminal, uid, search string, policy bool) *UserAssetPagination { return &UserAssetPagination{ UserID: uid, offset: 0, limit: 0, search: search, term: term, displayPolicy: policy, Data: model.AssetsPaginationResponse{}, } } type UserAssetPagination struct { UserID string offset int limit int search string term *utils.Terminal displayPolicy bool Data model.AssetsPaginationResponse IsNeedProxy bool currentData []model.Asset } func (p *UserAssetPagination) Start() []model.Asset { p.term.SetPrompt(": ") defer p.term.SetPrompt("Opt> ") for { p.retrieveData() if p.displayPolicy && p.Data.Total == 1 { p.IsNeedProxy = true return p.Data.Data } // 无上下页,则退出循环 if p.Data.NextURL == "" && p.Data.PreviousURL == "" { p.displayPageAssets() return p.currentData } inLoop: p.displayPageAssets() p.displayTipsInfo() line, err := p.term.ReadLine() if err != nil { return p.currentData } line = strings.TrimSpace(line) switch len(line) { case 0, 1: switch strings.ToLower(line) { case "p": if p.Data.PreviousURL == "" { continue } p.offset -= p.limit case "", "n": if p.Data.NextURL == "" { continue } p.offset += p.limit case "b", "q": return []model.Asset{} default: if indexID, err := strconv.Atoi(line); err == nil { if indexID > 0 && indexID <= len(p.currentData) { p.IsNeedProxy = true return []model.Asset{p.currentData[indexID-1]} } } goto inLoop } default: if indexID, err := strconv.Atoi(line); err == nil { if indexID > 0 && indexID <= len(p.currentData) { p.IsNeedProxy = true return []model.Asset{p.currentData[indexID-1]} } } goto inLoop } } } func (p *UserAssetPagination) displayPageAssets() { if len(p.Data.Data) == 0 { _, _ = p.term.Write([]byte(getI18nFromMap("NoAssets")+"\n\r")) return } Labels := []string{getI18nFromMap("ID"), getI18nFromMap("Hostname"), getI18nFromMap("IP"), getI18nFromMap("Comment")} fields := []string{"ID", "hostname", "IP", "comment"} p.currentData = model.AssetList(p.Data.Data).SortBy(config.GetConf().AssetListSortBy) data := make([]map[string]string, len(p.currentData)) for i, j := range p.currentData { row := make(map[string]string) row["ID"] = strconv.Itoa(i + 1) row["hostname"] = j.Hostname row["IP"] = j.IP comments := make([]string, 0) for _, item := range strings.Split(strings.TrimSpace(j.Comment), "\r\n") { if strings.TrimSpace(item) == "" { continue } comments = append(comments, strings.ReplaceAll(strings.TrimSpace(item), " ", ",")) } row["comment"] = strings.Join(comments, "|") data[i] = row } w, _ := p.term.GetSize() var pageSize int var totalPage int var currentPage int var totalCount int var currentOffset int currentOffset = p.offset + len(p.currentData) switch p.limit { case 0: pageSize = len(p.currentData) totalCount = pageSize totalPage = 1 currentPage = 1 default: pageSize = p.limit totalCount = p.Data.Total switch totalCount % pageSize { case 0: totalPage = totalCount / pageSize default: totalPage = (totalCount / pageSize) + 1 } switch currentOffset % pageSize { case 0: currentPage = currentOffset / pageSize default: currentPage = (currentOffset / pageSize) + 1 } } caption := fmt.Sprintf(getI18nFromMap("AssetTableCaption"), currentPage, pageSize, totalPage, totalCount) caption = utils.WrapperString(caption, utils.Green) table := common.WrapperTable{ Fields: fields, Labels: Labels, FieldsSize: map[string][3]int{ "ID": {0, 0, 5}, "hostname": {0, 8, 0}, "IP": {0, 15, 40}, "comment": {0, 0, 0}, }, Data: data, TotalSize: w, Caption: caption, TruncPolicy: common.TruncMiddle, } table.Initial() _, _ = p.term.Write([]byte(utils.CharClear)) _, _ = p.term.Write([]byte(table.Display())) } func (p *UserAssetPagination) displayTipsInfo() { displayAssetPaginationTipsInfo(p.term) } func (p *UserAssetPagination) retrieveData() { p.limit = getPageSize(p.term) if p.limit == 0 || p.offset < 0 || p.limit >= p.Data.Total { p.offset = 0 } p.Data = service.GetUserAssets(p.UserID, p.search, p.limit, p.offset) } func getPageSize(term *utils.Terminal) int { var ( pageSize int minHeight = 8 // 分页显示的最小高度 ) _, height := term.GetSize() conf := config.GetConf() switch conf.AssetListPageSize { case "auto": pageSize = height - minHeight case "all": return 0 default: if value, err := strconv.Atoi(conf.AssetListPageSize); err == nil { pageSize = value } else { pageSize = height - minHeight } } if pageSize <= 0 { pageSize = 1 } return pageSize } func displayAssetPaginationTipsInfo(w io.Writer) { utils.IgnoreErrWriteString(w, getI18nFromMap("LoginTip")) utils.IgnoreErrWriteString(w, getI18nFromMap("PageActionTip")) }