From 1a6bf4d7af52339b8a711aeff8bea2f8f67d31b4 Mon Sep 17 00:00:00 2001 From: Sypher845 Date: Tue, 12 May 2026 18:02:44 +0530 Subject: [PATCH 1/6] feat(preheat): preheat execution list command Signed-off-by: Sypher845 --- cmd/harbor/root/project/preheat.go | 1 + cmd/harbor/root/project/preheat/execution.go | 35 ++++++ .../root/project/preheat/execution/list.go | 106 ++++++++++++++++++ pkg/api/preheat_handler.go | 33 ++++++ pkg/views/preheat/execution/list/list.go | 68 +++++++++++ 5 files changed, 243 insertions(+) create mode 100644 cmd/harbor/root/project/preheat/execution.go create mode 100644 cmd/harbor/root/project/preheat/execution/list.go create mode 100644 pkg/views/preheat/execution/list/list.go diff --git a/cmd/harbor/root/project/preheat.go b/cmd/harbor/root/project/preheat.go index f65e1c536..664b58eca 100644 --- a/cmd/harbor/root/project/preheat.go +++ b/cmd/harbor/root/project/preheat.go @@ -28,6 +28,7 @@ func Preheat() *cobra.Command { } cmd.AddCommand( preheat.PolicyCommand(), + preheat.ExecutionCommand(), ) return cmd diff --git a/cmd/harbor/root/project/preheat/execution.go b/cmd/harbor/root/project/preheat/execution.go new file mode 100644 index 000000000..0b4c3314a --- /dev/null +++ b/cmd/harbor/root/project/preheat/execution.go @@ -0,0 +1,35 @@ +// Copyright Project Harbor Authors +// +// 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 preheat + +import ( + "github.com/goharbor/harbor-cli/cmd/harbor/root/project/preheat/execution" + "github.com/spf13/cobra" +) + +func ExecutionCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "execution", + Aliases: []string{"exec"}, + Short: "Manage preheat executions", + Long: "Manage P2P preheat executions under a project", + Example: ` harbor-cli project preheat execution list [NAME|ID] [POLICY_NAME]`, + } + + cmd.AddCommand( + execution.ListExecutionCommand(), + ) + + return cmd +} diff --git a/cmd/harbor/root/project/preheat/execution/list.go b/cmd/harbor/root/project/preheat/execution/list.go new file mode 100644 index 000000000..f5fb827e2 --- /dev/null +++ b/cmd/harbor/root/project/preheat/execution/list.go @@ -0,0 +1,106 @@ +// Copyright Project Harbor Authors +// +// 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 execution + +import ( + "fmt" + + "github.com/goharbor/harbor-cli/pkg/api" + "github.com/goharbor/harbor-cli/pkg/prompt" + "github.com/goharbor/harbor-cli/pkg/utils" + "github.com/goharbor/harbor-cli/pkg/views/preheat/execution/list" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func ListExecutionCommand() *cobra.Command { + var opts api.ListFlags + var isID bool + + cmd := &cobra.Command{ + Use: "list", + Short: "List preheat executions", + Long: "List preheat executions under a project", + Example: ` harbor-cli project preheat execution list [NAME|ID] [POLICY_NAME]`, + Args: cobra.MaximumNArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + var err error + var projectName, policyName string + + if opts.Page < 1 { + return fmt.Errorf("page number must be greater than or equal to 1") + } + if opts.PageSize <= 0 || opts.PageSize > 100 { + return fmt.Errorf("page size must be greater than 0 and less than or equal to 100") + } + + if len(args) >= 1 { + log.Debugf("Project name provided: %s", args[0]) + projectName = args[0] + } else { + log.Debug("No project name provided, prompting user") + projectName, err = prompt.GetProjectNameFromUser() + if err != nil { + return fmt.Errorf("failed to get project name: %v", utils.ParseHarborErrorMsg(err)) + } + } + + if len(args) >= 2 { + log.Debugf("Policy name provided: %s", args[1]) + policyName = args[1] + } else { + log.Debug("No policy name provided, prompting user") + policyName, err = prompt.GetPreheatPolicyNameFromUser(projectName) + if err != nil { + return fmt.Errorf("failed to get policy name: %v", utils.ParseHarborErrorMsg(err)) + } + } + + log.Debug("Fetching preheat policy executions...") + resp, err := api.ListPreheatExecutions(projectName, policyName, isID, opts) + if err != nil { + if utils.ParseHarborErrorCode(err) == "404" { + return fmt.Errorf("no executions found for policy %s in project %s", policyName, projectName) + } + return fmt.Errorf("failed to list preheat executions: %v", utils.ParseHarborErrorMsg(err)) + } + + if len(resp.Payload) == 0 { + fmt.Println("No executions found") + return nil + } + + FormatFlag := viper.GetString("output-format") + if FormatFlag != "" { + err = utils.PrintFormat(resp.Payload, FormatFlag) + if err != nil { + return err + } + } else { + list.ListExecutions(resp.Payload) + } + return nil + }, + } + + flags := cmd.Flags() + flags.BoolVar(&isID, "id", false, "Get preheat executions by project id") + flags.Int64VarP(&opts.Page, "page", "", 1, "Page number") + flags.Int64VarP(&opts.PageSize, "page-size", "", 10, "Size of per page") + flags.StringVarP(&opts.Q, "query", "q", "", "Query string to query resources") + flags.StringVarP(&opts.Sort, "sort", "", "", "Sort the resource list in ascending or descending order") + + return cmd +} diff --git a/pkg/api/preheat_handler.go b/pkg/api/preheat_handler.go index da52a72ce..d571a3ae6 100644 --- a/pkg/api/preheat_handler.go +++ b/pkg/api/preheat_handler.go @@ -152,3 +152,36 @@ func ListProvidersUnderProject(projectName string) ([]*models.ProviderUnderProje } return response.Payload, nil } + +func ListPreheatExecutions(projectName string, policyName string, isID bool, opts ...ListFlags) (*preheat.ListExecutionsOK, error) { + ctx, client, err := utils.ContextWithClient() + if err != nil { + return nil, err + } + + var listFlags ListFlags + if len(opts) > 0 { + listFlags = opts[0] + } + + if isID { + project, err := GetProject(projectName, true) + if err != nil { + return nil, err + } + projectName = project.Payload.Name + } + + response, err := client.Preheat.ListExecutions(ctx, &preheat.ListExecutionsParams{ + ProjectName: projectName, + PreheatPolicyName: policyName, + Page: &listFlags.Page, + PageSize: &listFlags.PageSize, + Q: &listFlags.Q, + Sort: &listFlags.Sort, + }) + if err != nil { + return nil, err + } + return response, nil +} diff --git a/pkg/views/preheat/execution/list/list.go b/pkg/views/preheat/execution/list/list.go new file mode 100644 index 000000000..d6d951842 --- /dev/null +++ b/pkg/views/preheat/execution/list/list.go @@ -0,0 +1,68 @@ +// Copyright Project Harbor Authors +// +// 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 list + +import ( + "fmt" + "os" + "strconv" + + "github.com/charmbracelet/bubbles/table" + tea "github.com/charmbracelet/bubbletea" + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" + "github.com/goharbor/harbor-cli/pkg/utils" + "github.com/goharbor/harbor-cli/pkg/views/base/tablelist" +) + +var columns = []table.Column{ + {Title: "ID", Width: tablelist.WidthS}, + {Title: "Status", Width: tablelist.WidthM}, + {Title: "Trigger", Width: tablelist.WidthM}, + {Title: "Success Rate", Width: tablelist.WidthM}, + {Title: "Start Time", Width: tablelist.WidthL}, + {Title: "End Time", Width: tablelist.WidthL}, + {Title: "Vendor", Width: tablelist.WidthM}, +} + +func ListExecutions(executions []*models.Execution) { + var rows []table.Row + for _, execution := range executions { + startTime, _ := utils.FormatCreatedTime(execution.StartTime) + endTime := "-" + if execution.Status != "Running" { + endTime, _ = utils.FormatCreatedTime(execution.EndTime) + } + + successRate := "-" + if m := execution.Metrics; m != nil && m.TaskCount > 0 { + successRate = fmt.Sprintf("%d%%", m.SuccessTaskCount*100/m.TaskCount) + } + + rows = append(rows, table.Row{ + strconv.FormatInt(execution.ID, 10), + execution.Status, + execution.Trigger, + successRate, + startTime, + endTime, + execution.VendorType, + }) + } + + m := tablelist.NewModel(columns, rows, len(rows)) + if _, err := tea.NewProgram(m).Run(); err != nil { + fmt.Println("Error running program:", err) + os.Exit(1) + } +} From aa6ced104f6a030c4f9fb41c9fca7340b24cc8d5 Mon Sep 17 00:00:00 2001 From: Sypher845 Date: Tue, 12 May 2026 18:04:48 +0530 Subject: [PATCH 2/6] docs(preheat): docs for preheat execution list command Signed-off-by: Sypher845 --- .../root/project/preheat/execution/list.go | 10 ++- .../harbor-project-preheat-execution-list.md | 47 ++++++++++++++ .../harbor-project-preheat-execution.md | 39 ++++++++++++ doc/cli-docs/harbor-project-preheat.md | 1 + .../harbor-project-preheat-execution-list.1 | 61 +++++++++++++++++++ .../man1/harbor-project-preheat-execution.1 | 41 +++++++++++++ doc/man-docs/man1/harbor-project-preheat.1 | 2 +- pkg/api/preheat_handler.go | 10 +-- 8 files changed, 200 insertions(+), 11 deletions(-) create mode 100644 doc/cli-docs/harbor-project-preheat-execution-list.md create mode 100644 doc/cli-docs/harbor-project-preheat-execution.md create mode 100644 doc/man-docs/man1/harbor-project-preheat-execution-list.1 create mode 100644 doc/man-docs/man1/harbor-project-preheat-execution.1 diff --git a/cmd/harbor/root/project/preheat/execution/list.go b/cmd/harbor/root/project/preheat/execution/list.go index f5fb827e2..3426f5c10 100644 --- a/cmd/harbor/root/project/preheat/execution/list.go +++ b/cmd/harbor/root/project/preheat/execution/list.go @@ -57,6 +57,14 @@ func ListExecutionCommand() *cobra.Command { } } + if isID { + project, err := api.GetProject(projectName, true) + if err != nil { + return fmt.Errorf("failed to resolve project ID: %v", utils.ParseHarborErrorMsg(err)) + } + projectName = project.Payload.Name + } + if len(args) >= 2 { log.Debugf("Policy name provided: %s", args[1]) policyName = args[1] @@ -69,7 +77,7 @@ func ListExecutionCommand() *cobra.Command { } log.Debug("Fetching preheat policy executions...") - resp, err := api.ListPreheatExecutions(projectName, policyName, isID, opts) + resp, err := api.ListPreheatExecutions(projectName, policyName, opts) if err != nil { if utils.ParseHarborErrorCode(err) == "404" { return fmt.Errorf("no executions found for policy %s in project %s", policyName, projectName) diff --git a/doc/cli-docs/harbor-project-preheat-execution-list.md b/doc/cli-docs/harbor-project-preheat-execution-list.md new file mode 100644 index 000000000..63ed281f5 --- /dev/null +++ b/doc/cli-docs/harbor-project-preheat-execution-list.md @@ -0,0 +1,47 @@ +--- +title: harbor project preheat execution list +weight: 70 +--- +## harbor project preheat execution list + +### Description + +##### List preheat executions + +### Synopsis + +List preheat executions under a project + +```sh +harbor project preheat execution list [flags] +``` + +### Examples + +```sh + harbor-cli project preheat execution list [NAME|ID] [POLICY_NAME] +``` + +### Options + +```sh + -h, --help help for list + --id Get preheat executions by project id + --page int Page number (default 1) + --page-size int Size of per page (default 10) + -q, --query string Query string to query resources + --sort string Sort the resource list in ascending or descending order +``` + +### Options inherited from parent commands + +```sh + -c, --config string config file (default is $HOME/.config/harbor-cli/config.yaml) + -o, --output-format string Output format. One of: json|yaml|csv + -v, --verbose verbose output +``` + +### SEE ALSO + +* [harbor project preheat execution](harbor-project-preheat-execution.md) - Manage preheat executions + diff --git a/doc/cli-docs/harbor-project-preheat-execution.md b/doc/cli-docs/harbor-project-preheat-execution.md new file mode 100644 index 000000000..27e746417 --- /dev/null +++ b/doc/cli-docs/harbor-project-preheat-execution.md @@ -0,0 +1,39 @@ +--- +title: harbor project preheat execution +weight: 95 +--- +## harbor project preheat execution + +### Description + +##### Manage preheat executions + +### Synopsis + +Manage P2P preheat executions under a project + +### Examples + +```sh + harbor-cli project preheat execution list [NAME|ID] [POLICY_NAME] +``` + +### Options + +```sh + -h, --help help for execution +``` + +### Options inherited from parent commands + +```sh + -c, --config string config file (default is $HOME/.config/harbor-cli/config.yaml) + -o, --output-format string Output format. One of: json|yaml|csv + -v, --verbose verbose output +``` + +### SEE ALSO + +* [harbor project preheat](harbor-project-preheat.md) - Manage project preheat resources +* [harbor project preheat execution list](harbor-project-preheat-execution-list.md) - List preheat executions + diff --git a/doc/cli-docs/harbor-project-preheat.md b/doc/cli-docs/harbor-project-preheat.md index e847c8b53..eef5d1ffc 100644 --- a/doc/cli-docs/harbor-project-preheat.md +++ b/doc/cli-docs/harbor-project-preheat.md @@ -35,5 +35,6 @@ Manage project-scoped P2P preheat policies, executions, and tasks in Harbor ### SEE ALSO * [harbor project](harbor-project.md) - Manage projects and assign resources to them +* [harbor project preheat execution](harbor-project-preheat-execution.md) - Manage preheat executions * [harbor project preheat policy](harbor-project-preheat-policy.md) - Manage preheat policies diff --git a/doc/man-docs/man1/harbor-project-preheat-execution-list.1 b/doc/man-docs/man1/harbor-project-preheat-execution-list.1 new file mode 100644 index 000000000..2023ba4b8 --- /dev/null +++ b/doc/man-docs/man1/harbor-project-preheat-execution-list.1 @@ -0,0 +1,61 @@ +.nh +.TH "HARBOR" "1" "Harbor Community" "Harbor User Manuals" + +.SH NAME +harbor-project-preheat-execution-list - List preheat executions + + +.SH SYNOPSIS +\fBharbor project preheat execution list [flags]\fP + + +.SH DESCRIPTION +List preheat executions under a project + + +.SH OPTIONS +\fB-h\fP, \fB--help\fP[=false] + help for list + +.PP +\fB--id\fP[=false] + Get preheat executions by project id + +.PP +\fB--page\fP=1 + Page number + +.PP +\fB--page-size\fP=10 + Size of per page + +.PP +\fB-q\fP, \fB--query\fP="" + Query string to query resources + +.PP +\fB--sort\fP="" + Sort the resource list in ascending or descending order + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +\fB-c\fP, \fB--config\fP="" + config file (default is $HOME/.config/harbor-cli/config.yaml) + +.PP +\fB-o\fP, \fB--output-format\fP="" + Output format. One of: json|yaml|csv + +.PP +\fB-v\fP, \fB--verbose\fP[=false] + verbose output + + +.SH EXAMPLE +.EX + harbor-cli project preheat execution list [NAME|ID] [POLICY_NAME] +.EE + + +.SH SEE ALSO +\fBharbor-project-preheat-execution(1)\fP \ No newline at end of file diff --git a/doc/man-docs/man1/harbor-project-preheat-execution.1 b/doc/man-docs/man1/harbor-project-preheat-execution.1 new file mode 100644 index 000000000..353e61d64 --- /dev/null +++ b/doc/man-docs/man1/harbor-project-preheat-execution.1 @@ -0,0 +1,41 @@ +.nh +.TH "HARBOR" "1" "Harbor Community" "Harbor User Manuals" + +.SH NAME +harbor-project-preheat-execution - Manage preheat executions + + +.SH SYNOPSIS +\fBharbor project preheat execution [flags]\fP + + +.SH DESCRIPTION +Manage P2P preheat executions under a project + + +.SH OPTIONS +\fB-h\fP, \fB--help\fP[=false] + help for execution + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +\fB-c\fP, \fB--config\fP="" + config file (default is $HOME/.config/harbor-cli/config.yaml) + +.PP +\fB-o\fP, \fB--output-format\fP="" + Output format. One of: json|yaml|csv + +.PP +\fB-v\fP, \fB--verbose\fP[=false] + verbose output + + +.SH EXAMPLE +.EX + harbor-cli project preheat execution list [NAME|ID] [POLICY_NAME] +.EE + + +.SH SEE ALSO +\fBharbor-project-preheat(1)\fP, \fBharbor-project-preheat-execution-list(1)\fP \ No newline at end of file diff --git a/doc/man-docs/man1/harbor-project-preheat.1 b/doc/man-docs/man1/harbor-project-preheat.1 index f5b24d21e..823f8388c 100644 --- a/doc/man-docs/man1/harbor-project-preheat.1 +++ b/doc/man-docs/man1/harbor-project-preheat.1 @@ -38,4 +38,4 @@ Manage project-scoped P2P preheat policies, executions, and tasks in Harbor .SH SEE ALSO -\fBharbor-project(1)\fP, \fBharbor-project-preheat-policy(1)\fP \ No newline at end of file +\fBharbor-project(1)\fP, \fBharbor-project-preheat-execution(1)\fP, \fBharbor-project-preheat-policy(1)\fP \ No newline at end of file diff --git a/pkg/api/preheat_handler.go b/pkg/api/preheat_handler.go index d571a3ae6..a536971f6 100644 --- a/pkg/api/preheat_handler.go +++ b/pkg/api/preheat_handler.go @@ -153,7 +153,7 @@ func ListProvidersUnderProject(projectName string) ([]*models.ProviderUnderProje return response.Payload, nil } -func ListPreheatExecutions(projectName string, policyName string, isID bool, opts ...ListFlags) (*preheat.ListExecutionsOK, error) { +func ListPreheatExecutions(projectName string, policyName string, opts ...ListFlags) (*preheat.ListExecutionsOK, error) { ctx, client, err := utils.ContextWithClient() if err != nil { return nil, err @@ -164,14 +164,6 @@ func ListPreheatExecutions(projectName string, policyName string, isID bool, opt listFlags = opts[0] } - if isID { - project, err := GetProject(projectName, true) - if err != nil { - return nil, err - } - projectName = project.Payload.Name - } - response, err := client.Preheat.ListExecutions(ctx, &preheat.ListExecutionsParams{ ProjectName: projectName, PreheatPolicyName: policyName, From d35c1b0aa79a8922931431a7a897956950bef5b5 Mon Sep 17 00:00:00 2001 From: Sypher845 Date: Thu, 14 May 2026 00:10:55 +0530 Subject: [PATCH 3/6] feat(preheat): preheat execution view command Signed-off-by: Sypher845 --- cmd/harbor/root/project/preheat/execution.go | 1 + .../root/project/preheat/execution/list.go | 4 + .../root/project/preheat/execution/view.go | 116 ++++++++++++++++++ pkg/api/preheat_handler.go | 17 +++ pkg/prompt/prompt.go | 37 ++++++ .../execution/list/{list.go => view.go} | 0 pkg/views/preheat/execution/select/view.go | 59 +++++++++ pkg/views/preheat/execution/view/view.go | 67 ++++++++++ 8 files changed, 301 insertions(+) create mode 100644 cmd/harbor/root/project/preheat/execution/view.go rename pkg/views/preheat/execution/list/{list.go => view.go} (100%) create mode 100644 pkg/views/preheat/execution/select/view.go create mode 100644 pkg/views/preheat/execution/view/view.go diff --git a/cmd/harbor/root/project/preheat/execution.go b/cmd/harbor/root/project/preheat/execution.go index 0b4c3314a..724aca7c6 100644 --- a/cmd/harbor/root/project/preheat/execution.go +++ b/cmd/harbor/root/project/preheat/execution.go @@ -29,6 +29,7 @@ func ExecutionCommand() *cobra.Command { cmd.AddCommand( execution.ListExecutionCommand(), + execution.ViewExecutionCommand(), ) return cmd diff --git a/cmd/harbor/root/project/preheat/execution/list.go b/cmd/harbor/root/project/preheat/execution/list.go index 3426f5c10..59b185af9 100644 --- a/cmd/harbor/root/project/preheat/execution/list.go +++ b/cmd/harbor/root/project/preheat/execution/list.go @@ -46,6 +46,10 @@ func ListExecutionCommand() *cobra.Command { return fmt.Errorf("page size must be greater than 0 and less than or equal to 100") } + if isID && len(args) == 0 { + return fmt.Errorf("project ID must be provided when using --id") + } + if len(args) >= 1 { log.Debugf("Project name provided: %s", args[0]) projectName = args[0] diff --git a/cmd/harbor/root/project/preheat/execution/view.go b/cmd/harbor/root/project/preheat/execution/view.go new file mode 100644 index 000000000..1e0879d47 --- /dev/null +++ b/cmd/harbor/root/project/preheat/execution/view.go @@ -0,0 +1,116 @@ +// Copyright Project Harbor Authors +// +// 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 execution + +import ( + "fmt" + "strconv" + + "github.com/goharbor/harbor-cli/pkg/api" + "github.com/goharbor/harbor-cli/pkg/prompt" + "github.com/goharbor/harbor-cli/pkg/utils" + "github.com/goharbor/harbor-cli/pkg/views/preheat/execution/view" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func ViewExecutionCommand() *cobra.Command { + var isID bool + + cmd := &cobra.Command{ + Use: "view [PROJECT_NAME|ID] [POLICY_NAME] [EXECUTION_ID]", + Short: "View preheat execution details", + Long: "Get details of a specific P2P preheat execution under a project", + Example: ` harbor-cli project preheat execution view [NAME|ID] [POLICY_NAME] [EXECUTION_ID]`, + Args: cobra.MaximumNArgs(3), + RunE: func(cmd *cobra.Command, args []string) error { + var err error + var projectName, policyName string + var executionID int64 + + if isID && len(args) == 0 { + return fmt.Errorf("project ID must be provided when using --id") + } + + if len(args) >= 1 { + log.Debugf("Project name provided: %s", args[0]) + projectName = args[0] + } else { + log.Debug("No project name provided, prompting user") + projectName, err = prompt.GetProjectNameFromUser() + if err != nil { + return fmt.Errorf("failed to get project name: %v", utils.ParseHarborErrorMsg(err)) + } + } + if isID { + project, err := api.GetProject(projectName, true) + if err != nil { + return fmt.Errorf("failed to resolve project ID: %v", utils.ParseHarborErrorMsg(err)) + } + projectName = project.Payload.Name + } + + if len(args) >= 2 { + log.Debugf("Policy name provided: %s", args[1]) + policyName = args[1] + } else { + log.Debug("No policy name provided, prompting user") + policyName, err = prompt.GetPreheatPolicyNameFromUser(projectName) + if err != nil { + return fmt.Errorf("failed to get policy name: %v", utils.ParseHarborErrorMsg(err)) + } + } + + if len(args) >= 3 { + log.Debugf("Execution ID provided: %s", args[2]) + executionID, err = strconv.ParseInt(args[2], 10, 64) + if err != nil { + return fmt.Errorf("invalid execution ID %q: %v", args[2], err) + } + } else { + log.Debug("No execution ID provided, prompting user") + executionID, err = prompt.GetPreheatPolicyExecIDFromUser(projectName, policyName) + if err != nil { + return fmt.Errorf("failed to get execution id: %v", utils.ParseHarborErrorMsg(err)) + } + } + + log.Debug("Fetching preheat execution details...") + resp, err := api.GetPreheatExecution(projectName, policyName, executionID) + if err != nil { + if utils.ParseHarborErrorCode(err) == "404" { + return fmt.Errorf("no execution found for execution ID %d in policy %s in project %s", executionID, policyName, projectName) + } + return fmt.Errorf("failed to view preheat execution: %v", utils.ParseHarborErrorMsg(err)) + } + + FormatFlag := viper.GetString("output-format") + if FormatFlag != "" { + err = utils.PrintFormat(resp.Payload, FormatFlag) + if err != nil { + return err + } + } else { + view.ViewExecution(resp.Payload) + } + return nil + }, + } + + flags := cmd.Flags() + flags.BoolVar(&isID, "id", false, "Get preheat policy execution by project id") + + return cmd +} diff --git a/pkg/api/preheat_handler.go b/pkg/api/preheat_handler.go index a536971f6..553c26c58 100644 --- a/pkg/api/preheat_handler.go +++ b/pkg/api/preheat_handler.go @@ -177,3 +177,20 @@ func ListPreheatExecutions(projectName string, policyName string, opts ...ListFl } return response, nil } + +func GetPreheatExecution(projectName string, policyName string, executionID int64) (*preheat.GetExecutionOK, error) { + ctx, client, err := utils.ContextWithClient() + if err != nil { + return nil, err + } + + response, err := client.Preheat.GetExecution(ctx, &preheat.GetExecutionParams{ + ProjectName: projectName, + PreheatPolicyName: policyName, + ExecutionID: executionID, + }) + if err != nil { + return nil, err + } + return response, nil +} diff --git a/pkg/prompt/prompt.go b/pkg/prompt/prompt.go index 68aede286..11ed7b25b 100644 --- a/pkg/prompt/prompt.go +++ b/pkg/prompt/prompt.go @@ -37,6 +37,7 @@ import ( rpolicies "github.com/goharbor/harbor-cli/pkg/views/replication/policies/select" rtasks "github.com/goharbor/harbor-cli/pkg/views/replication/task/select" + phexecutions "github.com/goharbor/harbor-cli/pkg/views/preheat/execution/select" phpolicies "github.com/goharbor/harbor-cli/pkg/views/preheat/policy/select" repoView "github.com/goharbor/harbor-cli/pkg/views/repository/select" @@ -505,3 +506,39 @@ func GetPreheatPolicyNameFromUser(projectName string) (string, error) { res := <-resultChan return res.name, res.err } + +func GetPreheatPolicyExecIDFromUser(projectName string, policyName string) (int64, error) { + type result struct { + id int64 + err error + } + executionID := make(chan result) + + go func() { + response, err := api.ListPreheatExecutions(projectName, policyName) + if err != nil { + executionID <- result{0, err} + return + } + + if len(response.Payload) == 0 { + executionID <- result{0, errors.New("no preheat executions found")} + return + } + + id, err := phexecutions.PreheatExecutionList(response.Payload) + if err != nil { + if err == phexecutions.ErrUserAborted { + executionID <- result{0, errors.New("user aborted execution selection")} + } else { + executionID <- result{0, fmt.Errorf("error during execution selection: %w", err)} + } + return + } + + executionID <- result{id, nil} + }() + + res := <-executionID + return res.id, res.err +} diff --git a/pkg/views/preheat/execution/list/list.go b/pkg/views/preheat/execution/list/view.go similarity index 100% rename from pkg/views/preheat/execution/list/list.go rename to pkg/views/preheat/execution/list/view.go diff --git a/pkg/views/preheat/execution/select/view.go b/pkg/views/preheat/execution/select/view.go new file mode 100644 index 000000000..513551d52 --- /dev/null +++ b/pkg/views/preheat/execution/select/view.go @@ -0,0 +1,59 @@ +// Copyright Project Harbor Authors +// +// 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 execution + +import ( + "errors" + "fmt" + "os" + "strconv" + + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" + "github.com/goharbor/harbor-cli/pkg/views/base/selection" +) + +var ErrUserAborted = errors.New("user aborted selection") + +func PreheatExecutionList(executions []*models.Execution) (int64, error) { + items := make([]list.Item, len(executions)) + for i, e := range executions { + items[i] = selection.Item(strconv.FormatInt(e.ID, 10)) + } + + m := selection.NewModel(items, "Preheat Execution") + + p, err := tea.NewProgram(m, tea.WithAltScreen()).Run() + if err != nil { + fmt.Println("Error running program:", err) + os.Exit(1) + } + + if model, ok := p.(selection.Model); ok { + if model.Aborted { + return 0, ErrUserAborted + } + if model.Choice == "" { + return 0, errors.New("no execution selected") + } + id, err := strconv.ParseInt(model.Choice, 10, 64) + if err != nil { + return 0, fmt.Errorf("failed to parse execution ID %q: %v", model.Choice, err) + } + return id, nil + } + + return 0, errors.New("unexpected program result") +} diff --git a/pkg/views/preheat/execution/view/view.go b/pkg/views/preheat/execution/view/view.go new file mode 100644 index 000000000..6eab9dd64 --- /dev/null +++ b/pkg/views/preheat/execution/view/view.go @@ -0,0 +1,67 @@ +// Copyright Project Harbor Authors +// +// 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 view + +import ( + "fmt" + "os" + "strconv" + + "github.com/charmbracelet/bubbles/table" + tea "github.com/charmbracelet/bubbletea" + "github.com/goharbor/go-client/pkg/sdk/v2.0/models" + "github.com/goharbor/harbor-cli/pkg/utils" + "github.com/goharbor/harbor-cli/pkg/views/base/tablelist" +) + +var columns = []table.Column{ + {Title: "ID", Width: tablelist.WidthS}, + {Title: "Status", Width: tablelist.WidthM}, + {Title: "Trigger", Width: tablelist.WidthM}, + {Title: "Success Rate", Width: tablelist.WidthM}, + {Title: "Start Time", Width: tablelist.WidthL}, + {Title: "End Time", Width: tablelist.WidthL}, + {Title: "Vendor", Width: tablelist.WidthM}, +} + +func ViewExecution(exec *models.Execution) { + var rows []table.Row + + startTime, _ := utils.FormatCreatedTime(exec.StartTime) + endTime := "-" + if exec.Status != "Running" { + endTime, _ = utils.FormatCreatedTime(exec.EndTime) + } + + successRate := "-" + if m := exec.Metrics; m != nil && m.TaskCount > 0 { + successRate = fmt.Sprintf("%d%%", m.SuccessTaskCount*100/m.TaskCount) + } + + rows = append(rows, table.Row{ + strconv.FormatInt(exec.ID, 10), + exec.Status, + exec.Trigger, + successRate, + startTime, + endTime, + exec.VendorType, + }) + + m := tablelist.NewModel(columns, rows, len(rows)) + if _, err := tea.NewProgram(m).Run(); err != nil { + fmt.Println("Error running program:", err) + os.Exit(1) + } +} From 01e9859b01a8288e843ddb4a9054f7e7445eea69 Mon Sep 17 00:00:00 2001 From: Sypher845 Date: Thu, 14 May 2026 00:14:59 +0530 Subject: [PATCH 4/6] docs(preheat): docs for preheat execution view command Signed-off-by: Sypher845 --- .../harbor-project-preheat-execution-view.md | 43 ++++++++++++++++++ .../harbor-project-preheat-execution.md | 1 + .../harbor-project-preheat-execution-view.1 | 45 +++++++++++++++++++ .../man1/harbor-project-preheat-execution.1 | 2 +- 4 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 doc/cli-docs/harbor-project-preheat-execution-view.md create mode 100644 doc/man-docs/man1/harbor-project-preheat-execution-view.1 diff --git a/doc/cli-docs/harbor-project-preheat-execution-view.md b/doc/cli-docs/harbor-project-preheat-execution-view.md new file mode 100644 index 000000000..55855719b --- /dev/null +++ b/doc/cli-docs/harbor-project-preheat-execution-view.md @@ -0,0 +1,43 @@ +--- +title: harbor project preheat execution view +weight: 50 +--- +## harbor project preheat execution view + +### Description + +##### View preheat execution details + +### Synopsis + +Get details of a specific P2P preheat execution under a project + +```sh +harbor project preheat execution view [PROJECT_NAME|ID] [POLICY_NAME] [EXECUTION_ID] [flags] +``` + +### Examples + +```sh + harbor-cli project preheat execution view [NAME|ID] [POLICY_NAME] [EXECUTION_ID] +``` + +### Options + +```sh + -h, --help help for view + --id Get preheat policy execution by project id +``` + +### Options inherited from parent commands + +```sh + -c, --config string config file (default is $HOME/.config/harbor-cli/config.yaml) + -o, --output-format string Output format. One of: json|yaml|csv + -v, --verbose verbose output +``` + +### SEE ALSO + +* [harbor project preheat execution](harbor-project-preheat-execution.md) - Manage preheat executions + diff --git a/doc/cli-docs/harbor-project-preheat-execution.md b/doc/cli-docs/harbor-project-preheat-execution.md index 27e746417..3bc9d5c44 100644 --- a/doc/cli-docs/harbor-project-preheat-execution.md +++ b/doc/cli-docs/harbor-project-preheat-execution.md @@ -36,4 +36,5 @@ Manage P2P preheat executions under a project * [harbor project preheat](harbor-project-preheat.md) - Manage project preheat resources * [harbor project preheat execution list](harbor-project-preheat-execution-list.md) - List preheat executions +* [harbor project preheat execution view](harbor-project-preheat-execution-view.md) - View preheat execution details diff --git a/doc/man-docs/man1/harbor-project-preheat-execution-view.1 b/doc/man-docs/man1/harbor-project-preheat-execution-view.1 new file mode 100644 index 000000000..403516daa --- /dev/null +++ b/doc/man-docs/man1/harbor-project-preheat-execution-view.1 @@ -0,0 +1,45 @@ +.nh +.TH "HARBOR" "1" "Harbor Community" "Harbor User Manuals" + +.SH NAME +harbor-project-preheat-execution-view - View preheat execution details + + +.SH SYNOPSIS +\fBharbor project preheat execution view [PROJECT_NAME|ID] [POLICY_NAME] [EXECUTION_ID] [flags]\fP + + +.SH DESCRIPTION +Get details of a specific P2P preheat execution under a project + + +.SH OPTIONS +\fB-h\fP, \fB--help\fP[=false] + help for view + +.PP +\fB--id\fP[=false] + Get preheat policy execution by project id + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +\fB-c\fP, \fB--config\fP="" + config file (default is $HOME/.config/harbor-cli/config.yaml) + +.PP +\fB-o\fP, \fB--output-format\fP="" + Output format. One of: json|yaml|csv + +.PP +\fB-v\fP, \fB--verbose\fP[=false] + verbose output + + +.SH EXAMPLE +.EX + harbor-cli project preheat execution view [NAME|ID] [POLICY_NAME] [EXECUTION_ID] +.EE + + +.SH SEE ALSO +\fBharbor-project-preheat-execution(1)\fP \ No newline at end of file diff --git a/doc/man-docs/man1/harbor-project-preheat-execution.1 b/doc/man-docs/man1/harbor-project-preheat-execution.1 index 353e61d64..586e31ca0 100644 --- a/doc/man-docs/man1/harbor-project-preheat-execution.1 +++ b/doc/man-docs/man1/harbor-project-preheat-execution.1 @@ -38,4 +38,4 @@ Manage P2P preheat executions under a project .SH SEE ALSO -\fBharbor-project-preheat(1)\fP, \fBharbor-project-preheat-execution-list(1)\fP \ No newline at end of file +\fBharbor-project-preheat(1)\fP, \fBharbor-project-preheat-execution-list(1)\fP, \fBharbor-project-preheat-execution-view(1)\fP \ No newline at end of file From 38acf3061b0a0081a0c67b097ea7d5f54d1e6d49 Mon Sep 17 00:00:00 2001 From: Sypher845 Date: Thu, 14 May 2026 00:53:18 +0530 Subject: [PATCH 5/6] feat(preheat): preheat execution stop command Signed-off-by: Sypher845 --- cmd/harbor/root/project/preheat/execution.go | 1 + .../root/project/preheat/execution/stop.go | 106 ++++++++++++++++++ pkg/api/preheat_handler.go | 20 ++++ 3 files changed, 127 insertions(+) create mode 100644 cmd/harbor/root/project/preheat/execution/stop.go diff --git a/cmd/harbor/root/project/preheat/execution.go b/cmd/harbor/root/project/preheat/execution.go index 724aca7c6..44d7a0810 100644 --- a/cmd/harbor/root/project/preheat/execution.go +++ b/cmd/harbor/root/project/preheat/execution.go @@ -30,6 +30,7 @@ func ExecutionCommand() *cobra.Command { cmd.AddCommand( execution.ListExecutionCommand(), execution.ViewExecutionCommand(), + execution.StopExecutionCommand(), ) return cmd diff --git a/cmd/harbor/root/project/preheat/execution/stop.go b/cmd/harbor/root/project/preheat/execution/stop.go new file mode 100644 index 000000000..6f0c231ba --- /dev/null +++ b/cmd/harbor/root/project/preheat/execution/stop.go @@ -0,0 +1,106 @@ +// Copyright Project Harbor Authors +// +// 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 execution + +import ( + "fmt" + "strconv" + + "github.com/goharbor/harbor-cli/pkg/api" + "github.com/goharbor/harbor-cli/pkg/prompt" + "github.com/goharbor/harbor-cli/pkg/utils" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +func StopExecutionCommand() *cobra.Command { + var isID bool + + cmd := &cobra.Command{ + Use: "stop [PROJECT_NAME|ID] [POLICY_NAME] [EXECUTION_ID]", + Short: "Stop preheat execution", + Long: "Stop a specific P2P preheat execution of a policy under a project", + Example: ` harbor-cli project preheat execution stop [NAME|ID] [POLICY_NAME] [EXECUTION_ID]`, + Args: cobra.MaximumNArgs(3), + RunE: func(cmd *cobra.Command, args []string) error { + var err error + var projectName, policyName string + var executionID int64 + + if isID && len(args) == 0 { + return fmt.Errorf("project ID must be provided when using --id") + } + + if len(args) >= 1 { + log.Debugf("Project name provided: %s", args[0]) + projectName = args[0] + } else { + log.Debug("No project name provided, prompting user") + projectName, err = prompt.GetProjectNameFromUser() + if err != nil { + return fmt.Errorf("failed to get project name: %v", utils.ParseHarborErrorMsg(err)) + } + } + if isID { + project, err := api.GetProject(projectName, true) + if err != nil { + return fmt.Errorf("failed to resolve project ID: %v", utils.ParseHarborErrorMsg(err)) + } + projectName = project.Payload.Name + } + + if len(args) >= 2 { + log.Debugf("Policy name provided: %s", args[1]) + policyName = args[1] + } else { + log.Debug("No policy name provided, prompting user") + policyName, err = prompt.GetPreheatPolicyNameFromUser(projectName) + if err != nil { + return fmt.Errorf("failed to get policy name: %v", utils.ParseHarborErrorMsg(err)) + } + } + + if len(args) >= 3 { + log.Debugf("Execution ID provided: %s", args[2]) + executionID, err = strconv.ParseInt(args[2], 10, 64) + if err != nil { + return fmt.Errorf("invalid execution ID %q: %v", args[2], err) + } + } else { + log.Debug("No execution ID provided, prompting user") + executionID, err = prompt.GetPreheatPolicyExecIDFromUser(projectName, policyName) + if err != nil { + return fmt.Errorf("failed to get execution id: %v", utils.ParseHarborErrorMsg(err)) + } + } + + log.Debug("Stopping preheat execution...") + err = api.StopPreheatExecution(projectName, policyName, executionID) + if err != nil { + if utils.ParseHarborErrorCode(err) == "404" { + return fmt.Errorf("preheat execution %d not found for policy %s in project %s", executionID, policyName, projectName) + } + return fmt.Errorf("failed to stop preheat execution: %v", utils.ParseHarborErrorMsg(err)) + } + + fmt.Printf("Preheat execution %d stopped successfully for policy '%s' in project '%s'\n", executionID, policyName, projectName) + return nil + }, + } + + flags := cmd.Flags() + flags.BoolVar(&isID, "id", false, "Get preheat policy execution by project id") + + return cmd +} diff --git a/pkg/api/preheat_handler.go b/pkg/api/preheat_handler.go index 553c26c58..ef4ce7981 100644 --- a/pkg/api/preheat_handler.go +++ b/pkg/api/preheat_handler.go @@ -194,3 +194,23 @@ func GetPreheatExecution(projectName string, policyName string, executionID int6 } return response, nil } + +func StopPreheatExecution(projectName string, policyName string, executionID int64) error { + ctx, client, err := utils.ContextWithClient() + if err != nil { + return err + } + + _, err = client.Preheat.StopExecution(ctx, &preheat.StopExecutionParams{ + ProjectName: projectName, + PreheatPolicyName: policyName, + ExecutionID: executionID, + Execution: &models.Execution{ + Status: "Stopped", + }, + }) + if err != nil { + return err + } + return nil +} From 4e1b5cee79202ef551963216fa4567bfd549c8e1 Mon Sep 17 00:00:00 2001 From: Sypher845 Date: Thu, 14 May 2026 00:55:18 +0530 Subject: [PATCH 6/6] docs(preheat): docs for preheat execution stop command Signed-off-by: Sypher845 --- .../harbor-project-preheat-execution-stop.md | 43 ++++++++++++++++++ .../harbor-project-preheat-execution.md | 1 + .../harbor-project-preheat-execution-stop.1 | 45 +++++++++++++++++++ .../man1/harbor-project-preheat-execution.1 | 2 +- 4 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 doc/cli-docs/harbor-project-preheat-execution-stop.md create mode 100644 doc/man-docs/man1/harbor-project-preheat-execution-stop.1 diff --git a/doc/cli-docs/harbor-project-preheat-execution-stop.md b/doc/cli-docs/harbor-project-preheat-execution-stop.md new file mode 100644 index 000000000..1456a95e6 --- /dev/null +++ b/doc/cli-docs/harbor-project-preheat-execution-stop.md @@ -0,0 +1,43 @@ +--- +title: harbor project preheat execution stop +weight: 5 +--- +## harbor project preheat execution stop + +### Description + +##### Stop preheat execution + +### Synopsis + +Stop a specific P2P preheat execution of a policy under a project + +```sh +harbor project preheat execution stop [PROJECT_NAME|ID] [POLICY_NAME] [EXECUTION_ID] [flags] +``` + +### Examples + +```sh + harbor-cli project preheat execution stop [NAME|ID] [POLICY_NAME] [EXECUTION_ID] +``` + +### Options + +```sh + -h, --help help for stop + --id Get preheat policy execution by project id +``` + +### Options inherited from parent commands + +```sh + -c, --config string config file (default is $HOME/.config/harbor-cli/config.yaml) + -o, --output-format string Output format. One of: json|yaml|csv + -v, --verbose verbose output +``` + +### SEE ALSO + +* [harbor project preheat execution](harbor-project-preheat-execution.md) - Manage preheat executions + diff --git a/doc/cli-docs/harbor-project-preheat-execution.md b/doc/cli-docs/harbor-project-preheat-execution.md index 3bc9d5c44..673da9e26 100644 --- a/doc/cli-docs/harbor-project-preheat-execution.md +++ b/doc/cli-docs/harbor-project-preheat-execution.md @@ -36,5 +36,6 @@ Manage P2P preheat executions under a project * [harbor project preheat](harbor-project-preheat.md) - Manage project preheat resources * [harbor project preheat execution list](harbor-project-preheat-execution-list.md) - List preheat executions +* [harbor project preheat execution stop](harbor-project-preheat-execution-stop.md) - Stop preheat execution * [harbor project preheat execution view](harbor-project-preheat-execution-view.md) - View preheat execution details diff --git a/doc/man-docs/man1/harbor-project-preheat-execution-stop.1 b/doc/man-docs/man1/harbor-project-preheat-execution-stop.1 new file mode 100644 index 000000000..bbbea9e67 --- /dev/null +++ b/doc/man-docs/man1/harbor-project-preheat-execution-stop.1 @@ -0,0 +1,45 @@ +.nh +.TH "HARBOR" "1" "Harbor Community" "Harbor User Manuals" + +.SH NAME +harbor-project-preheat-execution-stop - Stop preheat execution + + +.SH SYNOPSIS +\fBharbor project preheat execution stop [PROJECT_NAME|ID] [POLICY_NAME] [EXECUTION_ID] [flags]\fP + + +.SH DESCRIPTION +Stop a specific P2P preheat execution of a policy under a project + + +.SH OPTIONS +\fB-h\fP, \fB--help\fP[=false] + help for stop + +.PP +\fB--id\fP[=false] + Get preheat policy execution by project id + + +.SH OPTIONS INHERITED FROM PARENT COMMANDS +\fB-c\fP, \fB--config\fP="" + config file (default is $HOME/.config/harbor-cli/config.yaml) + +.PP +\fB-o\fP, \fB--output-format\fP="" + Output format. One of: json|yaml|csv + +.PP +\fB-v\fP, \fB--verbose\fP[=false] + verbose output + + +.SH EXAMPLE +.EX + harbor-cli project preheat execution stop [NAME|ID] [POLICY_NAME] [EXECUTION_ID] +.EE + + +.SH SEE ALSO +\fBharbor-project-preheat-execution(1)\fP \ No newline at end of file diff --git a/doc/man-docs/man1/harbor-project-preheat-execution.1 b/doc/man-docs/man1/harbor-project-preheat-execution.1 index 586e31ca0..494bbabaf 100644 --- a/doc/man-docs/man1/harbor-project-preheat-execution.1 +++ b/doc/man-docs/man1/harbor-project-preheat-execution.1 @@ -38,4 +38,4 @@ Manage P2P preheat executions under a project .SH SEE ALSO -\fBharbor-project-preheat(1)\fP, \fBharbor-project-preheat-execution-list(1)\fP, \fBharbor-project-preheat-execution-view(1)\fP \ No newline at end of file +\fBharbor-project-preheat(1)\fP, \fBharbor-project-preheat-execution-list(1)\fP, \fBharbor-project-preheat-execution-stop(1)\fP, \fBharbor-project-preheat-execution-view(1)\fP \ No newline at end of file