From bcda6f6fe64feca23d01490650f4ed9af41160f4 Mon Sep 17 00:00:00 2001 From: Acbox Date: Sun, 29 Mar 2026 18:49:30 +0800 Subject: [PATCH] refactor: replace Load More with Pagination across frontend and backend - Replace all "Load More" / "Show More" buttons with Pagination components in model-list, bot-compaction, and bot-heartbeat views - Convert backend log APIs (compaction, heartbeat, schedule) from cursor-based (before+limit) to offset+limit pagination with total_count - Update SQL queries to use OFFSET+LIMIT and add COUNT queries - Add shared parseOffsetLimit helper in handler_helpers.go - Regenerate sqlc, Swagger docs, and TypeScript SDK - Clean up unused i18n keys (loadMore, showMore, history.loadMore) --- apps/web/src/i18n/locales/en.json | 6 +- apps/web/src/i18n/locales/zh.json | 6 +- .../pages/bots/components/bot-compaction.vue | 91 ++++++++++++------- .../pages/bots/components/bot-heartbeat.vue | 91 ++++++++++++------- .../pages/providers/components/model-list.vue | 66 ++++++++++---- db/queries/compaction_logs.sql | 6 +- db/queries/heartbeat_logs.sql | 6 +- db/queries/schedule_logs.sql | 12 ++- internal/command/heartbeat_cmd.go | 2 +- internal/compaction/service.go | 30 +++--- internal/compaction/types.go | 3 +- internal/db/sqlc/compaction_logs.sql.go | 22 +++-- internal/db/sqlc/heartbeat_logs.sql.go | 22 +++-- internal/db/sqlc/schedule_logs.sql.go | 44 ++++++--- internal/handlers/compaction.go | 24 +---- internal/handlers/handler_helpers.go | 18 ++++ internal/handlers/heartbeat.go | 24 +---- internal/handlers/schedule.go | 34 ++----- internal/heartbeat/service.go | 25 +++-- internal/heartbeat/types.go | 3 +- internal/schedule/service.go | 48 ++++++---- internal/schedule/types.go | 3 +- packages/sdk/src/types.gen.ts | 35 +++---- spec/docs.go | 61 ++++++++----- spec/swagger.json | 61 ++++++++----- spec/swagger.yaml | 42 +++++---- 26 files changed, 469 insertions(+), 316 deletions(-) diff --git a/apps/web/src/i18n/locales/en.json b/apps/web/src/i18n/locales/en.json index f34cea54..6e760b47 100644 --- a/apps/web/src/i18n/locales/en.json +++ b/apps/web/src/i18n/locales/en.json @@ -218,8 +218,7 @@ "importSuccess": "Successfully imported {created} models, skipped {skipped}", "importFailed": "Failed to import models", "importConfirmHint": "Models will be imported from this provider's API with default compatibilities (vision, tool-call, reasoning).", - "showingCount": "Showing {count} of {total}", - "showMore": "Show More" + "showingCount": "Showing {count} of {total}" }, "provider": { "add": "Add Provider", @@ -1019,7 +1018,6 @@ "title": "Compaction Logs", "loadFailed": "Failed to load compaction logs", "empty": "No compaction logs", - "loadMore": "Load More", "clearLogs": "Clear Logs", "clearConfirm": "Are you sure you want to clear all compaction logs? This cannot be undone.", "clearSuccess": "Compaction logs cleared", @@ -1037,7 +1035,6 @@ "title": "Heartbeat Logs", "loadFailed": "Failed to load heartbeat logs", "empty": "No heartbeat logs", - "loadMore": "Load More", "clearLogs": "Clear Logs", "clearConfirm": "Are you sure you want to clear all heartbeat logs? This cannot be undone.", "clearSuccess": "Heartbeat logs cleared", @@ -1071,7 +1068,6 @@ "title": "History", "loadFailed": "Failed to load history", "empty": "No history messages", - "loadMore": "Load More", "messageCount": "{count} messages", "selectAll": "Select all", "deleteSelectedConfirm": "Are you sure you want to delete the selected {count} message(s)? This will clear all history and cannot be undone.", diff --git a/apps/web/src/i18n/locales/zh.json b/apps/web/src/i18n/locales/zh.json index f8b931e1..87e8c3c7 100644 --- a/apps/web/src/i18n/locales/zh.json +++ b/apps/web/src/i18n/locales/zh.json @@ -214,8 +214,7 @@ "importSuccess": "成功导入 {created} 个模型,跳过 {skipped} 个", "importFailed": "导入模型失败", "importConfirmHint": "将从该服务商的 API 导入模型,默认兼容性为视觉、工具调用和推理。", - "showingCount": "显示 {count} / {total}", - "showMore": "加载更多" + "showingCount": "显示 {count} / {total}" }, "provider": { "add": "添加服务商", @@ -1015,7 +1014,6 @@ "title": "压缩记录", "loadFailed": "加载压缩记录失败", "empty": "暂无压缩记录", - "loadMore": "加载更多", "clearLogs": "清空记录", "clearConfirm": "确定要清空所有压缩记录吗?此操作不可撤销。", "clearSuccess": "压缩记录已清空", @@ -1033,7 +1031,6 @@ "title": "心跳日志", "loadFailed": "加载心跳日志失败", "empty": "暂无心跳日志", - "loadMore": "加载更多", "clearLogs": "清空日志", "clearConfirm": "确定要清空所有心跳日志吗?此操作不可撤销。", "clearSuccess": "心跳日志已清空", @@ -1067,7 +1064,6 @@ "title": "对话历史", "loadFailed": "加载历史消息失败", "empty": "暂无历史消息", - "loadMore": "加载更多", "messageCount": "{count} 条消息", "selectAll": "全选", "deleteSelectedConfirm": "确定要删除选中的 {count} 条消息吗?此操作将清空所有历史记录,且无法撤销。", diff --git a/apps/web/src/pages/bots/components/bot-compaction.vue b/apps/web/src/pages/bots/components/bot-compaction.vue index 0e8227d3..21e05ea2 100644 --- a/apps/web/src/pages/bots/components/bot-compaction.vue +++ b/apps/web/src/pages/bots/components/bot-compaction.vue @@ -207,23 +207,42 @@

- +
- + + + + + + + +
@@ -236,6 +255,9 @@ import { useI18n } from 'vue-i18n' import { toast } from 'vue-sonner' import { Button, Badge, Spinner, NativeSelect, Label, Switch, Input, Separator, + Pagination, PaginationContent, PaginationEllipsis, + PaginationFirst, PaginationItem, PaginationLast, + PaginationNext, PaginationPrevious, } from '@memohai/ui' import ConfirmPopover from '@/components/confirm-popover/index.vue' import ModelSelect from './model-select.vue' @@ -335,17 +357,32 @@ async function handleSaveSettings() { const isLoading = ref(false) const isClearing = ref(false) const logs = ref([]) +const totalCount = ref(0) const statusFilter = ref('') const expandedIds = ref(new Set()) -const hasMore = ref(false) +const currentPage = ref(1) -const PAGE_SIZE = 50 +const PAGE_SIZE = 20 const filteredLogs = computed(() => { if (!statusFilter.value) return logs.value return logs.value.filter(l => l.status === statusFilter.value) }) +const totalPages = computed(() => Math.ceil(totalCount.value / PAGE_SIZE)) + +const paginationSummary = computed(() => { + const total = totalCount.value + if (total === 0) return '' + const start = (currentPage.value - 1) * PAGE_SIZE + 1 + const end = Math.min(currentPage.value * PAGE_SIZE, total) + return `${start}-${end} / ${total}` +}) + +watch(currentPage, () => { + fetchLogs() +}) + function statusVariant(status: string | undefined) { if (status === 'ok') return 'secondary' as const if (status === 'pending') return 'default' as const @@ -374,22 +411,18 @@ function toggleExpand(id: string | undefined) { } } -async function fetchLogs(before?: string) { +async function fetchLogs() { if (!props.botId) return isLoading.value = true try { + const offset = (currentPage.value - 1) * PAGE_SIZE const { data } = await getBotsByBotIdCompactionLogs({ path: { bot_id: props.botId }, - query: { limit: PAGE_SIZE, ...(before ? { before } : {}) }, + query: { limit: PAGE_SIZE, offset }, throwOnError: true, }) - const items = data?.items ?? [] - if (!before) { - logs.value = items - } else { - logs.value.push(...items) - } - hasMore.value = items.length >= PAGE_SIZE + logs.value = data?.items ?? [] + totalCount.value = data?.total_count ?? 0 } catch (error) { toast.error(resolveApiErrorMessage(error, t('bots.compaction.loadFailed'))) } finally { @@ -397,14 +430,9 @@ async function fetchLogs(before?: string) { } } -async function loadMore() { - if (logs.value.length === 0) return - const lastLog = logs.value[logs.value.length - 1] - await fetchLogs(lastLog?.started_at) -} - async function handleRefresh() { expandedIds.value.clear() + currentPage.value = 1 await fetchLogs() } @@ -416,6 +444,7 @@ async function handleClear() { throwOnError: true, }) logs.value = [] + totalCount.value = 0 expandedIds.value.clear() toast.success(t('bots.compaction.clearSuccess')) } catch (error) { diff --git a/apps/web/src/pages/bots/components/bot-heartbeat.vue b/apps/web/src/pages/bots/components/bot-heartbeat.vue index d747e067..fa1d7fee 100644 --- a/apps/web/src/pages/bots/components/bot-heartbeat.vue +++ b/apps/web/src/pages/bots/components/bot-heartbeat.vue @@ -211,23 +211,42 @@

- +
- + + + + + + + +
@@ -240,6 +259,9 @@ import { useI18n } from 'vue-i18n' import { toast } from 'vue-sonner' import { Button, Badge, Spinner, NativeSelect, Label, Switch, Input, Separator, + Pagination, PaginationContent, PaginationEllipsis, + PaginationFirst, PaginationItem, PaginationLast, + PaginationNext, PaginationPrevious, } from '@memohai/ui' import ConfirmPopover from '@/components/confirm-popover/index.vue' import ModelSelect from './model-select.vue' @@ -338,17 +360,32 @@ async function handleSaveSettings() { const isLoading = ref(false) const isClearing = ref(false) const logs = ref([]) +const totalCount = ref(0) const statusFilter = ref('') const expandedIds = ref(new Set()) -const hasMore = ref(false) +const currentPage = ref(1) -const PAGE_SIZE = 50 +const PAGE_SIZE = 20 const filteredLogs = computed(() => { if (!statusFilter.value) return logs.value return logs.value.filter(l => l.status === statusFilter.value) }) +const totalPages = computed(() => Math.ceil(totalCount.value / PAGE_SIZE)) + +const paginationSummary = computed(() => { + const total = totalCount.value + if (total === 0) return '' + const start = (currentPage.value - 1) * PAGE_SIZE + 1 + const end = Math.min(currentPage.value * PAGE_SIZE, total) + return `${start}-${end} / ${total}` +}) + +watch(currentPage, () => { + fetchLogs() +}) + function statusVariant(status: string | undefined) { if (status === 'ok') return 'secondary' as const if (status === 'alert') return 'default' as const @@ -383,22 +420,18 @@ function toggleExpand(id: string | undefined) { } } -async function fetchLogs(before?: string) { +async function fetchLogs() { if (!props.botId) return isLoading.value = true try { + const offset = (currentPage.value - 1) * PAGE_SIZE const { data } = await getBotsByBotIdHeartbeatLogs({ path: { bot_id: props.botId }, - query: { limit: PAGE_SIZE, ...(before ? { before } : {}) }, + query: { limit: PAGE_SIZE, offset }, throwOnError: true, }) - const items = data?.items ?? [] - if (!before) { - logs.value = items - } else { - logs.value.push(...items) - } - hasMore.value = items.length >= PAGE_SIZE + logs.value = data?.items ?? [] + totalCount.value = data?.total_count ?? 0 } catch (error) { toast.error(resolveApiErrorMessage(error, t('bots.heartbeat.loadFailed'))) } finally { @@ -406,14 +439,9 @@ async function fetchLogs(before?: string) { } } -async function loadMore() { - if (logs.value.length === 0) return - const lastLog = logs.value[logs.value.length - 1] - await fetchLogs(lastLog?.started_at) -} - async function handleRefresh() { expandedIds.value.clear() + currentPage.value = 1 await fetchLogs() } @@ -425,6 +453,7 @@ async function handleClear() { throwOnError: true, }) logs.value = [] + totalCount.value = 0 expandedIds.value.clear() toast.success(t('bots.heartbeat.clearSuccess')) } catch (error) { diff --git a/apps/web/src/pages/providers/components/model-list.vue b/apps/web/src/pages/providers/components/model-list.vue index 0f265dc6..9194b691 100644 --- a/apps/web/src/pages/providers/components/model-list.vue +++ b/apps/web/src/pages/providers/components/model-list.vue @@ -41,20 +41,40 @@
- {{ $t('models.showingCount', { count: displayLimit, total: filteredModels.length }) }} + {{ $t('models.showingCount', { count: `${pageStart}-${pageEnd}`, total: filteredModels.length }) }} - + + + + + + + +
import { ref, computed, watch } from 'vue' import { - Button, Empty, EmptyContent, EmptyDescription, @@ -99,6 +118,14 @@ import { InputGroup, InputGroupAddon, InputGroupInput, + Pagination, + PaginationContent, + PaginationEllipsis, + PaginationFirst, + PaginationItem, + PaginationLast, + PaginationNext, + PaginationPrevious, } from '@memohai/ui' import { Search, List } from 'lucide-vue-next' import CreateModel from '@/components/create-model/index.vue' @@ -120,7 +147,7 @@ defineEmits<{ }>() const searchQuery = ref('') -const displayLimit = ref(PAGE_SIZE) +const currentPage = ref(1) const filteredModels = computed(() => { if (!props.models) return [] @@ -133,14 +160,15 @@ const filteredModels = computed(() => { }) }) -const displayedModels = computed(() => filteredModels.value.slice(0, displayLimit.value)) -const hasMore = computed(() => displayLimit.value < filteredModels.value.length) - -function showMore() { - displayLimit.value += PAGE_SIZE -} +const totalPages = computed(() => Math.ceil(filteredModels.value.length / PAGE_SIZE)) +const pageStart = computed(() => (currentPage.value - 1) * PAGE_SIZE + 1) +const pageEnd = computed(() => Math.min(currentPage.value * PAGE_SIZE, filteredModels.value.length)) +const displayedModels = computed(() => { + const start = (currentPage.value - 1) * PAGE_SIZE + return filteredModels.value.slice(start, start + PAGE_SIZE) +}) watch(searchQuery, () => { - displayLimit.value = PAGE_SIZE + currentPage.value = 1 }) diff --git a/db/queries/compaction_logs.sql b/db/queries/compaction_logs.sql index bae4c8b6..e73661fc 100644 --- a/db/queries/compaction_logs.sql +++ b/db/queries/compaction_logs.sql @@ -24,9 +24,11 @@ WHERE id = $1; SELECT id, bot_id, session_id, status, summary, message_count, error_message, usage, model_id, started_at, completed_at FROM bot_history_message_compacts WHERE bot_id = $1 - AND ($2::timestamptz IS NULL OR started_at < $2) ORDER BY started_at DESC -LIMIT $3; +LIMIT $2 OFFSET $3; + +-- name: CountCompactionLogsByBot :one +SELECT count(*) FROM bot_history_message_compacts WHERE bot_id = $1; -- name: ListCompactionLogsBySession :many SELECT id, bot_id, session_id, status, summary, message_count, error_message, usage, model_id, started_at, completed_at diff --git a/db/queries/heartbeat_logs.sql b/db/queries/heartbeat_logs.sql index e57da7c5..abc75243 100644 --- a/db/queries/heartbeat_logs.sql +++ b/db/queries/heartbeat_logs.sql @@ -18,9 +18,11 @@ RETURNING id, bot_id, session_id, status, result_text, error_message, usage, mod SELECT id, bot_id, session_id, status, result_text, error_message, usage, started_at, completed_at FROM bot_heartbeat_logs WHERE bot_id = $1 - AND ($2::timestamptz IS NULL OR started_at < $2::timestamptz) ORDER BY started_at DESC -LIMIT $3; +LIMIT $2 OFFSET $3; + +-- name: CountHeartbeatLogsByBot :one +SELECT count(*) FROM bot_heartbeat_logs WHERE bot_id = $1; -- name: DeleteHeartbeatLogsByBot :exec DELETE FROM bot_heartbeat_logs WHERE bot_id = $1; diff --git a/db/queries/schedule_logs.sql b/db/queries/schedule_logs.sql index 1fde9cb3..3ae0c7da 100644 --- a/db/queries/schedule_logs.sql +++ b/db/queries/schedule_logs.sql @@ -18,17 +18,21 @@ RETURNING id, schedule_id, bot_id, session_id, status, result_text, error_messag SELECT id, schedule_id, bot_id, session_id, status, result_text, error_message, usage, started_at, completed_at FROM schedule_logs WHERE bot_id = $1 - AND ($2::timestamptz IS NULL OR started_at < $2::timestamptz) ORDER BY started_at DESC -LIMIT $3; +LIMIT $2 OFFSET $3; + +-- name: CountScheduleLogsByBot :one +SELECT count(*) FROM schedule_logs WHERE bot_id = $1; -- name: ListScheduleLogsBySchedule :many SELECT id, schedule_id, bot_id, session_id, status, result_text, error_message, usage, started_at, completed_at FROM schedule_logs WHERE schedule_id = $1 - AND ($2::timestamptz IS NULL OR started_at < $2::timestamptz) ORDER BY started_at DESC -LIMIT $3; +LIMIT $2 OFFSET $3; + +-- name: CountScheduleLogsBySchedule :one +SELECT count(*) FROM schedule_logs WHERE schedule_id = $1; -- name: DeleteScheduleLogsByBot :exec DELETE FROM schedule_logs WHERE bot_id = $1; diff --git a/internal/command/heartbeat_cmd.go b/internal/command/heartbeat_cmd.go index 5628a6a2..e57cf7b7 100644 --- a/internal/command/heartbeat_cmd.go +++ b/internal/command/heartbeat_cmd.go @@ -11,7 +11,7 @@ func (h *Handler) buildHeartbeatGroup() *CommandGroup { Name: "logs", Usage: "logs - List recent heartbeat logs", Handler: func(cc CommandContext) (string, error) { - items, err := h.heartbeatService.ListLogs(cc.Ctx, cc.BotID, nil, 10) + items, _, err := h.heartbeatService.ListLogs(cc.Ctx, cc.BotID, 10, 0) if err != nil { return "", err } diff --git a/internal/compaction/service.go b/internal/compaction/service.go index 61aabe82..132d8661 100644 --- a/internal/compaction/service.go +++ b/internal/compaction/service.go @@ -5,7 +5,6 @@ import ( "encoding/json" "log/slog" "strings" - "time" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgtype" @@ -151,35 +150,38 @@ func (s *Service) completeLog(ctx context.Context, logID pgtype.UUID, status, su } // ListLogs returns paginated compaction logs for a bot. -func (s *Service) ListLogs(ctx context.Context, botID string, before *time.Time, limit int) ([]Log, error) { +func (s *Service) ListLogs(ctx context.Context, botID string, limit, offset int) ([]Log, int64, error) { botUUID, err := db.ParseUUID(botID) if err != nil { - return nil, err + return nil, 0, err } - var beforeTS pgtype.Timestamptz - if before != nil { - beforeTS = pgtype.Timestamptz{Time: *before, Valid: true} + if limit <= 0 || limit > 100 { + limit = 50 + } + if offset < 0 { + offset = 0 } - clampedLimit := limit - if clampedLimit > 1000 { - clampedLimit = 1000 + total, err := s.queries.CountCompactionLogsByBot(ctx, botUUID) + if err != nil { + return nil, 0, err } + rows, err := s.queries.ListCompactionLogsByBot(ctx, sqlc.ListCompactionLogsByBotParams{ - BotID: botUUID, - Column2: beforeTS, - Limit: int32(clampedLimit), //nolint:gosec // clamped above + BotID: botUUID, + Limit: int32(limit), //nolint:gosec // clamped above + Offset: int32(offset), //nolint:gosec // validated above }) if err != nil { - return nil, err + return nil, 0, err } logs := make([]Log, len(rows)) for i, r := range rows { logs[i] = toLog(r) } - return logs, nil + return logs, total, nil } // DeleteLogs deletes all compaction logs for a bot. diff --git a/internal/compaction/types.go b/internal/compaction/types.go index 8a352b37..b1edb31b 100644 --- a/internal/compaction/types.go +++ b/internal/compaction/types.go @@ -22,7 +22,8 @@ type Log struct { // ListLogsResponse is the API response for listing compaction logs. type ListLogsResponse struct { - Items []Log `json:"items"` + Items []Log `json:"items"` + TotalCount int64 `json:"total_count"` } // TriggerConfig holds the parameters needed to trigger a compaction. diff --git a/internal/db/sqlc/compaction_logs.sql.go b/internal/db/sqlc/compaction_logs.sql.go index ebe311cb..db262287 100644 --- a/internal/db/sqlc/compaction_logs.sql.go +++ b/internal/db/sqlc/compaction_logs.sql.go @@ -61,6 +61,17 @@ func (q *Queries) CompleteCompactionLog(ctx context.Context, arg CompleteCompact return i, err } +const countCompactionLogsByBot = `-- name: CountCompactionLogsByBot :one +SELECT count(*) FROM bot_history_message_compacts WHERE bot_id = $1 +` + +func (q *Queries) CountCompactionLogsByBot(ctx context.Context, botID pgtype.UUID) (int64, error) { + row := q.db.QueryRow(ctx, countCompactionLogsByBot, botID) + var count int64 + err := row.Scan(&count) + return count, err +} + const createCompactionLog = `-- name: CreateCompactionLog :one INSERT INTO bot_history_message_compacts (bot_id, session_id) VALUES ($1, $2) @@ -129,19 +140,18 @@ const listCompactionLogsByBot = `-- name: ListCompactionLogsByBot :many SELECT id, bot_id, session_id, status, summary, message_count, error_message, usage, model_id, started_at, completed_at FROM bot_history_message_compacts WHERE bot_id = $1 - AND ($2::timestamptz IS NULL OR started_at < $2) ORDER BY started_at DESC -LIMIT $3 +LIMIT $2 OFFSET $3 ` type ListCompactionLogsByBotParams struct { - BotID pgtype.UUID `json:"bot_id"` - Column2 pgtype.Timestamptz `json:"column_2"` - Limit int32 `json:"limit"` + BotID pgtype.UUID `json:"bot_id"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` } func (q *Queries) ListCompactionLogsByBot(ctx context.Context, arg ListCompactionLogsByBotParams) ([]BotHistoryMessageCompact, error) { - rows, err := q.db.Query(ctx, listCompactionLogsByBot, arg.BotID, arg.Column2, arg.Limit) + rows, err := q.db.Query(ctx, listCompactionLogsByBot, arg.BotID, arg.Limit, arg.Offset) if err != nil { return nil, err } diff --git a/internal/db/sqlc/heartbeat_logs.sql.go b/internal/db/sqlc/heartbeat_logs.sql.go index 95c1cc18..6efc09d6 100644 --- a/internal/db/sqlc/heartbeat_logs.sql.go +++ b/internal/db/sqlc/heartbeat_logs.sql.go @@ -57,6 +57,17 @@ func (q *Queries) CompleteHeartbeatLog(ctx context.Context, arg CompleteHeartbea return i, err } +const countHeartbeatLogsByBot = `-- name: CountHeartbeatLogsByBot :one +SELECT count(*) FROM bot_heartbeat_logs WHERE bot_id = $1 +` + +func (q *Queries) CountHeartbeatLogsByBot(ctx context.Context, botID pgtype.UUID) (int64, error) { + row := q.db.QueryRow(ctx, countHeartbeatLogsByBot, botID) + var count int64 + err := row.Scan(&count) + return count, err +} + const createHeartbeatLog = `-- name: CreateHeartbeatLog :one INSERT INTO bot_heartbeat_logs (bot_id, session_id, started_at) VALUES ($1, $2::uuid, now()) @@ -110,15 +121,14 @@ const listHeartbeatLogsByBot = `-- name: ListHeartbeatLogsByBot :many SELECT id, bot_id, session_id, status, result_text, error_message, usage, started_at, completed_at FROM bot_heartbeat_logs WHERE bot_id = $1 - AND ($2::timestamptz IS NULL OR started_at < $2::timestamptz) ORDER BY started_at DESC -LIMIT $3 +LIMIT $2 OFFSET $3 ` type ListHeartbeatLogsByBotParams struct { - BotID pgtype.UUID `json:"bot_id"` - Column2 pgtype.Timestamptz `json:"column_2"` - Limit int32 `json:"limit"` + BotID pgtype.UUID `json:"bot_id"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` } type ListHeartbeatLogsByBotRow struct { @@ -134,7 +144,7 @@ type ListHeartbeatLogsByBotRow struct { } func (q *Queries) ListHeartbeatLogsByBot(ctx context.Context, arg ListHeartbeatLogsByBotParams) ([]ListHeartbeatLogsByBotRow, error) { - rows, err := q.db.Query(ctx, listHeartbeatLogsByBot, arg.BotID, arg.Column2, arg.Limit) + rows, err := q.db.Query(ctx, listHeartbeatLogsByBot, arg.BotID, arg.Limit, arg.Offset) if err != nil { return nil, err } diff --git a/internal/db/sqlc/schedule_logs.sql.go b/internal/db/sqlc/schedule_logs.sql.go index 540a3eff..1717fc9d 100644 --- a/internal/db/sqlc/schedule_logs.sql.go +++ b/internal/db/sqlc/schedule_logs.sql.go @@ -58,6 +58,28 @@ func (q *Queries) CompleteScheduleLog(ctx context.Context, arg CompleteScheduleL return i, err } +const countScheduleLogsByBot = `-- name: CountScheduleLogsByBot :one +SELECT count(*) FROM schedule_logs WHERE bot_id = $1 +` + +func (q *Queries) CountScheduleLogsByBot(ctx context.Context, botID pgtype.UUID) (int64, error) { + row := q.db.QueryRow(ctx, countScheduleLogsByBot, botID) + var count int64 + err := row.Scan(&count) + return count, err +} + +const countScheduleLogsBySchedule = `-- name: CountScheduleLogsBySchedule :one +SELECT count(*) FROM schedule_logs WHERE schedule_id = $1 +` + +func (q *Queries) CountScheduleLogsBySchedule(ctx context.Context, scheduleID pgtype.UUID) (int64, error) { + row := q.db.QueryRow(ctx, countScheduleLogsBySchedule, scheduleID) + var count int64 + err := row.Scan(&count) + return count, err +} + const createScheduleLog = `-- name: CreateScheduleLog :one INSERT INTO schedule_logs (schedule_id, bot_id, session_id, started_at) VALUES ($1, $2, $3::uuid, now()) @@ -123,15 +145,14 @@ const listScheduleLogsByBot = `-- name: ListScheduleLogsByBot :many SELECT id, schedule_id, bot_id, session_id, status, result_text, error_message, usage, started_at, completed_at FROM schedule_logs WHERE bot_id = $1 - AND ($2::timestamptz IS NULL OR started_at < $2::timestamptz) ORDER BY started_at DESC -LIMIT $3 +LIMIT $2 OFFSET $3 ` type ListScheduleLogsByBotParams struct { - BotID pgtype.UUID `json:"bot_id"` - Column2 pgtype.Timestamptz `json:"column_2"` - Limit int32 `json:"limit"` + BotID pgtype.UUID `json:"bot_id"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` } type ListScheduleLogsByBotRow struct { @@ -148,7 +169,7 @@ type ListScheduleLogsByBotRow struct { } func (q *Queries) ListScheduleLogsByBot(ctx context.Context, arg ListScheduleLogsByBotParams) ([]ListScheduleLogsByBotRow, error) { - rows, err := q.db.Query(ctx, listScheduleLogsByBot, arg.BotID, arg.Column2, arg.Limit) + rows, err := q.db.Query(ctx, listScheduleLogsByBot, arg.BotID, arg.Limit, arg.Offset) if err != nil { return nil, err } @@ -182,15 +203,14 @@ const listScheduleLogsBySchedule = `-- name: ListScheduleLogsBySchedule :many SELECT id, schedule_id, bot_id, session_id, status, result_text, error_message, usage, started_at, completed_at FROM schedule_logs WHERE schedule_id = $1 - AND ($2::timestamptz IS NULL OR started_at < $2::timestamptz) ORDER BY started_at DESC -LIMIT $3 +LIMIT $2 OFFSET $3 ` type ListScheduleLogsByScheduleParams struct { - ScheduleID pgtype.UUID `json:"schedule_id"` - Column2 pgtype.Timestamptz `json:"column_2"` - Limit int32 `json:"limit"` + ScheduleID pgtype.UUID `json:"schedule_id"` + Limit int32 `json:"limit"` + Offset int32 `json:"offset"` } type ListScheduleLogsByScheduleRow struct { @@ -207,7 +227,7 @@ type ListScheduleLogsByScheduleRow struct { } func (q *Queries) ListScheduleLogsBySchedule(ctx context.Context, arg ListScheduleLogsByScheduleParams) ([]ListScheduleLogsByScheduleRow, error) { - rows, err := q.db.Query(ctx, listScheduleLogsBySchedule, arg.ScheduleID, arg.Column2, arg.Limit) + rows, err := q.db.Query(ctx, listScheduleLogsBySchedule, arg.ScheduleID, arg.Limit, arg.Offset) if err != nil { return nil, err } diff --git a/internal/handlers/compaction.go b/internal/handlers/compaction.go index 77a7787a..0cdc892e 100644 --- a/internal/handlers/compaction.go +++ b/internal/handlers/compaction.go @@ -4,9 +4,7 @@ import ( "context" "log/slog" "net/http" - "strconv" "strings" - "time" "github.com/labstack/echo/v4" @@ -42,8 +40,8 @@ func (h *CompactionHandler) Register(e *echo.Echo) { // @Description List compaction logs for a bot // @Tags compaction // @Param bot_id path string true "Bot ID" -// @Param before query string false "Before timestamp (RFC3339)" // @Param limit query int false "Limit" default(50) +// @Param offset query int false "Offset" default(0) // @Success 200 {object} compaction.ListLogsResponse // @Failure 400 {object} ErrorResponse // @Failure 500 {object} ErrorResponse @@ -61,26 +59,12 @@ func (h *CompactionHandler) ListLogs(c echo.Context) error { return err } - var before *time.Time - if raw := strings.TrimSpace(c.QueryParam("before")); raw != "" { - t, err := time.Parse(time.RFC3339Nano, raw) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "invalid before timestamp") - } - before = &t - } - limit := 50 - if raw := strings.TrimSpace(c.QueryParam("limit")); raw != "" { - if v, err := strconv.Atoi(raw); err == nil && v > 0 { - limit = v - } - } - - items, err := h.service.ListLogs(c.Request().Context(), botID, before, limit) + limit, offset := parseOffsetLimit(c) + items, total, err := h.service.ListLogs(c.Request().Context(), botID, limit, offset) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - return c.JSON(http.StatusOK, compaction.ListLogsResponse{Items: items}) + return c.JSON(http.StatusOK, compaction.ListLogsResponse{Items: items, TotalCount: total}) } // DeleteLogs godoc diff --git a/internal/handlers/handler_helpers.go b/internal/handlers/handler_helpers.go index 06df3c77..040ab26e 100644 --- a/internal/handlers/handler_helpers.go +++ b/internal/handlers/handler_helpers.go @@ -4,6 +4,8 @@ import ( "context" "errors" "net/http" + "strconv" + "strings" "github.com/labstack/echo/v4" @@ -46,3 +48,19 @@ func AuthorizeBotAccess(ctx context.Context, botService *bots.Service, accountSe } return bot, nil } + +// parseOffsetLimit extracts limit and offset query parameters with defaults. +func parseOffsetLimit(c echo.Context) (limit, offset int) { + limit = 50 + if raw := strings.TrimSpace(c.QueryParam("limit")); raw != "" { + if v, err := strconv.Atoi(raw); err == nil && v > 0 { + limit = v + } + } + if raw := strings.TrimSpace(c.QueryParam("offset")); raw != "" { + if v, err := strconv.Atoi(raw); err == nil && v >= 0 { + offset = v + } + } + return limit, offset +} diff --git a/internal/handlers/heartbeat.go b/internal/handlers/heartbeat.go index 6b09ed9d..322ba4f9 100644 --- a/internal/handlers/heartbeat.go +++ b/internal/handlers/heartbeat.go @@ -4,9 +4,7 @@ import ( "context" "log/slog" "net/http" - "strconv" "strings" - "time" "github.com/labstack/echo/v4" @@ -42,8 +40,8 @@ func (h *HeartbeatHandler) Register(e *echo.Echo) { // @Description List heartbeat execution logs for a bot // @Tags heartbeat // @Param bot_id path string true "Bot ID" -// @Param before query string false "Before timestamp (RFC3339)" // @Param limit query int false "Limit" default(50) +// @Param offset query int false "Offset" default(0) // @Success 200 {object} heartbeat.ListLogsResponse // @Failure 400 {object} ErrorResponse // @Failure 500 {object} ErrorResponse @@ -61,26 +59,12 @@ func (h *HeartbeatHandler) ListLogs(c echo.Context) error { return err } - var before *time.Time - if raw := strings.TrimSpace(c.QueryParam("before")); raw != "" { - t, err := time.Parse(time.RFC3339Nano, raw) - if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "invalid before timestamp") - } - before = &t - } - limit := 50 - if raw := strings.TrimSpace(c.QueryParam("limit")); raw != "" { - if v, err := strconv.Atoi(raw); err == nil && v > 0 { - limit = v - } - } - - items, err := h.service.ListLogs(c.Request().Context(), botID, before, limit) + limit, offset := parseOffsetLimit(c) + items, total, err := h.service.ListLogs(c.Request().Context(), botID, limit, offset) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - return c.JSON(http.StatusOK, heartbeat.ListLogsResponse{Items: items}) + return c.JSON(http.StatusOK, heartbeat.ListLogsResponse{Items: items, TotalCount: total}) } // DeleteLogs godoc diff --git a/internal/handlers/schedule.go b/internal/handlers/schedule.go index dfe368f6..26ce97b2 100644 --- a/internal/handlers/schedule.go +++ b/internal/handlers/schedule.go @@ -4,9 +4,7 @@ import ( "context" "log/slog" "net/http" - "strconv" "strings" - "time" "github.com/labstack/echo/v4" @@ -225,8 +223,8 @@ func (h *ScheduleHandler) Delete(c echo.Context) error { // @Description List schedule execution logs for a bot // @Tags schedule // @Param bot_id path string true "Bot ID" -// @Param before query string false "Before timestamp (RFC3339)" // @Param limit query int false "Limit" default(50) +// @Param offset query int false "Offset" default(0) // @Success 200 {object} schedule.ListLogsResponse // @Failure 400 {object} ErrorResponse // @Failure 500 {object} ErrorResponse @@ -244,12 +242,12 @@ func (h *ScheduleHandler) ListLogs(c echo.Context) error { return err } - before, limit := parseBeforeLimit(c) - items, err := h.service.ListLogs(c.Request().Context(), botID, before, limit) + limit, offset := parseOffsetLimit(c) + items, total, err := h.service.ListLogs(c.Request().Context(), botID, limit, offset) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - return c.JSON(http.StatusOK, schedule.ListLogsResponse{Items: items}) + return c.JSON(http.StatusOK, schedule.ListLogsResponse{Items: items, TotalCount: total}) } // ListLogsBySchedule godoc @@ -258,8 +256,8 @@ func (h *ScheduleHandler) ListLogs(c echo.Context) error { // @Tags schedule // @Param bot_id path string true "Bot ID" // @Param id path string true "Schedule ID" -// @Param before query string false "Before timestamp (RFC3339)" // @Param limit query int false "Limit" default(50) +// @Param offset query int false "Offset" default(0) // @Success 200 {object} schedule.ListLogsResponse // @Failure 400 {object} ErrorResponse // @Failure 500 {object} ErrorResponse @@ -281,12 +279,12 @@ func (h *ScheduleHandler) ListLogsBySchedule(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, "schedule id is required") } - before, limit := parseBeforeLimit(c) - items, err := h.service.ListLogsBySchedule(c.Request().Context(), scheduleID, before, limit) + limit, offset := parseOffsetLimit(c) + items, total, err := h.service.ListLogsBySchedule(c.Request().Context(), scheduleID, limit, offset) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) } - return c.JSON(http.StatusOK, schedule.ListLogsResponse{Items: items}) + return c.JSON(http.StatusOK, schedule.ListLogsResponse{Items: items, TotalCount: total}) } // DeleteLogs godoc @@ -316,22 +314,6 @@ func (h *ScheduleHandler) DeleteLogs(c echo.Context) error { return c.NoContent(http.StatusNoContent) } -func parseBeforeLimit(c echo.Context) (*time.Time, int) { - var before *time.Time - if raw := strings.TrimSpace(c.QueryParam("before")); raw != "" { - if t, err := time.Parse(time.RFC3339Nano, raw); err == nil { - before = &t - } - } - limit := 50 - if raw := strings.TrimSpace(c.QueryParam("limit")); raw != "" { - if v, err := strconv.Atoi(raw); err == nil && v > 0 { - limit = v - } - } - return before, limit -} - func (*ScheduleHandler) requireUserID(c echo.Context) (string, error) { return RequireChannelIdentityID(c) } diff --git a/internal/heartbeat/service.go b/internal/heartbeat/service.go index e954d734..d19ae56f 100644 --- a/internal/heartbeat/service.go +++ b/internal/heartbeat/service.go @@ -182,31 +182,36 @@ func (s *Service) completeLog(ctx context.Context, logID pgtype.UUID, status, re } } -func (s *Service) ListLogs(ctx context.Context, botID string, before *time.Time, limit int) ([]Log, error) { +func (s *Service) ListLogs(ctx context.Context, botID string, limit, offset int) ([]Log, int64, error) { pgBotID, err := db.ParseUUID(botID) if err != nil { - return nil, err + return nil, 0, err } if limit <= 0 || limit > 100 { limit = 50 } - beforeTS := pgtype.Timestamptz{} - if before != nil { - beforeTS = pgtype.Timestamptz{Time: *before, Valid: true} + if offset < 0 { + offset = 0 } + + total, err := s.queries.CountHeartbeatLogsByBot(ctx, pgBotID) + if err != nil { + return nil, 0, err + } + rows, err := s.queries.ListHeartbeatLogsByBot(ctx, sqlc.ListHeartbeatLogsByBotParams{ - BotID: pgBotID, - Column2: beforeTS, - Limit: int32(limit), //nolint:gosec // capped to 100 above + BotID: pgBotID, + Limit: int32(limit), //nolint:gosec // capped to 100 above + Offset: int32(offset), //nolint:gosec // validated above }) if err != nil { - return nil, err + return nil, 0, err } items := make([]Log, 0, len(rows)) for _, row := range rows { items = append(items, toLog(row)) } - return items, nil + return items, total, nil } func (s *Service) DeleteLogs(ctx context.Context, botID string) error { diff --git a/internal/heartbeat/types.go b/internal/heartbeat/types.go index d446b0f6..cae5b9a1 100644 --- a/internal/heartbeat/types.go +++ b/internal/heartbeat/types.go @@ -21,5 +21,6 @@ type Log struct { } type ListLogsResponse struct { - Items []Log `json:"items"` + Items []Log `json:"items"` + TotalCount int64 `json:"total_count"` } diff --git a/internal/schedule/service.go b/internal/schedule/service.go index 2804f157..97441f77 100644 --- a/internal/schedule/service.go +++ b/internal/schedule/service.go @@ -325,58 +325,68 @@ func (s *Service) completeLog(ctx context.Context, logID pgtype.UUID, status, re } } -func (s *Service) ListLogs(ctx context.Context, botID string, before *time.Time, limit int) ([]Log, error) { +func (s *Service) ListLogs(ctx context.Context, botID string, limit, offset int) ([]Log, int64, error) { pgBotID, err := db.ParseUUID(botID) if err != nil { - return nil, err + return nil, 0, err } if limit <= 0 || limit > 100 { limit = 50 } - beforeTS := pgtype.Timestamptz{} - if before != nil { - beforeTS = pgtype.Timestamptz{Time: *before, Valid: true} + if offset < 0 { + offset = 0 } + + total, err := s.queries.CountScheduleLogsByBot(ctx, pgBotID) + if err != nil { + return nil, 0, err + } + rows, err := s.queries.ListScheduleLogsByBot(ctx, sqlc.ListScheduleLogsByBotParams{ - BotID: pgBotID, - Column2: beforeTS, - Limit: int32(limit), //nolint:gosec // capped to 100 above + BotID: pgBotID, + Limit: int32(limit), //nolint:gosec // capped to 100 above + Offset: int32(offset), //nolint:gosec // validated above }) if err != nil { - return nil, err + return nil, 0, err } items := make([]Log, 0, len(rows)) for _, row := range rows { items = append(items, toScheduleLog(row)) } - return items, nil + return items, total, nil } -func (s *Service) ListLogsBySchedule(ctx context.Context, scheduleID string, before *time.Time, limit int) ([]Log, error) { +func (s *Service) ListLogsBySchedule(ctx context.Context, scheduleID string, limit, offset int) ([]Log, int64, error) { pgID, err := db.ParseUUID(scheduleID) if err != nil { - return nil, err + return nil, 0, err } if limit <= 0 || limit > 100 { limit = 50 } - beforeTS := pgtype.Timestamptz{} - if before != nil { - beforeTS = pgtype.Timestamptz{Time: *before, Valid: true} + if offset < 0 { + offset = 0 } + + total, err := s.queries.CountScheduleLogsBySchedule(ctx, pgID) + if err != nil { + return nil, 0, err + } + rows, err := s.queries.ListScheduleLogsBySchedule(ctx, sqlc.ListScheduleLogsByScheduleParams{ ScheduleID: pgID, - Column2: beforeTS, - Limit: int32(limit), //nolint:gosec // capped to 100 above + Limit: int32(limit), //nolint:gosec // capped to 100 above + Offset: int32(offset), //nolint:gosec // validated above }) if err != nil { - return nil, err + return nil, 0, err } items := make([]Log, 0, len(rows)) for _, row := range rows { items = append(items, toScheduleLogFromSchedule(row)) } - return items, nil + return items, total, nil } func (s *Service) DeleteLogs(ctx context.Context, botID string) error { diff --git a/internal/schedule/types.go b/internal/schedule/types.go index fcc4785e..e49c65e8 100644 --- a/internal/schedule/types.go +++ b/internal/schedule/types.go @@ -85,5 +85,6 @@ type Log struct { } type ListLogsResponse struct { - Items []Log `json:"items"` + Items []Log `json:"items"` + TotalCount int64 `json:"total_count"` } diff --git a/packages/sdk/src/types.gen.ts b/packages/sdk/src/types.gen.ts index 3b4dec6e..cd188d2d 100644 --- a/packages/sdk/src/types.gen.ts +++ b/packages/sdk/src/types.gen.ts @@ -575,6 +575,7 @@ export type ChannelUpsertConfigRequest = { export type CompactionListLogsResponse = { items?: Array; + total_count?: number; }; export type CompactionLog = { @@ -1053,6 +1054,7 @@ export type HandlersUpdateSessionRequest = { export type HeartbeatListLogsResponse = { items?: Array; + total_count?: number; }; export type HeartbeatLog = { @@ -1320,6 +1322,7 @@ export type ScheduleCreateRequest = { export type ScheduleListLogsResponse = { items?: Array; + total_count?: number; }; export type ScheduleListResponse = { @@ -2267,14 +2270,14 @@ export type GetBotsByBotIdCompactionLogsData = { bot_id: string; }; query?: { - /** - * Before timestamp (RFC3339) - */ - before?: string; /** * Limit */ limit?: number; + /** + * Offset + */ + offset?: number; }; url: '/bots/{bot_id}/compaction/logs'; }; @@ -3538,14 +3541,14 @@ export type GetBotsByBotIdHeartbeatLogsData = { bot_id: string; }; query?: { - /** - * Before timestamp (RFC3339) - */ - before?: string; /** * Limit */ limit?: number; + /** + * Offset + */ + offset?: number; }; url: '/bots/{bot_id}/heartbeat/logs'; }; @@ -4775,14 +4778,14 @@ export type GetBotsByBotIdScheduleLogsData = { bot_id: string; }; query?: { - /** - * Before timestamp (RFC3339) - */ - before?: string; /** * Limit */ limit?: number; + /** + * Offset + */ + offset?: number; }; url: '/bots/{bot_id}/schedule/logs'; }; @@ -4929,14 +4932,14 @@ export type GetBotsByBotIdScheduleByIdLogsData = { id: string; }; query?: { - /** - * Before timestamp (RFC3339) - */ - before?: string; /** * Limit */ limit?: number; + /** + * Offset + */ + offset?: number; }; url: '/bots/{bot_id}/schedule/{id}/logs'; }; diff --git a/spec/docs.go b/spec/docs.go index 3469a39b..ffa334e0 100644 --- a/spec/docs.go +++ b/spec/docs.go @@ -851,18 +851,19 @@ const docTemplate = `{ "in": "path", "required": true }, - { - "type": "string", - "description": "Before timestamp (RFC3339)", - "name": "before", - "in": "query" - }, { "type": "integer", "default": 50, "description": "Limit", "name": "limit", "in": "query" + }, + { + "type": "integer", + "default": 0, + "description": "Offset", + "name": "offset", + "in": "query" } ], "responses": { @@ -2374,18 +2375,19 @@ const docTemplate = `{ "in": "path", "required": true }, - { - "type": "string", - "description": "Before timestamp (RFC3339)", - "name": "before", - "in": "query" - }, { "type": "integer", "default": 50, "description": "Limit", "name": "limit", "in": "query" + }, + { + "type": "integer", + "default": 0, + "description": "Offset", + "name": "offset", + "in": "query" } ], "responses": { @@ -3909,18 +3911,19 @@ const docTemplate = `{ "in": "path", "required": true }, - { - "type": "string", - "description": "Before timestamp (RFC3339)", - "name": "before", - "in": "query" - }, { "type": "integer", "default": 50, "description": "Limit", "name": "limit", "in": "query" + }, + { + "type": "integer", + "default": 0, + "description": "Offset", + "name": "offset", + "in": "query" } ], "responses": { @@ -4122,18 +4125,19 @@ const docTemplate = `{ "in": "path", "required": true }, - { - "type": "string", - "description": "Before timestamp (RFC3339)", - "name": "before", - "in": "query" - }, { "type": "integer", "default": 50, "description": "Limit", "name": "limit", "in": "query" + }, + { + "type": "integer", + "default": 0, + "description": "Offset", + "name": "offset", + "in": "query" } ], "responses": { @@ -10213,6 +10217,9 @@ const docTemplate = `{ "items": { "$ref": "#/definitions/compaction.Log" } + }, + "total_count": { + "type": "integer" } } }, @@ -11386,6 +11393,9 @@ const docTemplate = `{ "items": { "$ref": "#/definitions/heartbeat.Log" } + }, + "total_count": { + "type": "integer" } } }, @@ -12068,6 +12078,9 @@ const docTemplate = `{ "items": { "$ref": "#/definitions/schedule.Log" } + }, + "total_count": { + "type": "integer" } } }, diff --git a/spec/swagger.json b/spec/swagger.json index 7bd8f7b2..eb61d66f 100644 --- a/spec/swagger.json +++ b/spec/swagger.json @@ -842,18 +842,19 @@ "in": "path", "required": true }, - { - "type": "string", - "description": "Before timestamp (RFC3339)", - "name": "before", - "in": "query" - }, { "type": "integer", "default": 50, "description": "Limit", "name": "limit", "in": "query" + }, + { + "type": "integer", + "default": 0, + "description": "Offset", + "name": "offset", + "in": "query" } ], "responses": { @@ -2365,18 +2366,19 @@ "in": "path", "required": true }, - { - "type": "string", - "description": "Before timestamp (RFC3339)", - "name": "before", - "in": "query" - }, { "type": "integer", "default": 50, "description": "Limit", "name": "limit", "in": "query" + }, + { + "type": "integer", + "default": 0, + "description": "Offset", + "name": "offset", + "in": "query" } ], "responses": { @@ -3900,18 +3902,19 @@ "in": "path", "required": true }, - { - "type": "string", - "description": "Before timestamp (RFC3339)", - "name": "before", - "in": "query" - }, { "type": "integer", "default": 50, "description": "Limit", "name": "limit", "in": "query" + }, + { + "type": "integer", + "default": 0, + "description": "Offset", + "name": "offset", + "in": "query" } ], "responses": { @@ -4113,18 +4116,19 @@ "in": "path", "required": true }, - { - "type": "string", - "description": "Before timestamp (RFC3339)", - "name": "before", - "in": "query" - }, { "type": "integer", "default": 50, "description": "Limit", "name": "limit", "in": "query" + }, + { + "type": "integer", + "default": 0, + "description": "Offset", + "name": "offset", + "in": "query" } ], "responses": { @@ -10204,6 +10208,9 @@ "items": { "$ref": "#/definitions/compaction.Log" } + }, + "total_count": { + "type": "integer" } } }, @@ -11377,6 +11384,9 @@ "items": { "$ref": "#/definitions/heartbeat.Log" } + }, + "total_count": { + "type": "integer" } } }, @@ -12059,6 +12069,9 @@ "items": { "$ref": "#/definitions/schedule.Log" } + }, + "total_count": { + "type": "integer" } } }, diff --git a/spec/swagger.yaml b/spec/swagger.yaml index 1b7607fb..aaac48da 100644 --- a/spec/swagger.yaml +++ b/spec/swagger.yaml @@ -957,6 +957,8 @@ definitions: items: $ref: '#/definitions/compaction.Log' type: array + total_count: + type: integer type: object compaction.Log: properties: @@ -1723,6 +1725,8 @@ definitions: items: $ref: '#/definitions/heartbeat.Log' type: array + total_count: + type: integer type: object heartbeat.Log: properties: @@ -2174,6 +2178,8 @@ definitions: items: $ref: '#/definitions/schedule.Log' type: array + total_count: + type: integer type: object schedule.ListResponse: properties: @@ -3173,15 +3179,16 @@ paths: name: bot_id required: true type: string - - description: Before timestamp (RFC3339) - in: query - name: before - type: string - default: 50 description: Limit in: query name: limit type: integer + - default: 0 + description: Offset + in: query + name: offset + type: integer responses: "200": description: OK @@ -4179,15 +4186,16 @@ paths: name: bot_id required: true type: string - - description: Before timestamp (RFC3339) - in: query - name: before - type: string - default: 50 description: Limit in: query name: limit type: integer + - default: 0 + description: Offset + in: query + name: offset + type: integer responses: "200": description: OK @@ -5274,15 +5282,16 @@ paths: name: id required: true type: string - - description: Before timestamp (RFC3339) - in: query - name: before - type: string - default: 50 description: Limit in: query name: limit type: integer + - default: 0 + description: Offset + in: query + name: offset + type: integer responses: "200": description: OK @@ -5330,15 +5339,16 @@ paths: name: bot_id required: true type: string - - description: Before timestamp (RFC3339) - in: query - name: before - type: string - default: 50 description: Limit in: query name: limit type: integer + - default: 0 + description: Offset + in: query + name: offset + type: integer responses: "200": description: OK