Merge Go frontend (apiserver + xcecli) into meson build
This commit is contained in:
parent
43dc12a36c
commit
c9dc8c4676
205 changed files with 30793 additions and 328 deletions
45
src/frontend/xcecli/common/output/output_clusters.go
Executable file
45
src/frontend/xcecli/common/output/output_clusters.go
Executable file
|
|
@ -0,0 +1,45 @@
|
|||
package output
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
"xcecli/models"
|
||||
)
|
||||
|
||||
type Output struct {
|
||||
fields string
|
||||
formatJson bool
|
||||
noheader bool
|
||||
verbose bool
|
||||
wideFormat bool
|
||||
jobStatusFilter []string
|
||||
}
|
||||
|
||||
func NewOutput(fields string, formatJson bool, noheader bool) *Output {
|
||||
return &Output{fields: fields, formatJson: formatJson, noheader: noheader}
|
||||
}
|
||||
|
||||
func (o *Output) SetVerbose(verbose bool) {
|
||||
o.verbose = verbose
|
||||
}
|
||||
|
||||
func (o *Output) SetNoHeader(noheader bool) {
|
||||
o.noheader = noheader
|
||||
}
|
||||
|
||||
func (o *Output) SetWideFormat(wideFormat bool) {
|
||||
o.wideFormat = wideFormat
|
||||
}
|
||||
|
||||
func (o *Output) ClusterPrintData(cluster *models.ClusterInfo) {
|
||||
printClustersVerboseOutput(cluster)
|
||||
}
|
||||
|
||||
func printClustersVerboseOutput(clusters *models.ClusterInfo) {
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
||||
defer w.Flush()
|
||||
fmt.Fprintf(w, "XCE Standard Cluster\t%s\t%s\n", clusters.Version, "Jul 08 2025")
|
||||
fmt.Fprintf(w, "My cluster name: <%s>\n", clusters.ClusterName)
|
||||
fmt.Fprintf(w, "The cluster status: <%s>\n", clusters.Status)
|
||||
}
|
||||
377
src/frontend/xcecli/common/output/page_print.go
Executable file
377
src/frontend/xcecli/common/output/page_print.go
Executable file
|
|
@ -0,0 +1,377 @@
|
|||
package output
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
// PagePrinter
|
||||
type PagePrinter interface {
|
||||
// PrepareForPrinting
|
||||
PrepareForPrinting(firstPage interface{})
|
||||
// PrintPage
|
||||
PrintPage(page interface{})
|
||||
// Finish
|
||||
Finish()
|
||||
// StartPrinting
|
||||
StartPrinting()
|
||||
}
|
||||
|
||||
const (
|
||||
DataTypeJobs = "jobs"
|
||||
DataTypeHosts = "hosts"
|
||||
DataTypeQueues = "queues"
|
||||
DataTypeArrayJobs = "array_jobs"
|
||||
DataTypeArrayElements = "array_elements"
|
||||
DataTypeResourcesDist = "resources_dist"
|
||||
DataTypeResourcesDef = "resource_def"
|
||||
DataTypeRoles = "roles"
|
||||
DataTypeUsers = "users"
|
||||
)
|
||||
|
||||
// NewPagePrinter
|
||||
func NewPagePrinter(dataType string, opt ...BasePageOption) PagePrinter {
|
||||
switch dataType {
|
||||
case DataTypeJobs:
|
||||
return NewJobsPagePrinter(opt...)
|
||||
case DataTypeHosts:
|
||||
return NewHostsPagePrinter(opt...)
|
||||
case DataTypeQueues:
|
||||
return NewQueuesPagePrinter(opt...)
|
||||
case DataTypeArrayJobs:
|
||||
return NewArrayJobsPagePrinter(opt...)
|
||||
case DataTypeArrayElements:
|
||||
return NewArrayElementsPagePrinter(opt...)
|
||||
case DataTypeResourcesDist:
|
||||
return NewResourcesDistPagePrinter(opt...)
|
||||
case DataTypeResourcesDef:
|
||||
return NewResourcesDefPagePrinter(opt...)
|
||||
case DataTypeRoles:
|
||||
return NewRolesPagePrinter(opt...)
|
||||
case DataTypeUsers:
|
||||
return NewUserRolesPagePrinter(opt...)
|
||||
default:
|
||||
fmt.Printf("unsupported data type: %s, using default Jobs printer\n", dataType)
|
||||
return NewJobsPagePrinter(opt...)
|
||||
}
|
||||
}
|
||||
|
||||
type BasePageOption interface {
|
||||
apply(*BasePagePrinter)
|
||||
}
|
||||
|
||||
type basePageOptionFunc func(*BasePagePrinter)
|
||||
|
||||
func (f basePageOptionFunc) apply(bpp *BasePagePrinter) {
|
||||
f(bpp)
|
||||
}
|
||||
|
||||
func WithFields(fields string) BasePageOption {
|
||||
return basePageOptionFunc(func(bpp *BasePagePrinter) {
|
||||
bpp.fields = fields
|
||||
})
|
||||
}
|
||||
|
||||
func WithFormatJson(b bool) BasePageOption {
|
||||
return basePageOptionFunc(func(bpp *BasePagePrinter) {
|
||||
bpp.formatJson = b
|
||||
})
|
||||
}
|
||||
|
||||
func WithNoHeader(b bool) BasePageOption {
|
||||
return basePageOptionFunc(func(bpp *BasePagePrinter) {
|
||||
bpp.noheader = b
|
||||
})
|
||||
}
|
||||
|
||||
func WithVerbose(b bool) BasePageOption {
|
||||
return basePageOptionFunc(func(bpp *BasePagePrinter) {
|
||||
bpp.verbose = b
|
||||
})
|
||||
}
|
||||
|
||||
func WithWideFormat(b bool) BasePageOption {
|
||||
return basePageOptionFunc(func(bpp *BasePagePrinter) {
|
||||
bpp.wideFormat = b
|
||||
})
|
||||
}
|
||||
|
||||
func WithJobStatusFilter(filter []string) BasePageOption {
|
||||
return basePageOptionFunc(func(bpp *BasePagePrinter) {
|
||||
bpp.jobStatusFilter = filter
|
||||
})
|
||||
}
|
||||
|
||||
// BasePagePrinter
|
||||
type BasePagePrinter struct {
|
||||
/* external config */
|
||||
fields string
|
||||
formatJson bool
|
||||
noheader bool
|
||||
verbose bool
|
||||
wideFormat bool
|
||||
jobStatusFilter []string
|
||||
|
||||
/* internal variables */
|
||||
headerPrinted bool
|
||||
columnWidths map[string]int // store column widths
|
||||
fieldFormats []*fieldFormat // field formats
|
||||
formattedHeaders []string // formatted headers
|
||||
w *tabwriter.Writer
|
||||
pageCount int // record the number of printed pages
|
||||
startPrintOnce *sync.Once
|
||||
}
|
||||
|
||||
// newBasePagePrinter
|
||||
func newBasePagePrinter(opts ...BasePageOption) *BasePagePrinter {
|
||||
base := &BasePagePrinter{
|
||||
headerPrinted: false,
|
||||
columnWidths: make(map[string]int),
|
||||
w: tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0),
|
||||
pageCount: 0,
|
||||
startPrintOnce: &sync.Once{},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt.apply(base)
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
// StartPrinting
|
||||
func (p *BasePagePrinter) StartPrinting() {
|
||||
p.startPrintOnce.Do(func() {
|
||||
// if the format is JSON and need to print multiple pages, print the opening bracket
|
||||
if p.formatJson && !p.verbose {
|
||||
fmt.Print("[")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// prepareHeaders
|
||||
func (p *BasePagePrinter) prepareHeaders() {
|
||||
p.formattedHeaders = make([]string, len(p.fieldFormats))
|
||||
for i, format := range p.fieldFormats {
|
||||
p.formattedHeaders[i] = fmt.Sprintf("%"+format.align+"*s", p.columnWidths[format.name], format.name)
|
||||
}
|
||||
}
|
||||
|
||||
// printHeader
|
||||
func (p *BasePagePrinter) printHeader() {
|
||||
if !p.headerPrinted && !p.noheader {
|
||||
fmt.Fprintf(p.w, "%s\n", strings.Join(p.formattedHeaders, "\t"))
|
||||
p.headerPrinted = true
|
||||
}
|
||||
}
|
||||
|
||||
// Finish
|
||||
func (p *BasePagePrinter) Finish() {
|
||||
// if the format is JSON and printed data, add the closing bracket
|
||||
if p.formatJson && !p.verbose && p.pageCount > 0 {
|
||||
fmt.Println("]")
|
||||
}
|
||||
p.w.Flush()
|
||||
}
|
||||
|
||||
type fieldFormat struct {
|
||||
name string
|
||||
width int
|
||||
align string // "-" for left align, "" for right align
|
||||
}
|
||||
|
||||
func parseFieldFormat(field string) *fieldFormat {
|
||||
// parse the field format, support the following formats:
|
||||
// "FIELD" - default format
|
||||
// "FIELD:10" - specify the width to 10, right align
|
||||
// "FIELD:-10" - specify the width to 10, left align
|
||||
parts := strings.Split(field, ":")
|
||||
if len(parts) == 1 {
|
||||
return &fieldFormat{
|
||||
name: parts[0],
|
||||
width: 0,
|
||||
align: "-",
|
||||
}
|
||||
}
|
||||
|
||||
width := 0
|
||||
align := ""
|
||||
if strings.HasPrefix(parts[1], "-") {
|
||||
align = "-"
|
||||
width, _ = strconv.Atoi(parts[1][1:])
|
||||
} else {
|
||||
width, _ = strconv.Atoi(parts[1])
|
||||
}
|
||||
|
||||
return &fieldFormat{
|
||||
name: parts[0],
|
||||
width: width,
|
||||
align: align,
|
||||
}
|
||||
}
|
||||
|
||||
func formatSize(value float64) (float64, string, error) {
|
||||
if value <= 0 {
|
||||
return 0, "MB", nil
|
||||
}
|
||||
switch {
|
||||
case value >= 1024*1024*1024: // >= 1PB
|
||||
return value / (1024 * 1024 * 1024), "PB", nil
|
||||
case value >= 1024*1024: // >= 1TB
|
||||
return value / (1024 * 1024), "TB", nil
|
||||
case value >= 1024: // >= 1GB
|
||||
return value / 1024, "GB", nil
|
||||
default: // MB
|
||||
return value, "MB", nil
|
||||
}
|
||||
}
|
||||
|
||||
func formatSizeString(value float64) string {
|
||||
size, unit, err := formatSize(value)
|
||||
if err != nil {
|
||||
return "-"
|
||||
}
|
||||
return fmt.Sprintf("%.2f%s", size, unit)
|
||||
}
|
||||
|
||||
func appendOrDefault(slice []string, value string, defaultValue string) []string {
|
||||
if value != "" {
|
||||
return append(slice, value)
|
||||
} else {
|
||||
return append(slice, defaultValue)
|
||||
}
|
||||
}
|
||||
|
||||
func getFieldFormatIndex(fieldFormats []*fieldFormat, target string) int {
|
||||
for i, f := range fieldFormats {
|
||||
if f.name == target {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func PrintWrappedLine(w *tabwriter.Writer, line string, width int, indentSpaces int) {
|
||||
line = strings.TrimRight(line, " ")
|
||||
indent := strings.Repeat(" ", indentSpaces)
|
||||
first := true
|
||||
for len(line) > 0 {
|
||||
if line[len(line)-1] == ',' {
|
||||
line = line[:len(line)-1] + ";"
|
||||
}
|
||||
if first {
|
||||
// first line
|
||||
if len(line) <= width {
|
||||
fmt.Fprintf(w, "%s\n", line)
|
||||
break
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s\n", line[:width])
|
||||
line = line[width:]
|
||||
}
|
||||
first = false
|
||||
} else {
|
||||
avail := width - indentSpaces
|
||||
if avail <= 0 {
|
||||
avail = 1
|
||||
}
|
||||
if len(line) <= avail {
|
||||
fmt.Fprintf(w, "%s%s\n", indent, line)
|
||||
break
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s%s\n", indent, line[:avail])
|
||||
line = line[avail:]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *BasePagePrinter) PrintJsonPageGeneric(rows [][]string, headers []string) {
|
||||
data := make([]map[string]string, len(rows))
|
||||
for i, row := range rows {
|
||||
entry := make(map[string]string)
|
||||
for j, header := range headers {
|
||||
if j < len(row) {
|
||||
entry[header] = strings.TrimSpace(row[j])
|
||||
} else {
|
||||
entry[header] = ""
|
||||
}
|
||||
}
|
||||
data[i] = entry
|
||||
}
|
||||
for i, item := range data {
|
||||
jsonData, err := json.MarshalIndent(item, "", " ")
|
||||
if err != nil {
|
||||
fmt.Println("error marshalling to JSON:", err)
|
||||
return
|
||||
}
|
||||
if p.pageCount > 1 || i > 0 {
|
||||
fmt.Print(",\n")
|
||||
}
|
||||
fmt.Print(string(jsonData))
|
||||
}
|
||||
}
|
||||
|
||||
func FormatStringField(val string, width int, truncatFlag bool, prefixAtStart bool) string {
|
||||
if len(val) > width && truncatFlag {
|
||||
if prefixAtStart {
|
||||
return "*" + val[len(val)-width+1:]
|
||||
} else {
|
||||
keepLen := width - 1
|
||||
if keepLen < 0 {
|
||||
keepLen = 0
|
||||
}
|
||||
return val[:keepLen] + "*"
|
||||
}
|
||||
} else {
|
||||
if val == "" {
|
||||
return fmt.Sprintf("%-*s", width, "-")
|
||||
} else {
|
||||
return fmt.Sprintf("%-*s", width, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type TablePrinter struct {
|
||||
Headers []string
|
||||
Rows [][]string
|
||||
ColWidths []int
|
||||
}
|
||||
|
||||
func (tp *TablePrinter) CalcColWidths() {
|
||||
tp.ColWidths = make([]int, len(tp.Headers))
|
||||
for i, h := range tp.Headers {
|
||||
tp.ColWidths[i] = len(h)
|
||||
}
|
||||
for _, row := range tp.Rows {
|
||||
for i, cell := range row {
|
||||
if len(cell) > tp.ColWidths[i] {
|
||||
tp.ColWidths[i] = len(cell)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TablePrinter) Print() {
|
||||
tp.CalcColWidths()
|
||||
format := ""
|
||||
for _, w := range tp.ColWidths {
|
||||
format += fmt.Sprintf("%%-%ds ", w)
|
||||
}
|
||||
format += "\n"
|
||||
|
||||
fmt.Printf(format, ToInterfaceSlice(tp.Headers)...)
|
||||
for _, row := range tp.Rows {
|
||||
fmt.Printf(format, ToInterfaceSlice(row)...)
|
||||
}
|
||||
}
|
||||
|
||||
func ToInterfaceSlice(strs []string) []interface{} {
|
||||
res := make([]interface{}, len(strs))
|
||||
for i, s := range strs {
|
||||
res[i] = s
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
|
@ -0,0 +1,230 @@
|
|||
package output
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
"xcecli/models"
|
||||
)
|
||||
|
||||
// ArrayElementsPagePrinter
|
||||
type ArrayElementsPagePrinter struct {
|
||||
*BasePagePrinter
|
||||
}
|
||||
|
||||
// NewArrayElementsPagePrinter
|
||||
func NewArrayElementsPagePrinter(opt ...BasePageOption) *ArrayElementsPagePrinter {
|
||||
return &ArrayElementsPagePrinter{
|
||||
BasePagePrinter: newBasePagePrinter(opt...),
|
||||
}
|
||||
}
|
||||
|
||||
// PrepareForPrinting
|
||||
func (p *ArrayElementsPagePrinter) PrepareForPrinting(firstPage interface{}) {
|
||||
jobs, ok := firstPage.(*models.JobsResponse)
|
||||
if !ok {
|
||||
fmt.Println("error: wrong data type to ArrayElementsPagePrinter")
|
||||
return
|
||||
}
|
||||
|
||||
// set field formats
|
||||
p.fieldFormats = p.prepareFieldFormats()
|
||||
|
||||
// init column widths
|
||||
p.initColumnWidths(jobs)
|
||||
|
||||
// prepare headers
|
||||
p.prepareHeaders()
|
||||
}
|
||||
|
||||
// prepare field formats
|
||||
func (p *ArrayElementsPagePrinter) prepareFieldFormats() []*fieldFormat {
|
||||
var fieldFormats []*fieldFormat
|
||||
if p.fields == "" {
|
||||
// default fields
|
||||
defaultFields := []string{
|
||||
models.HEADER_JOB_JOBID,
|
||||
models.HEADER_JOB_USER,
|
||||
models.HEADER_JOB_STATUS,
|
||||
models.HEADER_JOB_QUEUE,
|
||||
models.HEADER_JOB_FROM_HOST,
|
||||
models.HEADER_JOB_EXEC_HOST,
|
||||
models.HEADER_JOB_NAME,
|
||||
models.HEADER_JOB_SUBMIT_TIME,
|
||||
}
|
||||
for _, field := range defaultFields {
|
||||
fieldFormats = append(fieldFormats, &fieldFormat{name: field, align: "-"})
|
||||
}
|
||||
} else {
|
||||
// parse user specified fields
|
||||
for _, field := range strings.Split(p.fields, ",") {
|
||||
fieldFormats = append(fieldFormats, parseFieldFormat(field))
|
||||
}
|
||||
}
|
||||
return fieldFormats
|
||||
}
|
||||
|
||||
// init column widths
|
||||
func (p *ArrayElementsPagePrinter) initColumnWidths(jobs *models.JobsResponse) {
|
||||
// init column widths to header length
|
||||
for _, format := range p.fieldFormats {
|
||||
p.columnWidths[format.name] = len(format.name)
|
||||
if format.width > 0 {
|
||||
p.columnWidths[format.name] = format.width
|
||||
}
|
||||
}
|
||||
|
||||
// calculate the max width of the first page data
|
||||
for _, job := range jobs.Data.Rows {
|
||||
for _, format := range p.fieldFormats {
|
||||
width := 0
|
||||
switch format.name {
|
||||
case models.HEADER_JOB_JOBID:
|
||||
// format the job id as "jobID[idx]"
|
||||
arrayJobIDStr := fmt.Sprintf("%d[%d]", job.JobID, job.ArrayIdx)
|
||||
width = len(arrayJobIDStr)
|
||||
case models.HEADER_JOB_USER:
|
||||
width = len(job.SubmitUser)
|
||||
case models.HEADER_JOB_STATUS:
|
||||
width = len(job.JobStatus)
|
||||
case models.HEADER_JOB_QUEUE:
|
||||
width = len(job.QueueName)
|
||||
case models.HEADER_JOB_FROM_HOST:
|
||||
width = len(job.FromHost)
|
||||
case models.HEADER_JOB_EXEC_HOST:
|
||||
continue
|
||||
case models.HEADER_JOB_NAME:
|
||||
width = len(job.JobName)
|
||||
case models.HEADER_JOB_SUBMIT_TIME:
|
||||
width = len(job.SubmitTime)
|
||||
}
|
||||
|
||||
if !p.wideFormat && width > 15 {
|
||||
width = 15
|
||||
}
|
||||
|
||||
if width > p.columnWidths[format.name] && format.width == 0 {
|
||||
p.columnWidths[format.name] = width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PrintPage
|
||||
func (p *ArrayElementsPagePrinter) PrintPage(page interface{}) {
|
||||
jobs, ok := page.(*models.JobsResponse)
|
||||
if !ok {
|
||||
fmt.Println("error: wrong data type to ArrayElementsPagePrinter")
|
||||
return
|
||||
}
|
||||
|
||||
p.pageCount++
|
||||
|
||||
if p.verbose {
|
||||
printJobsVerboseOutput(jobs, p.jobStatusFilter)
|
||||
return
|
||||
}
|
||||
|
||||
if p.formatJson {
|
||||
p.printJsonPage(jobs)
|
||||
return
|
||||
} else {
|
||||
// print header (if needed)
|
||||
p.printHeader()
|
||||
|
||||
// print data rows
|
||||
rows := p.formatRows(jobs, true)
|
||||
for _, row := range rows {
|
||||
fmt.Fprintln(p.w, strings.Join(row, "\t"))
|
||||
}
|
||||
p.w.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// format data rows
|
||||
func (p *ArrayElementsPagePrinter) formatRows(jobs *models.JobsResponse, truncatFlag bool) [][]string {
|
||||
var rows [][]string
|
||||
if p.wideFormat {
|
||||
truncatFlag = false
|
||||
}
|
||||
for _, job := range jobs.Data.Rows {
|
||||
mainRow := make([]string, 0, len(p.fieldFormats))
|
||||
var extraRows [][]string
|
||||
for _, format := range p.fieldFormats {
|
||||
width := p.columnWidths[format.name]
|
||||
align := format.align
|
||||
switch format.name {
|
||||
case models.HEADER_JOB_JOBID:
|
||||
// format the job id as "jobID[idx]"
|
||||
arrayJobIDStr := fmt.Sprintf("%d[%d]", job.JobID, job.ArrayIdx)
|
||||
mainRow = appendOrDefault(mainRow, fmt.Sprintf("%"+align+"*s", 20, arrayJobIDStr), "-")
|
||||
case models.HEADER_JOB_USER:
|
||||
mainRow = appendOrDefault(mainRow, FormatStringField(job.SubmitUser, width, truncatFlag, true), "-")
|
||||
case models.HEADER_JOB_STATUS:
|
||||
mainRow = appendOrDefault(mainRow, FormatStringField(job.JobStatus, width, truncatFlag, true), "-")
|
||||
case models.HEADER_JOB_QUEUE:
|
||||
mainRow = appendOrDefault(mainRow, FormatStringField(job.QueueName, width, truncatFlag, true), "-")
|
||||
case models.HEADER_JOB_FROM_HOST:
|
||||
mainRow = appendOrDefault(mainRow, FormatStringField(job.FromHost, width, truncatFlag, true), "-")
|
||||
case models.HEADER_JOB_EXEC_HOST:
|
||||
var execHosts models.ExecHosts
|
||||
if err := json.Unmarshal([]byte(job.ExecHosts.String()), &execHosts); err == nil && len(execHosts.HostSlots) > 0 {
|
||||
firstHost := fmt.Sprintf("%d*%s", execHosts.HostSlots[0].NumSlots, execHosts.HostSlots[0].HostName)
|
||||
mainRow = appendOrDefault(mainRow, FormatStringField(firstHost, width, truncatFlag, true), "-")
|
||||
|
||||
if len(execHosts.HostSlots) > 1 {
|
||||
for _, slot := range execHosts.HostSlots[1:] {
|
||||
newLine := make([]string, len(p.fieldFormats))
|
||||
for i, f := range p.fieldFormats {
|
||||
if f.name == "EXEC_HOST" {
|
||||
continue
|
||||
}
|
||||
newLine[i] = strings.Repeat(" ", p.columnWidths[f.name])
|
||||
}
|
||||
hostText := fmt.Sprintf("%d*%s", slot.NumSlots, slot.HostName)
|
||||
newLine[getFieldFormatIndex(p.fieldFormats, "EXEC_HOST")] = FormatStringField(hostText, width, truncatFlag, true)
|
||||
extraRows = append(extraRows, append([]string{}, newLine...))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mainRow = appendOrDefault(mainRow, fmt.Sprintf("%"+align+"*s", width, "-"), "-")
|
||||
}
|
||||
case models.HEADER_JOB_NAME:
|
||||
mainRow = appendOrDefault(mainRow, FormatStringField(job.JobName, width, truncatFlag, true), "-")
|
||||
case models.HEADER_JOB_SUBMIT_TIME:
|
||||
// remove the timezone part and parse the time
|
||||
timeStr := strings.Split(job.SubmitTime, " ")[0:2]
|
||||
timeStrWithoutTZ := strings.Join(timeStr, " ")
|
||||
|
||||
if submitTime, err := time.Parse("2006-01-02 15:04:05", timeStrWithoutTZ); err == nil {
|
||||
month := submitTime.Format("Jan")
|
||||
displayTime := fmt.Sprintf("%s %02d %02d:%02d",
|
||||
month,
|
||||
submitTime.Day(),
|
||||
submitTime.Hour(),
|
||||
submitTime.Minute())
|
||||
mainRow = appendOrDefault(mainRow, FormatStringField(displayTime, width, truncatFlag, true), "-")
|
||||
} else {
|
||||
mainRow = appendOrDefault(mainRow, fmt.Sprintf("%"+align+"*s", width, "-"), "-")
|
||||
}
|
||||
}
|
||||
}
|
||||
rows = append(rows, mainRow)
|
||||
rows = append(rows, extraRows...)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// print JSON page
|
||||
func (p *ArrayElementsPagePrinter) printJsonPage(jobs *models.JobsResponse) {
|
||||
// if the format is JSON, directly convert the current page to JSON
|
||||
headers := make([]string, len(p.fieldFormats))
|
||||
for i, format := range p.fieldFormats {
|
||||
headers[i] = format.name
|
||||
}
|
||||
|
||||
rows := p.formatRows(jobs, false)
|
||||
|
||||
p.PrintJsonPageGeneric(rows, headers)
|
||||
}
|
||||
199
src/frontend/xcecli/common/output/page_print_array_jobs.go
Normal file
199
src/frontend/xcecli/common/output/page_print_array_jobs.go
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
package output
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"xcecli/models"
|
||||
)
|
||||
|
||||
// ArrayJobsPagePrinter
|
||||
type ArrayJobsPagePrinter struct {
|
||||
*BasePagePrinter
|
||||
}
|
||||
|
||||
// NewArrayJobsPagePrinter
|
||||
func NewArrayJobsPagePrinter(opt ...BasePageOption) *ArrayJobsPagePrinter {
|
||||
return &ArrayJobsPagePrinter{
|
||||
BasePagePrinter: newBasePagePrinter(opt...),
|
||||
}
|
||||
}
|
||||
|
||||
// PrepareForPrinting
|
||||
func (p *ArrayJobsPagePrinter) PrepareForPrinting(firstPage interface{}) {
|
||||
arrayJobsStats, ok := firstPage.(*models.ArrayJobsStatsResponse)
|
||||
if !ok {
|
||||
fmt.Println("error: wrong data type to ArrayJobsPagePrinter")
|
||||
return
|
||||
}
|
||||
|
||||
// set field formats
|
||||
p.fieldFormats = p.prepareFieldFormats()
|
||||
|
||||
// init column widths
|
||||
p.initColumnWidths(arrayJobsStats)
|
||||
|
||||
// prepare headers
|
||||
p.prepareHeaders()
|
||||
}
|
||||
|
||||
// prepare field formats
|
||||
func (p *ArrayJobsPagePrinter) prepareFieldFormats() []*fieldFormat {
|
||||
var fieldFormats []*fieldFormat
|
||||
if p.fields == "" {
|
||||
// default fields
|
||||
defaultFields := []string{
|
||||
models.HEADER_ARRAY_JOB_JOBID,
|
||||
models.HEADER_ARRAY_JOB_SPEC,
|
||||
models.HEADER_ARRAY_JOB_OWNER,
|
||||
models.HEADER_ARRAY_JOB_NJOBS,
|
||||
models.HEADER_ARRAY_JOB_PEND,
|
||||
models.HEADER_ARRAY_JOB_DONE,
|
||||
models.HEADER_ARRAY_JOB_RUN,
|
||||
models.HEADER_ARRAY_JOB_EXIT,
|
||||
models.HEADER_ARRAY_JOB_SSUSP,
|
||||
models.HEADER_ARRAY_JOB_USUSP,
|
||||
models.HEADER_ARRAY_JOB_PSUSP,
|
||||
}
|
||||
for _, field := range defaultFields {
|
||||
fieldFormats = append(fieldFormats, &fieldFormat{name: field, align: "-"})
|
||||
}
|
||||
} else {
|
||||
// parse user specified fields
|
||||
for _, field := range strings.Split(p.fields, ",") {
|
||||
fieldFormats = append(fieldFormats, parseFieldFormat(field))
|
||||
}
|
||||
}
|
||||
return fieldFormats
|
||||
}
|
||||
|
||||
// init column widths
|
||||
func (p *ArrayJobsPagePrinter) initColumnWidths(arrayJobsStats *models.ArrayJobsStatsResponse) {
|
||||
// init column widths to header length
|
||||
for _, format := range p.fieldFormats {
|
||||
p.columnWidths[format.name] = len(format.name)
|
||||
if format.width > 0 {
|
||||
p.columnWidths[format.name] = format.width
|
||||
}
|
||||
}
|
||||
|
||||
// calculate the max width of the first page data
|
||||
for _, stat := range arrayJobsStats.Data.Rows {
|
||||
for _, format := range p.fieldFormats {
|
||||
width := 0
|
||||
switch format.name {
|
||||
case models.HEADER_ARRAY_JOB_JOBID:
|
||||
width = len(fmt.Sprintf("%d", stat.JobID))
|
||||
case models.HEADER_ARRAY_JOB_SPEC:
|
||||
width = len(stat.ArrayJobName)
|
||||
case models.HEADER_ARRAY_JOB_OWNER:
|
||||
width = len(stat.SubmitUser)
|
||||
case models.HEADER_ARRAY_JOB_NJOBS:
|
||||
width = len(fmt.Sprintf("%d", stat.NumJobs))
|
||||
case models.HEADER_ARRAY_JOB_PEND:
|
||||
width = len(fmt.Sprintf("%d", stat.NumPend))
|
||||
case models.HEADER_ARRAY_JOB_DONE:
|
||||
width = len(fmt.Sprintf("%d", stat.NumDone))
|
||||
case models.HEADER_ARRAY_JOB_RUN:
|
||||
width = len(fmt.Sprintf("%d", stat.NumRun))
|
||||
case models.HEADER_ARRAY_JOB_EXIT:
|
||||
width = len(fmt.Sprintf("%d", stat.NumExit))
|
||||
case models.HEADER_ARRAY_JOB_SSUSP:
|
||||
width = len(fmt.Sprintf("%d", stat.NumSSusp))
|
||||
case models.HEADER_ARRAY_JOB_USUSP:
|
||||
width = len(fmt.Sprintf("%d", stat.NumUSusp))
|
||||
case models.HEADER_ARRAY_JOB_PSUSP:
|
||||
width = len(fmt.Sprintf("%d", stat.NumPSusp))
|
||||
}
|
||||
|
||||
if !p.wideFormat && width > 15 {
|
||||
width = 15
|
||||
}
|
||||
|
||||
if width > p.columnWidths[format.name] && format.width == 0 {
|
||||
p.columnWidths[format.name] = width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PrintPage
|
||||
func (p *ArrayJobsPagePrinter) PrintPage(page interface{}) {
|
||||
arrayJobsStats, ok := page.(*models.ArrayJobsStatsResponse)
|
||||
if !ok {
|
||||
fmt.Println("error: wrong data type to ArrayJobsPagePrinter")
|
||||
return
|
||||
}
|
||||
|
||||
p.pageCount++
|
||||
|
||||
if p.formatJson {
|
||||
p.printJsonPage(arrayJobsStats)
|
||||
return
|
||||
} else {
|
||||
// print header (if needed)
|
||||
p.printHeader()
|
||||
|
||||
// print data rows
|
||||
rows := p.formatRows(arrayJobsStats, true)
|
||||
for _, row := range rows {
|
||||
fmt.Fprintln(p.w, strings.Join(row, "\t"))
|
||||
}
|
||||
p.w.Flush()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// format data rows
|
||||
// ./xce jobs list -A
|
||||
func (p *ArrayJobsPagePrinter) formatRows(arrayJobsStats *models.ArrayJobsStatsResponse, truncatFlag bool) [][]string {
|
||||
var rows [][]string
|
||||
if p.wideFormat {
|
||||
truncatFlag = false
|
||||
}
|
||||
for _, stat := range arrayJobsStats.Data.Rows {
|
||||
row := make([]string, 0, len(p.fieldFormats))
|
||||
for _, format := range p.fieldFormats {
|
||||
width := p.columnWidths[format.name]
|
||||
align := format.align
|
||||
switch format.name {
|
||||
case models.HEADER_ARRAY_JOB_JOBID:
|
||||
row = appendOrDefault(row, fmt.Sprintf("%"+align+"*d", 20, stat.JobID), "-")
|
||||
case models.HEADER_ARRAY_JOB_SPEC:
|
||||
row = appendOrDefault(row, FormatStringField(stat.ArrayJobName, width, truncatFlag, true), "-")
|
||||
case models.HEADER_ARRAY_JOB_OWNER:
|
||||
row = appendOrDefault(row, FormatStringField(stat.SubmitUser, width, truncatFlag, true), "-")
|
||||
case models.HEADER_ARRAY_JOB_NJOBS:
|
||||
row = appendOrDefault(row, fmt.Sprintf("%"+align+"*d", width, stat.NumJobs), "0")
|
||||
case models.HEADER_ARRAY_JOB_PEND:
|
||||
row = appendOrDefault(row, fmt.Sprintf("%"+align+"*d", width, stat.NumPend), "0")
|
||||
case models.HEADER_ARRAY_JOB_DONE:
|
||||
row = appendOrDefault(row, fmt.Sprintf("%"+align+"*d", width, stat.NumDone), "0")
|
||||
case models.HEADER_ARRAY_JOB_RUN:
|
||||
row = appendOrDefault(row, fmt.Sprintf("%"+align+"*d", width, stat.NumRun), "0")
|
||||
case models.HEADER_ARRAY_JOB_EXIT:
|
||||
row = appendOrDefault(row, fmt.Sprintf("%"+align+"*d", width, stat.NumExit), "0")
|
||||
case models.HEADER_ARRAY_JOB_SSUSP:
|
||||
row = appendOrDefault(row, fmt.Sprintf("%"+align+"*d", width, stat.NumSSusp), "0")
|
||||
case models.HEADER_ARRAY_JOB_USUSP:
|
||||
row = appendOrDefault(row, fmt.Sprintf("%"+align+"*d", width, stat.NumUSusp), "0")
|
||||
case models.HEADER_ARRAY_JOB_PSUSP:
|
||||
row = appendOrDefault(row, fmt.Sprintf("%"+align+"*d", width, stat.NumPSusp), "0")
|
||||
}
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// print JSON page
|
||||
func (p *ArrayJobsPagePrinter) printJsonPage(arrayJobsStats *models.ArrayJobsStatsResponse) {
|
||||
// if the format is JSON, directly convert the current page to JSON
|
||||
headers := make([]string, len(p.fieldFormats))
|
||||
for i, format := range p.fieldFormats {
|
||||
headers[i] = format.name
|
||||
}
|
||||
|
||||
rows := p.formatRows(arrayJobsStats, false)
|
||||
|
||||
p.PrintJsonPageGeneric(rows, headers)
|
||||
}
|
||||
289
src/frontend/xcecli/common/output/page_print_hosts.go
Normal file
289
src/frontend/xcecli/common/output/page_print_hosts.go
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
package output
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"xcecli/models"
|
||||
)
|
||||
|
||||
// HostsPagePrinter
|
||||
type HostsPagePrinter struct {
|
||||
*BasePagePrinter
|
||||
}
|
||||
|
||||
// NewHostsPagePrinter
|
||||
func NewHostsPagePrinter(opt ...BasePageOption) *HostsPagePrinter {
|
||||
return &HostsPagePrinter{
|
||||
BasePagePrinter: newBasePagePrinter(opt...),
|
||||
}
|
||||
}
|
||||
|
||||
// PrepareForPrinting
|
||||
func (p *HostsPagePrinter) PrepareForPrinting(firstPage interface{}) {
|
||||
hosts, ok := firstPage.(*models.HostsResponse)
|
||||
if !ok {
|
||||
fmt.Println("error: wrong data type to HostsPagePrinter")
|
||||
return
|
||||
}
|
||||
|
||||
// set field formats
|
||||
p.fieldFormats = p.prepareFieldFormats()
|
||||
|
||||
// init column widths
|
||||
p.initColumnWidths(hosts)
|
||||
|
||||
// prepare headers
|
||||
p.prepareHeaders()
|
||||
}
|
||||
|
||||
// prepare field formats
|
||||
func (p *HostsPagePrinter) prepareFieldFormats() []*fieldFormat {
|
||||
// here define the default fields based on the host data
|
||||
var fieldFormats []*fieldFormat
|
||||
if p.fields == "" {
|
||||
// default fields
|
||||
defaultFields := []string{
|
||||
models.HEADER_HOST_NAME,
|
||||
models.HEADER_HOST_STATUS,
|
||||
models.HEADER_HOST_NCPUS,
|
||||
models.HEADER_HOST_MAX_SLOT,
|
||||
models.HEADER_HOST_NUM_RUN,
|
||||
models.HEADER_HOST_NUM_SSUSP,
|
||||
models.HEADER_HOST_NUM_USUSP,
|
||||
models.HEADER_HOST_HOST_TYPE,
|
||||
}
|
||||
for _, field := range defaultFields {
|
||||
fieldFormats = append(fieldFormats, &fieldFormat{name: field, align: "-"})
|
||||
}
|
||||
} else {
|
||||
// parse user specified fields
|
||||
for _, field := range strings.Split(p.fields, ",") {
|
||||
fieldFormats = append(fieldFormats, parseFieldFormat(field))
|
||||
}
|
||||
}
|
||||
return fieldFormats
|
||||
}
|
||||
|
||||
// init column widths
|
||||
func (p *HostsPagePrinter) initColumnWidths(hosts *models.HostsResponse) {
|
||||
// init column widths to header length
|
||||
for _, format := range p.fieldFormats {
|
||||
p.columnWidths[format.name] = len(format.name)
|
||||
if format.width > 0 {
|
||||
p.columnWidths[format.name] = format.width
|
||||
}
|
||||
}
|
||||
|
||||
// calculate the max width of the first page data
|
||||
for _, host := range hosts.Data.Rows {
|
||||
for _, format := range p.fieldFormats {
|
||||
width := 0
|
||||
switch format.name {
|
||||
case models.HEADER_HOST_NAME:
|
||||
width = len(host.HostName)
|
||||
case models.HEADER_HOST_STATUS:
|
||||
width = len(host.HostStatus)
|
||||
case models.HEADER_HOST_HOST_TYPE:
|
||||
width = len(host.HostType)
|
||||
case models.HEADER_HOST_NCPUS:
|
||||
width = len(fmt.Sprintf("%d", host.NumCPUs))
|
||||
case models.HEADER_HOST_MAX_SLOT:
|
||||
width = len(fmt.Sprintf("%d", host.MaxSlot))
|
||||
case models.HEADER_HOST_NUM_RUN:
|
||||
width = len(fmt.Sprintf("%d", host.NumRun))
|
||||
case models.HEADER_HOST_NUM_SSUSP:
|
||||
width = len(fmt.Sprintf("%d", host.NumSSusp))
|
||||
case models.HEADER_HOST_NUM_USUSP:
|
||||
width = len(fmt.Sprintf("%d", host.NumUSusp))
|
||||
}
|
||||
|
||||
if !p.wideFormat && width > 15 {
|
||||
width = 15
|
||||
}
|
||||
|
||||
if width > p.columnWidths[format.name] && format.width == 0 {
|
||||
p.columnWidths[format.name] = width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PrintPage
|
||||
func (p *HostsPagePrinter) PrintPage(page interface{}) {
|
||||
hosts, ok := page.(*models.HostsResponse)
|
||||
if !ok {
|
||||
fmt.Println("error: wrong data type to HostsPagePrinter")
|
||||
return
|
||||
}
|
||||
|
||||
p.pageCount++
|
||||
|
||||
if p.verbose {
|
||||
printHostsVerboseOutput(hosts)
|
||||
return
|
||||
}
|
||||
|
||||
if p.formatJson {
|
||||
p.printJsonPage(hosts)
|
||||
return
|
||||
} else {
|
||||
// print header (if needed)
|
||||
p.printHeader()
|
||||
|
||||
// print data rows
|
||||
rows := p.formatRows(hosts, true)
|
||||
for _, row := range rows {
|
||||
fmt.Fprintln(p.w, strings.Join(row, "\t"))
|
||||
}
|
||||
p.w.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// format data rows
|
||||
func (p *HostsPagePrinter) formatRows(hosts *models.HostsResponse, truncatFlag bool) [][]string {
|
||||
var rows [][]string
|
||||
if p.wideFormat {
|
||||
truncatFlag = false
|
||||
}
|
||||
for _, host := range hosts.Data.Rows {
|
||||
row := make([]string, 0, len(p.fieldFormats))
|
||||
for _, format := range p.fieldFormats {
|
||||
width := p.columnWidths[format.name]
|
||||
align := format.align
|
||||
switch format.name {
|
||||
case models.HEADER_HOST_NAME:
|
||||
row = appendOrDefault(row, FormatStringField(host.HostName, width, truncatFlag, true), "-")
|
||||
case models.HEADER_HOST_STATUS:
|
||||
row = appendOrDefault(row, FormatStringField(host.HostStatus, width, truncatFlag, true), "-")
|
||||
case models.HEADER_HOST_HOST_TYPE:
|
||||
row = appendOrDefault(row, FormatStringField(host.HostType, width, truncatFlag, true), "-")
|
||||
case models.HEADER_HOST_NCPUS:
|
||||
row = appendOrDefault(row, fmt.Sprintf("%"+align+"*d", width, host.NumCPUs), "0")
|
||||
case models.HEADER_HOST_MAX_SLOT:
|
||||
row = appendOrDefault(row, fmt.Sprintf("%"+align+"*d", width, host.MaxSlot), "0")
|
||||
case models.HEADER_HOST_NUM_RUN:
|
||||
row = appendOrDefault(row, fmt.Sprintf("%"+align+"*d", width, host.NumRun), "0")
|
||||
case models.HEADER_HOST_NUM_SSUSP:
|
||||
row = appendOrDefault(row, fmt.Sprintf("%"+align+"*d", width, host.NumSSusp), "0")
|
||||
case models.HEADER_HOST_NUM_USUSP:
|
||||
row = appendOrDefault(row, fmt.Sprintf("%"+align+"*d", width, host.NumUSusp), "0")
|
||||
}
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// print JSON page
|
||||
func (p *HostsPagePrinter) printJsonPage(hosts *models.HostsResponse) {
|
||||
// if the format is JSON, directly convert the current page to JSON
|
||||
headers := make([]string, len(p.fieldFormats))
|
||||
for i, format := range p.fieldFormats {
|
||||
headers[i] = format.name
|
||||
}
|
||||
|
||||
rows := p.formatRows(hosts, false)
|
||||
p.PrintJsonPageGeneric(rows, headers)
|
||||
}
|
||||
|
||||
func printHostsVerboseOutput(hosts *models.HostsResponse) {
|
||||
for _, host := range hosts.Data.Rows {
|
||||
fmt.Printf("HOST %s\n", host.HostName)
|
||||
// table 1
|
||||
headers := models.HostDetailHeaders
|
||||
row1 := make([]string, 0, len(headers))
|
||||
widths1 := make([]int, len(headers))
|
||||
for i, h := range headers {
|
||||
var val string
|
||||
switch h {
|
||||
case models.HEADER_HOST_STATUS:
|
||||
val = host.HostStatus
|
||||
case models.HEADER_HOST_CPUF:
|
||||
val = fmt.Sprintf("%.2f", host.CPUFactor)
|
||||
case models.HEADER_HOST_JLU:
|
||||
val = "-"
|
||||
case models.HEADER_HOST_MAX:
|
||||
val = fmt.Sprintf("%d", host.MaxSlot)
|
||||
case models.HEADER_HOST_NJOBS:
|
||||
val = fmt.Sprintf("%d", host.NumRun)
|
||||
case models.HEADER_HOST_RUN:
|
||||
val = fmt.Sprintf("%d", host.NumRun)
|
||||
case models.HEADER_HOST_SSUSP:
|
||||
val = fmt.Sprintf("%d", host.NumSSusp)
|
||||
case models.HEADER_HOST_USUSP:
|
||||
val = fmt.Sprintf("%d", host.NumUSusp)
|
||||
case models.HEADER_HOST_RSV:
|
||||
val = "-"
|
||||
case models.HEADER_HOST_DISPT_WINDOW:
|
||||
if str, ok := host.DisptWindow.(string); ok {
|
||||
val = str
|
||||
} else {
|
||||
val = "-"
|
||||
}
|
||||
default:
|
||||
val = "-"
|
||||
}
|
||||
widths1[i] = max(len(h), len(val))
|
||||
row1 = append(row1, FormatStringField(val, widths1[i], false, true))
|
||||
}
|
||||
tp1 := TablePrinter{Headers: headers, Rows: [][]string{row1}, ColWidths: widths1}
|
||||
tp1.Print()
|
||||
fmt.Println("")
|
||||
|
||||
fmt.Println("CURRENT LOAD USED FOR SCHEDULING:")
|
||||
|
||||
// table2
|
||||
headers2 := append([]string{""}, models.HostLoadSchedulingHeaders...)
|
||||
totalVals := []string{"Total", "0.0", "0.0", "0.0", "1%", "0.0", "33", "1", "1137", "51G", "3.9G", "6.8G", "8", "0.0"}
|
||||
reserveVals := []string{"Reserved", "0.0", "0.0", "0.0", "0%", "0.0", "0", "0", "0", "0M", "0M", "0M", "-", "-"}
|
||||
|
||||
widths2 := make([]int, len(headers2))
|
||||
for i := range headers2 {
|
||||
widths2[i] = max(len(headers2[i]), len(totalVals[i]))
|
||||
widths2[i] = max(widths2[i], len(reserveVals[i]))
|
||||
}
|
||||
|
||||
rowTotal := make([]string, len(headers2))
|
||||
rowReserve := make([]string, len(headers2))
|
||||
for i := range headers2 {
|
||||
rowTotal[i] = FormatStringField(totalVals[i], widths2[i], false, true)
|
||||
rowReserve[i] = FormatStringField(reserveVals[i], widths2[i], false, true)
|
||||
}
|
||||
|
||||
tp2 := TablePrinter{Headers: headers2, Rows: [][]string{rowTotal, rowReserve}, ColWidths: widths2}
|
||||
tp2.Print()
|
||||
fmt.Println("")
|
||||
|
||||
// Table 3
|
||||
fmt.Println("LOAD THRESHOLD USED FOR SCHEDULING:")
|
||||
|
||||
headers3 := append([]string{""}, models.HostLoadSchedulingHeaders...)
|
||||
loadSched := []string{"loadSched", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-"}
|
||||
loadStop := []string{"loadStop", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-"}
|
||||
|
||||
widths3 := make([]int, len(headers3))
|
||||
for i := range headers3 {
|
||||
widths3[i] = max(len(headers3[i]), len(loadSched[i]))
|
||||
widths3[i] = max(widths3[i], len(loadStop[i]))
|
||||
}
|
||||
|
||||
rowSched := make([]string, len(headers3))
|
||||
rowStop := make([]string, len(headers3))
|
||||
for i := range headers3 {
|
||||
rowSched[i] = FormatStringField(loadSched[i], widths2[i], false, true)
|
||||
rowStop[i] = FormatStringField(loadStop[i], widths2[i], false, true)
|
||||
}
|
||||
|
||||
tp3 := TablePrinter{Headers: headers3, Rows: [][]string{rowSched, rowStop}, ColWidths: widths2}
|
||||
tp3.Print()
|
||||
fmt.Println("")
|
||||
fmt.Println("CONFIGURED AFFINITY CPU LIST: all")
|
||||
}
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
447
src/frontend/xcecli/common/output/page_print_jobs.go
Normal file
447
src/frontend/xcecli/common/output/page_print_jobs.go
Normal file
|
|
@ -0,0 +1,447 @@
|
|||
package output
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"xcecli/models"
|
||||
)
|
||||
|
||||
// JobsPagePrinter is the printer for jobs
|
||||
type JobsPagePrinter struct {
|
||||
*BasePagePrinter
|
||||
}
|
||||
|
||||
// NewJobsPagePrinter creates a new jobs page printer
|
||||
func NewJobsPagePrinter(opt ...BasePageOption) *JobsPagePrinter {
|
||||
return &JobsPagePrinter{
|
||||
BasePagePrinter: newBasePagePrinter(opt...),
|
||||
}
|
||||
}
|
||||
|
||||
// PrepareForPrinting prepares for printing, initializes field formats and column widths
|
||||
func (p *JobsPagePrinter) PrepareForPrinting(firstPage interface{}) {
|
||||
jobs, ok := firstPage.(*models.JobsResponse)
|
||||
if !ok {
|
||||
fmt.Println("error: wrong type of data passed to JobsPagePrinter")
|
||||
return
|
||||
}
|
||||
|
||||
// set the field formats
|
||||
p.fieldFormats = p.prepareFieldFormats()
|
||||
|
||||
// initialize the column widths
|
||||
p.initColumnWidths(jobs)
|
||||
|
||||
// prepare the headers
|
||||
p.prepareHeaders()
|
||||
}
|
||||
|
||||
// prepare the field formats
|
||||
func (p *JobsPagePrinter) prepareFieldFormats() []*fieldFormat {
|
||||
var fieldFormats []*fieldFormat
|
||||
if p.fields == "" {
|
||||
// default fields
|
||||
defaultFields := []string{
|
||||
models.HEADER_JOB_JOBID,
|
||||
models.HEADER_JOB_USER,
|
||||
models.HEADER_JOB_STATUS,
|
||||
models.HEADER_JOB_QUEUE,
|
||||
models.HEADER_JOB_FROM_HOST,
|
||||
models.HEADER_JOB_EXEC_HOST,
|
||||
models.HEADER_JOB_NAME,
|
||||
models.HEADER_JOB_SUBMIT_TIME,
|
||||
}
|
||||
for _, field := range defaultFields {
|
||||
fieldFormats = append(fieldFormats, &fieldFormat{name: field, align: "-"})
|
||||
}
|
||||
} else {
|
||||
// parse user defined fields
|
||||
for _, field := range strings.Split(p.fields, ",") {
|
||||
fieldFormats = append(fieldFormats, parseFieldFormat(field))
|
||||
}
|
||||
}
|
||||
return fieldFormats
|
||||
}
|
||||
|
||||
// initialize the column widths
|
||||
func (p *JobsPagePrinter) initColumnWidths(jobs *models.JobsResponse) {
|
||||
// initialize the column widths to the length of the headers
|
||||
for _, format := range p.fieldFormats {
|
||||
p.columnWidths[format.name] = len(format.name)
|
||||
if format.width > 0 {
|
||||
p.columnWidths[format.name] = format.width
|
||||
}
|
||||
}
|
||||
|
||||
// calculate the maximum width of the first page data
|
||||
for _, job := range jobs.Data.Rows {
|
||||
for _, format := range p.fieldFormats {
|
||||
width := 0
|
||||
switch format.name {
|
||||
case models.HEADER_JOB_JOBID:
|
||||
width = len(fmt.Sprintf("%d", job.JobID))
|
||||
case models.HEADER_JOB_USER:
|
||||
width = len(job.SubmitUser)
|
||||
case models.HEADER_JOB_STATUS:
|
||||
width = len(job.JobStatus)
|
||||
case models.HEADER_JOB_QUEUE:
|
||||
width = len(job.QueueName)
|
||||
case models.HEADER_JOB_FROM_HOST:
|
||||
width = len(job.FromHost)
|
||||
case models.HEADER_JOB_EXEC_HOST:
|
||||
continue
|
||||
case models.HEADER_JOB_NAME:
|
||||
width = len(job.JobName)
|
||||
case models.HEADER_JOB_SUBMIT_TIME:
|
||||
width = len(job.SubmitTime)
|
||||
case models.HEADER_JOB_CORE_LIMIT:
|
||||
width = len(fmt.Sprintf("%f", job.CoreLimit))
|
||||
case models.HEADER_JOB_MEM_LIMIT:
|
||||
width = len(fmt.Sprintf("%f", job.MemLimit))
|
||||
}
|
||||
|
||||
if !p.wideFormat && width > 15 {
|
||||
width = 15
|
||||
}
|
||||
|
||||
if width > p.columnWidths[format.name] && format.width == 0 {
|
||||
p.columnWidths[format.name] = width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PrintPage prints a page of data
|
||||
func (p *JobsPagePrinter) PrintPage(page interface{}) {
|
||||
jobs, ok := page.(*models.JobsResponse)
|
||||
if !ok {
|
||||
fmt.Println("error: wrong type of data passed to JobsPagePrinter")
|
||||
return
|
||||
}
|
||||
|
||||
p.pageCount++
|
||||
|
||||
if p.verbose {
|
||||
printJobsVerboseOutput(jobs, p.jobStatusFilter)
|
||||
return
|
||||
}
|
||||
|
||||
if p.formatJson {
|
||||
p.printJsonPage(jobs)
|
||||
return
|
||||
} else {
|
||||
// print the header (if needed)
|
||||
p.printHeader()
|
||||
|
||||
// print the data rows
|
||||
rows := p.formatRows(jobs, true)
|
||||
for _, row := range rows {
|
||||
fmt.Fprintln(p.w, strings.Join(row, "\t"))
|
||||
}
|
||||
p.w.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *JobsPagePrinter) printJsonPage(jobs *models.JobsResponse) {
|
||||
// if the format is JSON, directly convert the current page to JSON
|
||||
headers := make([]string, len(p.fieldFormats))
|
||||
for i, format := range p.fieldFormats {
|
||||
headers[i] = format.name
|
||||
}
|
||||
rows := p.formatRows(jobs, false)
|
||||
p.PrintJsonPageGeneric(rows, headers)
|
||||
|
||||
}
|
||||
|
||||
// format the data rows
|
||||
func (p *JobsPagePrinter) formatRows(jobs *models.JobsResponse, truncatFlag bool) [][]string {
|
||||
var rows [][]string
|
||||
if p.wideFormat {
|
||||
truncatFlag = false
|
||||
}
|
||||
for _, job := range jobs.Data.Rows {
|
||||
mainRow := make([]string, 0, len(p.fieldFormats))
|
||||
var extraRows [][]string
|
||||
for _, format := range p.fieldFormats {
|
||||
width := p.columnWidths[format.name]
|
||||
|
||||
align := format.align
|
||||
|
||||
switch format.name {
|
||||
case models.HEADER_JOB_JOBID:
|
||||
var arrayJobIDStr string
|
||||
if job.ArrayIdx != 0 {
|
||||
arrayJobIDStr = fmt.Sprintf("%d[%d]", job.JobID, job.ArrayIdx)
|
||||
} else {
|
||||
arrayJobIDStr = fmt.Sprintf("%d", job.JobID)
|
||||
}
|
||||
mainRow = appendOrDefault(mainRow,
|
||||
fmt.Sprintf("%"+align+"*s", 20, arrayJobIDStr), "-")
|
||||
|
||||
case models.HEADER_JOB_EXEC_HOST:
|
||||
var execHosts models.ExecHosts
|
||||
if err := json.Unmarshal([]byte(job.ExecHosts.String()), &execHosts); err == nil && len(execHosts.HostSlots) > 0 {
|
||||
firstHost := fmt.Sprintf("%d*%s", execHosts.HostSlots[0].NumSlots, execHosts.HostSlots[0].HostName)
|
||||
mainRow = appendOrDefault(mainRow,
|
||||
FormatStringField(firstHost, width, truncatFlag, false), "-")
|
||||
if len(execHosts.HostSlots) > 1 {
|
||||
for _, slot := range execHosts.HostSlots[1:] {
|
||||
newLine := make([]string, len(p.fieldFormats))
|
||||
for i, f := range p.fieldFormats {
|
||||
if f.name == "EXEC_HOST" {
|
||||
continue
|
||||
}
|
||||
newLine[i] = strings.Repeat(" ", p.columnWidths[f.name])
|
||||
}
|
||||
hostText := fmt.Sprintf("%d*%s", slot.NumSlots, slot.HostName)
|
||||
newLine[getFieldFormatIndex(p.fieldFormats, "EXEC_HOST")] = FormatStringField(hostText, width, truncatFlag, false)
|
||||
extraRows = append(extraRows, append([]string{}, newLine...))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mainRow = appendOrDefault(mainRow, fmt.Sprintf("%"+align+"*s", width, "-"), "-")
|
||||
}
|
||||
case models.HEADER_JOB_SUBMIT_TIME:
|
||||
if submitTime, err := parseJobTime(job.SubmitTime); err == nil {
|
||||
mainRow = appendOrDefault(mainRow, FormatStringField(formatJobTime(submitTime), width, truncatFlag, true), "-")
|
||||
} else {
|
||||
mainRow = appendOrDefault(mainRow, fmt.Sprintf("%"+align+"*s", width, "-"), "-")
|
||||
}
|
||||
|
||||
// normal fields
|
||||
case models.HEADER_JOB_NAME:
|
||||
mainRow = appendOrDefault(mainRow, FormatStringField(job.JobName, width, truncatFlag, true), "-")
|
||||
case models.HEADER_JOB_USER:
|
||||
mainRow = appendOrDefault(mainRow, FormatStringField(job.SubmitUser, width, truncatFlag, true), "-")
|
||||
case models.HEADER_JOB_STATUS:
|
||||
mainRow = appendOrDefault(mainRow, FormatStringField(job.JobStatus, width, truncatFlag, true), "-")
|
||||
case models.HEADER_JOB_QUEUE:
|
||||
mainRow = appendOrDefault(mainRow, FormatStringField(job.QueueName, width, truncatFlag, true), "-")
|
||||
case models.HEADER_JOB_FROM_HOST:
|
||||
mainRow = appendOrDefault(mainRow, FormatStringField(job.FromHost, width, truncatFlag, true), "-")
|
||||
case models.HEADER_JOB_COMMAND:
|
||||
mainRow = appendOrDefault(mainRow, FormatStringField(job.Command, width, truncatFlag, true), "-")
|
||||
case models.HEADER_JOB_PROJECT_NAME:
|
||||
mainRow = appendOrDefault(mainRow, FormatStringField(job.ProjectName, width, truncatFlag, true), "-")
|
||||
case models.HEADER_JOB_START_TIME:
|
||||
mainRow = appendOrDefault(mainRow, FormatStringField(job.StartTime, width, truncatFlag, true), "-")
|
||||
case models.HEADER_JOB_END_TIME:
|
||||
mainRow = appendOrDefault(mainRow, FormatStringField(job.EndTime, width, truncatFlag, true), "-")
|
||||
case models.HEADER_JOB_CWD:
|
||||
mainRow = appendOrDefault(mainRow, FormatStringField(job.CWD, width, truncatFlag, true), "-")
|
||||
case models.HEADER_JOB_LAUNCH_TYPE:
|
||||
mainRow = appendOrDefault(mainRow, FormatStringField(job.LaunchType, width, truncatFlag, true), "-")
|
||||
case models.HEADER_JOB_INTERACTIVE_URL:
|
||||
mainRow = appendOrDefault(mainRow, FormatStringField(job.InteractiveJobURL, width, truncatFlag, true), "-")
|
||||
|
||||
// not string field
|
||||
case models.HEADER_JOB_NUM_PROCESSORS:
|
||||
mainRow = appendOrDefault(mainRow, fmt.Sprintf("%"+align+"*d", width, job.NumProcessors), "-")
|
||||
case models.HEADER_JOB_CORE_LIMIT:
|
||||
mainRow = appendOrDefault(mainRow, fmt.Sprintf("%"+align+"*s", width, formatSizeString(job.CoreLimit)), "-")
|
||||
case models.HEADER_JOB_MEM_LIMIT:
|
||||
mainRow = appendOrDefault(mainRow, fmt.Sprintf("%"+align+"*s", width, formatSizeString(job.MemLimit)), "-")
|
||||
default:
|
||||
mainRow = appendOrDefault(mainRow, fmt.Sprintf("%"+align+"*v", width, "-"), "-")
|
||||
}
|
||||
}
|
||||
rows = append(rows, mainRow)
|
||||
rows = append(rows, extraRows...)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
func printJobsVerboseOutput(jobs *models.JobsResponse, filter []string) {
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
||||
defer w.Flush()
|
||||
for jobIndex, job := range jobs.Data.Rows {
|
||||
// if the filter is not empty, only show the jobs that match the filter
|
||||
if len(filter) > 0 {
|
||||
found := false
|
||||
for _, status := range filter {
|
||||
if job.JobStatus == status {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// generate jobId for two format: 111 or 111[10]
|
||||
jobId := fmt.Sprintf("%d", job.JobID)
|
||||
index := strings.Index(job.JobName, "[")
|
||||
if index != -1 {
|
||||
jobId = fmt.Sprintf("%s%s", jobId, job.JobName[index:])
|
||||
}
|
||||
|
||||
// (1) print the job baisc info when sending "jobs list -l" command
|
||||
firstLine := "Job <" + jobId + ">, "
|
||||
if job.JobName != "" {
|
||||
firstLine = firstLine + "Job Name <" + job.JobName + ">, "
|
||||
}
|
||||
if job.SubmitUser != "" {
|
||||
firstLine = firstLine + "User <" + job.SubmitUser + ">, "
|
||||
}
|
||||
if job.ProjectName != "" {
|
||||
firstLine = firstLine + "Project <" + job.ProjectName + ">, "
|
||||
}
|
||||
if job.JobStatus != "" {
|
||||
firstLine = firstLine + "Status <" + job.JobStatus + ">, "
|
||||
}
|
||||
|
||||
if job.QueueName != "" {
|
||||
firstLine = firstLine + "Queue <" + job.QueueName + ">, "
|
||||
}
|
||||
if job.JobPriority != 0 {
|
||||
firstLine = firstLine + "Priority <" + strconv.Itoa(job.JobPriority) + ">, "
|
||||
}
|
||||
if job.LaunchType != "" {
|
||||
firstLine = firstLine + "Launch Type <" + job.LaunchType + ">, "
|
||||
}
|
||||
if job.Command != "" {
|
||||
firstLine = firstLine + "Command <" + job.Command + ">, "
|
||||
}
|
||||
if job.JobDescription != "" {
|
||||
firstLine = firstLine + "Job Description <" + job.JobDescription + "> "
|
||||
}
|
||||
PrintWrappedLine(w, firstLine, 80, 18)
|
||||
|
||||
// (2) print the submit time info
|
||||
submitLine := ""
|
||||
if submitTime, err := parseJobTime(job.SubmitTime); err == nil {
|
||||
submitLine = submitLine + fmt.Sprintf("%s %s %02d:%02d:%02d: ",
|
||||
submitTime.Format("Jan"), submitTime.Format("02"),
|
||||
submitTime.Hour(), submitTime.Minute(), submitTime.Second())
|
||||
if job.FromHost != "" {
|
||||
submitLine = submitLine + "Submitted from host <" + job.FromHost + ">, "
|
||||
}
|
||||
if job.CWD != "" {
|
||||
submitLine = submitLine + "CWD <" + job.CWD + ">, "
|
||||
}
|
||||
if job.NumProcessors != 0 {
|
||||
submitLine = submitLine + strconv.Itoa(job.NumProcessors) + " Task(s), "
|
||||
}
|
||||
if job.Resreq != "" {
|
||||
submitLine = submitLine + "Requested Resources <" + job.Resreq + ">, "
|
||||
}
|
||||
}
|
||||
PrintWrappedLine(w, submitLine, 80, 18)
|
||||
|
||||
// (3) print the start time info
|
||||
startLine := ""
|
||||
if startTime, err := parseJobTime(job.StartTime); err == nil {
|
||||
month := startTime.Format("Jan")
|
||||
day := startTime.Format("02")
|
||||
exec_hostname := ""
|
||||
started_slots := 0
|
||||
if len(job.ExecHosts.HostSlots) > 0 {
|
||||
for _, host := range job.ExecHosts.HostSlots {
|
||||
started_slots += host.NumSlots
|
||||
if exec_hostname == "" {
|
||||
exec_hostname = exec_hostname + fmt.Sprintf("<%d*%s>", host.NumSlots, host.HostName)
|
||||
} else {
|
||||
exec_hostname = exec_hostname + " " + fmt.Sprintf("<%d*%s>", host.NumSlots, host.HostName)
|
||||
}
|
||||
}
|
||||
}
|
||||
startLine = startLine + fmt.Sprintf("%s %s %02d:%02d:%02d: ",
|
||||
month, day, startTime.Hour(), startTime.Minute(), startTime.Second())
|
||||
|
||||
if started_slots > 0 {
|
||||
startSlotMsg := fmt.Sprintf("%d", started_slots)
|
||||
startLine = startLine + "Started " + startSlotMsg + " Task(s) "
|
||||
}
|
||||
if exec_hostname != "" {
|
||||
startLine = startLine + "on Host(s) " + exec_hostname + ", "
|
||||
}
|
||||
if job.ExecHome != "" {
|
||||
startLine = startLine + "Execution Home <" + job.ExecHome + ">, "
|
||||
}
|
||||
if job.ExecCWD != "" {
|
||||
startLine = startLine + "Execution CWD <" + job.ExecCWD + ">, "
|
||||
}
|
||||
if job.InteractiveJobURL != "" {
|
||||
startLine = startLine + "Interactive Job URL <" + job.InteractiveJobURL + ">, "
|
||||
}
|
||||
|
||||
//fmt.Fprintf(w, "%s %s %02d:%02d:%02d: Resource usage collected.\n",
|
||||
// month, day, submitTime.Hour(), submitTime.Minute(), submitTime.Second())
|
||||
//fmt.Fprintf(w, " MEM: Mbytes; SWAP: Mbytes; NTHREAD: \n")
|
||||
pgids := make([]string, len(job.PGIDs))
|
||||
pids := make([]string, len(job.PID))
|
||||
for i, pgid := range job.PGIDs {
|
||||
pgids[i] = fmt.Sprintf("%d", pgid)
|
||||
}
|
||||
for i, pid := range job.PID {
|
||||
pids[i] = fmt.Sprintf("%d", pid)
|
||||
}
|
||||
|
||||
if len(pgids) > 0 {
|
||||
startLine = startLine + "PGID: " + strings.Join(pgids, ",") + ", "
|
||||
} else if len(pids) > 0 {
|
||||
// only show pids
|
||||
startLine = startLine + "PIDs: " + strings.Join(pids, ",") + ", "
|
||||
}
|
||||
}
|
||||
PrintWrappedLine(w, startLine, 80, 18)
|
||||
|
||||
// (4) print end line
|
||||
endLine := ""
|
||||
if job.JobStatus == "DONE" || job.JobStatus == "EXIT" {
|
||||
if endTime, err := parseJobTime(job.EndTime); err == nil {
|
||||
endLine = endLine + fmt.Sprintf("%s %s %02d:%02d:%02d: ",
|
||||
endTime.Format("Jan"), endTime.Format("02"),
|
||||
endTime.Hour(), endTime.Minute(), endTime.Second())
|
||||
}
|
||||
if job.JobStatus == "EXIT" {
|
||||
endLine = endLine + "Completed <" + strings.ToLower(job.JobStatus) + ">. "
|
||||
} else {
|
||||
endLine = endLine + "Done successfully. "
|
||||
}
|
||||
|
||||
if job.CPUTime != 0 {
|
||||
endLine = endLine + "The CPU time used is <" + strconv.Itoa(job.CPUTime) + "> seconds."
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
PrintWrappedLine(w, endLine, 80, 18)
|
||||
fmt.Println("")
|
||||
// print the scheduling parameters table
|
||||
fmt.Printf("SCHEDULING PARAMETERS\n")
|
||||
|
||||
headers2 := append([]string{""}, models.JobLoadSchedulingHeaders...)
|
||||
loadSched := []string{"loadSched", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-"}
|
||||
loadStop := []string{"loadStop", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-"}
|
||||
|
||||
widths2 := make([]int, len(headers2))
|
||||
for i := range headers2 {
|
||||
widths2[i] = max(len(headers2[i]), len(loadSched[i]))
|
||||
widths2[i] = max(widths2[i], len(loadStop[i]))
|
||||
}
|
||||
|
||||
rowSched := make([]string, len(headers2))
|
||||
rowStop := make([]string, len(headers2))
|
||||
for i := range headers2 {
|
||||
rowSched[i] = FormatStringField(loadSched[i], widths2[i], false, true)
|
||||
rowStop[i] = FormatStringField(loadStop[i], widths2[i], false, true)
|
||||
}
|
||||
|
||||
tp2 := TablePrinter{Headers: headers2, Rows: [][]string{rowSched, rowStop}, ColWidths: widths2}
|
||||
tp2.Print()
|
||||
fmt.Println("")
|
||||
|
||||
// print the resource requirement details
|
||||
fmt.Printf("RESOURCE REQUIREMENT DETAILS:\n")
|
||||
fmt.Fprintln(w, " Combined: select[type == local] order[r15s:pg]")
|
||||
fmt.Fprintln(w, " Effective: select[type == local] order[r15s:pg]")
|
||||
if jobIndex != len(jobs.Data.Rows)-1 {
|
||||
fmt.Fprintln(w, "------------------------------------------------------------------------------")
|
||||
}
|
||||
}
|
||||
}
|
||||
268
src/frontend/xcecli/common/output/page_print_queues.go
Normal file
268
src/frontend/xcecli/common/output/page_print_queues.go
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
package output
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"xcecli/models"
|
||||
)
|
||||
|
||||
// HostsPagePrinter
|
||||
type QueuesPagePrinter struct {
|
||||
*BasePagePrinter
|
||||
}
|
||||
|
||||
// NewHostsPagePrinter
|
||||
func NewQueuesPagePrinter(opt ...BasePageOption) *QueuesPagePrinter {
|
||||
return &QueuesPagePrinter{
|
||||
BasePagePrinter: newBasePagePrinter(opt...),
|
||||
}
|
||||
}
|
||||
|
||||
// PrepareForPrinting
|
||||
func (p *QueuesPagePrinter) PrepareForPrinting(firstPage interface{}) {
|
||||
queues, ok := firstPage.(*models.QueuesResponse)
|
||||
if !ok {
|
||||
fmt.Println("error: wrong data type to QueuesPagePrinter")
|
||||
return
|
||||
}
|
||||
|
||||
// set field formats
|
||||
p.fieldFormats = p.prepareFieldFormats()
|
||||
|
||||
// init column widths
|
||||
p.initColumnWidths(queues)
|
||||
|
||||
// prepare headers
|
||||
p.prepareHeaders()
|
||||
}
|
||||
|
||||
// prepare field formats
|
||||
func (p *QueuesPagePrinter) prepareFieldFormats() []*fieldFormat {
|
||||
var fieldFormats []*fieldFormat
|
||||
if p.fields == "" {
|
||||
// default fields
|
||||
defaultFields := []string{
|
||||
models.HEADER_QUEUE_NAME,
|
||||
models.HEADER_QUEUE_PRIORITY,
|
||||
models.HEADER_QUEUE_STATUS,
|
||||
models.HEADER_QUEUE_NUM_PEND,
|
||||
models.HEADER_QUEUE_NUM_RUN,
|
||||
models.HEADER_QUEUE_NUM_SSUSP,
|
||||
models.HEADER_QUEUE_NUM_USUSP,
|
||||
models.HEADER_QUEUE_NUM_DONE,
|
||||
}
|
||||
for _, field := range defaultFields {
|
||||
fieldFormats = append(fieldFormats, &fieldFormat{name: field, align: "-"})
|
||||
}
|
||||
} else {
|
||||
// parse user specified fields
|
||||
for _, field := range strings.Split(p.fields, ",") {
|
||||
fieldFormats = append(fieldFormats, parseFieldFormat(field))
|
||||
}
|
||||
}
|
||||
return fieldFormats
|
||||
}
|
||||
|
||||
// init column widths
|
||||
func (p *QueuesPagePrinter) initColumnWidths(queues *models.QueuesResponse) {
|
||||
// init column widths to header length
|
||||
for _, format := range p.fieldFormats {
|
||||
p.columnWidths[format.name] = len(format.name)
|
||||
if format.width > 0 {
|
||||
p.columnWidths[format.name] = format.width
|
||||
}
|
||||
}
|
||||
|
||||
// calculate the max width of the first page data
|
||||
for _, queue := range queues.Data.Rows {
|
||||
for _, format := range p.fieldFormats {
|
||||
width := 0
|
||||
switch format.name {
|
||||
case models.HEADER_QUEUE_NAME:
|
||||
width = len(queue.QueueName)
|
||||
case models.HEADER_QUEUE_PRIORITY:
|
||||
width = len(fmt.Sprintf("%d", queue.Priority))
|
||||
case models.HEADER_QUEUE_STATUS:
|
||||
width = len(queue.QueueStatus)
|
||||
case models.HEADER_QUEUE_NUM_PEND:
|
||||
width = len(fmt.Sprintf("%d", queue.NumPend))
|
||||
case models.HEADER_QUEUE_NUM_RUN:
|
||||
width = len(fmt.Sprintf("%d", queue.NumRun))
|
||||
case models.HEADER_QUEUE_NUM_SSUSP:
|
||||
width = len(fmt.Sprintf("%d", queue.NumSSusp))
|
||||
case models.HEADER_QUEUE_NUM_USUSP:
|
||||
width = len(fmt.Sprintf("%d", queue.NumUSusp))
|
||||
case models.HEADER_QUEUE_NUM_DONE:
|
||||
width = len(fmt.Sprintf("%d", queue.NumDone))
|
||||
}
|
||||
|
||||
if !p.wideFormat && width > 15 {
|
||||
width = 15
|
||||
}
|
||||
|
||||
if width > p.columnWidths[format.name] && format.width == 0 {
|
||||
p.columnWidths[format.name] = width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PrintPage
|
||||
func (p *QueuesPagePrinter) PrintPage(page interface{}) {
|
||||
queues, ok := page.(*models.QueuesResponse)
|
||||
if !ok {
|
||||
fmt.Println("error: wrong data type to QueuesPagePrinter")
|
||||
return
|
||||
}
|
||||
|
||||
p.pageCount++
|
||||
|
||||
if p.verbose {
|
||||
printQueuesVerboseOutput(queues)
|
||||
return
|
||||
}
|
||||
|
||||
if p.formatJson {
|
||||
p.printJsonPage(queues)
|
||||
return
|
||||
} else {
|
||||
// print header (if needed)
|
||||
p.printHeader()
|
||||
// print data rows
|
||||
rows := p.formatRows(queues, true)
|
||||
for _, row := range rows {
|
||||
fmt.Fprintln(p.w, strings.Join(row, "\t"))
|
||||
}
|
||||
p.w.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// format data rows
|
||||
func (p *QueuesPagePrinter) formatRows(queues *models.QueuesResponse, truncatFlag bool) [][]string {
|
||||
var rows [][]string
|
||||
if p.wideFormat {
|
||||
truncatFlag = false
|
||||
}
|
||||
for _, queue := range queues.Data.Rows {
|
||||
row := make([]string, 0, len(p.fieldFormats))
|
||||
for _, format := range p.fieldFormats {
|
||||
width := p.columnWidths[format.name]
|
||||
align := format.align
|
||||
switch format.name {
|
||||
case models.HEADER_QUEUE_NAME:
|
||||
row = appendOrDefault(row, FormatStringField(queue.QueueName, width, truncatFlag, true), "-")
|
||||
case models.HEADER_QUEUE_STATUS:
|
||||
row = appendOrDefault(row, FormatStringField(queue.QueueStatus, width, truncatFlag, true), "-")
|
||||
case models.HEADER_QUEUE_PRIORITY:
|
||||
row = appendOrDefault(row, fmt.Sprintf("%"+align+"*d", width, queue.Priority), "0")
|
||||
case models.HEADER_QUEUE_NUM_PEND:
|
||||
row = appendOrDefault(row, fmt.Sprintf("%"+align+"*d", width, queue.NumPend), "0")
|
||||
case models.HEADER_QUEUE_NUM_RUN:
|
||||
row = appendOrDefault(row, fmt.Sprintf("%"+align+"*d", width, queue.NumRun), "0")
|
||||
case models.HEADER_QUEUE_NUM_SSUSP:
|
||||
row = appendOrDefault(row, fmt.Sprintf("%"+align+"*d", width, queue.NumSSusp), "0")
|
||||
case models.HEADER_QUEUE_NUM_USUSP:
|
||||
row = appendOrDefault(row, fmt.Sprintf("%"+align+"*d", width, queue.NumUSusp), "0")
|
||||
case models.HEADER_QUEUE_NUM_DONE:
|
||||
row = appendOrDefault(row, fmt.Sprintf("%"+align+"*d", width, queue.NumDone), "0")
|
||||
}
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// print JSON page
|
||||
func (p *QueuesPagePrinter) printJsonPage(queues *models.QueuesResponse) {
|
||||
// if the format is JSON, directly convert the current page to JSON
|
||||
headers := make([]string, len(p.fieldFormats))
|
||||
for i, format := range p.fieldFormats {
|
||||
headers[i] = format.name
|
||||
}
|
||||
|
||||
rows := p.formatRows(queues, false)
|
||||
p.PrintJsonPageGeneric(rows, headers)
|
||||
}
|
||||
|
||||
func printQueuesVerboseOutput(queues *models.QueuesResponse) {
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
||||
defer w.Flush()
|
||||
|
||||
for _, queue := range queues.Data.Rows {
|
||||
fmt.Printf("QUEUE: %s\n", queue.QueueName)
|
||||
if queue.Description != "" {
|
||||
fmt.Fprintf(w, "-- %s\n", queue.Description)
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Printf("PARAMETERS/STATISTICS:\n")
|
||||
// table 1
|
||||
headers := models.QueueDetailHeaders
|
||||
row1 := make([]string, 0, len(headers))
|
||||
widths1 := make([]int, len(headers))
|
||||
|
||||
for i, q := range headers {
|
||||
var val string
|
||||
switch q {
|
||||
case models.HEADER_QUEUE_PRIO:
|
||||
val = fmt.Sprintf("%d", queue.Priority)
|
||||
case models.HEADER_QUEUE_NICE:
|
||||
val = "-"
|
||||
case models.HEADER_QUEUE_STATUS:
|
||||
val = queue.QueueStatus
|
||||
case models.HEADER_QUEUE_MAX:
|
||||
val = "-"
|
||||
case models.HEADER_QUEUE_JLU:
|
||||
val = "-"
|
||||
case models.HEADER_QUEUE_JLP:
|
||||
val = "-"
|
||||
case models.HEADER_QUEUE_JLH:
|
||||
val = "-"
|
||||
case models.HEADER_QUEUE_NJOBS:
|
||||
val = "-"
|
||||
case models.HEADER_QUEUE_PEND:
|
||||
val = fmt.Sprintf("%d", queue.NumPend)
|
||||
case models.HEADER_QUEUE_RUN:
|
||||
val = fmt.Sprintf("%d", queue.NumRun)
|
||||
case models.HEADER_QUEUE_SSUSP:
|
||||
val = fmt.Sprintf("%d", queue.NumSSusp)
|
||||
case models.HEADER_QUEUE_USUSP:
|
||||
val = fmt.Sprintf("%d", queue.NumUSusp)
|
||||
case models.HEADER_HOST_RSV:
|
||||
val = "-"
|
||||
default:
|
||||
val = "-"
|
||||
}
|
||||
widths1[i] = max(len(q), len(val))
|
||||
row1 = append(row1, FormatStringField(val, widths1[i], false, true))
|
||||
}
|
||||
tp1 := TablePrinter{Headers: headers, Rows: [][]string{row1}, ColWidths: widths1}
|
||||
tp1.Print()
|
||||
fmt.Println()
|
||||
|
||||
// table 2
|
||||
fmt.Printf("SCHEDULING PARAMETERS\n")
|
||||
|
||||
headers2 := append([]string{""}, models.QueueLoadSchedulingHeaders...)
|
||||
loadSched := []string{"loadSched", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-"}
|
||||
loadStop := []string{"loadStop", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-", "-"}
|
||||
|
||||
widths2 := make([]int, len(headers2))
|
||||
for i := range headers2 {
|
||||
widths2[i] = max(len(headers2[i]), len(loadSched[i]))
|
||||
widths2[i] = max(widths2[i], len(loadStop[i]))
|
||||
}
|
||||
|
||||
rowSched := make([]string, len(headers2))
|
||||
rowStop := make([]string, len(headers2))
|
||||
for i := range headers2 {
|
||||
rowSched[i] = FormatStringField(loadSched[i], widths2[i], false, true)
|
||||
rowStop[i] = FormatStringField(loadStop[i], widths2[i], false, true)
|
||||
}
|
||||
|
||||
tp2 := TablePrinter{Headers: headers2, Rows: [][]string{rowSched, rowStop}, ColWidths: widths2}
|
||||
tp2.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
}
|
||||
365
src/frontend/xcecli/common/output/page_print_resources.go
Normal file
365
src/frontend/xcecli/common/output/page_print_resources.go
Normal file
|
|
@ -0,0 +1,365 @@
|
|||
package output
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"xcecli/models"
|
||||
)
|
||||
|
||||
type ResourcesPagePrinter struct {
|
||||
*BasePagePrinter
|
||||
isDefinition bool // Flag to distinguish between distribution and definition
|
||||
}
|
||||
|
||||
func NewResourcesDistPagePrinter(opt ...BasePageOption) *ResourcesPagePrinter {
|
||||
return &ResourcesPagePrinter{
|
||||
BasePagePrinter: newBasePagePrinter(opt...),
|
||||
isDefinition: false,
|
||||
}
|
||||
}
|
||||
|
||||
func NewResourcesDefPagePrinter(opt ...BasePageOption) *ResourcesPagePrinter {
|
||||
return &ResourcesPagePrinter{
|
||||
BasePagePrinter: newBasePagePrinter(opt...),
|
||||
isDefinition: true,
|
||||
}
|
||||
}
|
||||
|
||||
// PrepareForPrinting supports both ResourceDistResponse and ResourceDefResponse
|
||||
func (p *ResourcesPagePrinter) PrepareForPrinting(firstPage interface{}) {
|
||||
// Check if it's ResourceDistResponse
|
||||
if resourcesDist, ok := firstPage.(*models.ResourceDistResponse); ok {
|
||||
p.isDefinition = false
|
||||
// set field formats
|
||||
p.fieldFormats = p.prepareFieldFormats()
|
||||
// init column widths
|
||||
p.initColumnWidthsForDist(resourcesDist)
|
||||
// prepare headers
|
||||
p.prepareHeaders()
|
||||
return
|
||||
}
|
||||
|
||||
// Check if it's ResourceDefResponse
|
||||
if resourcesDef, ok := firstPage.(*models.ResourceDefResponse); ok {
|
||||
p.isDefinition = true
|
||||
// set field formats
|
||||
p.fieldFormats = p.prepareFieldFormats()
|
||||
// init column widths
|
||||
p.initColumnWidthsForDef(resourcesDef)
|
||||
// prepare headers
|
||||
p.prepareHeaders()
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("error: wrong data type to ResourcesPagePrinter")
|
||||
}
|
||||
|
||||
// prepare field formats
|
||||
func (p *ResourcesPagePrinter) prepareFieldFormats() []*fieldFormat {
|
||||
var fieldFormats []*fieldFormat
|
||||
if p.fields == "" {
|
||||
// default fields based on type
|
||||
if p.isDefinition {
|
||||
defaultFields := []string{
|
||||
models.HEADER_RESOURCE_NAME,
|
||||
models.HEADER_RESOURCE_DEF_TYPE,
|
||||
models.HEADER_RESOURCE_DEF_ORDER,
|
||||
models.HEADER_RESOURCE_DEF_DESC,
|
||||
}
|
||||
for _, field := range defaultFields {
|
||||
fieldFormats = append(fieldFormats, &fieldFormat{name: field, width: 20, align: "-"})
|
||||
}
|
||||
} else {
|
||||
defaultFields := []string{
|
||||
models.HEADER_RESOURCE_NAME,
|
||||
models.HEADER_RESOURCE_VALUE,
|
||||
models.HEADER_RESOURCE_LOC,
|
||||
}
|
||||
for _, field := range defaultFields {
|
||||
fieldFormats = append(fieldFormats, &fieldFormat{name: field, width: 20, align: "-"})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// parse user specified fields
|
||||
for _, field := range strings.Split(p.fields, ",") {
|
||||
fieldFormats = append(fieldFormats, parseFieldFormat(field))
|
||||
}
|
||||
}
|
||||
return fieldFormats
|
||||
}
|
||||
|
||||
// init column widths for ResourceDistResponse
|
||||
func (p *ResourcesPagePrinter) initColumnWidthsForDist(resourcesDist *models.ResourceDistResponse) {
|
||||
// init column widths to header length
|
||||
for _, format := range p.fieldFormats {
|
||||
p.columnWidths[format.name] = len(format.name)
|
||||
if format.width > 0 {
|
||||
p.columnWidths[format.name] = format.width
|
||||
}
|
||||
}
|
||||
|
||||
// calculate the max width of the first page data
|
||||
for _, resourceDist := range resourcesDist.Data.Rows {
|
||||
for _, format := range p.fieldFormats {
|
||||
width := 15
|
||||
switch format.name {
|
||||
case models.HEADER_RESOURCE_NAME:
|
||||
width = len(resourceDist.ResourceName)
|
||||
case models.HEADER_RESOURCE_VALUE:
|
||||
width = len(resourceDist.ResourceValue.String())
|
||||
case models.HEADER_RESOURCE_LOC:
|
||||
width = len(resourceDist.ResourceLoc.String())
|
||||
}
|
||||
|
||||
if !p.wideFormat && width > 15 {
|
||||
width = 15
|
||||
}
|
||||
|
||||
if width > p.columnWidths[format.name] {
|
||||
p.columnWidths[format.name] = width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// init column widths for ResourceDefResponse
|
||||
func (p *ResourcesPagePrinter) initColumnWidthsForDef(resourcesDef *models.ResourceDefResponse) {
|
||||
// init column widths to header length
|
||||
for _, format := range p.fieldFormats {
|
||||
p.columnWidths[format.name] = len(format.name)
|
||||
if format.width > 0 {
|
||||
p.columnWidths[format.name] = format.width
|
||||
}
|
||||
}
|
||||
|
||||
// calculate the max width of the first page data
|
||||
for _, resourceDef := range resourcesDef.Data.Rows {
|
||||
for _, format := range p.fieldFormats {
|
||||
width := 15
|
||||
switch format.name {
|
||||
case models.HEADER_RESOURCE_NAME:
|
||||
width = len(resourceDef.ResourceName)
|
||||
case models.HEADER_RESOURCE_DEF_TYPE:
|
||||
width = len(resourceDef.ResourceType)
|
||||
case models.HEADER_RESOURCE_DEF_DESC:
|
||||
width = len(resourceDef.Description)
|
||||
}
|
||||
|
||||
if !p.wideFormat && width > 15 {
|
||||
width = 15
|
||||
}
|
||||
|
||||
if width > p.columnWidths[format.name] {
|
||||
p.columnWidths[format.name] = width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PrintPage supports both ResourceDistResponse and ResourceDefResponse
|
||||
func (p *ResourcesPagePrinter) PrintPage(page interface{}) {
|
||||
|
||||
p.pageCount++
|
||||
|
||||
if p.isDefinition {
|
||||
resourcesDef, ok := page.(*models.ResourceDefResponse)
|
||||
if !ok {
|
||||
fmt.Println("error: wrong data type to ResourcesPagePrinter")
|
||||
return
|
||||
}
|
||||
if p.verbose {
|
||||
printResourcesDefVerboseOutput(resourcesDef)
|
||||
return
|
||||
}
|
||||
}
|
||||
if p.formatJson {
|
||||
if p.isDefinition {
|
||||
if resourcesDef, ok := page.(*models.ResourceDefResponse); ok {
|
||||
p.printJsonPageForDef(resourcesDef)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if resourcesDist, ok := page.(*models.ResourceDistResponse); ok {
|
||||
p.printJsonPageForDist(resourcesDist)
|
||||
return
|
||||
}
|
||||
}
|
||||
fmt.Println("error: wrong data type for JSON printing")
|
||||
return
|
||||
} else {
|
||||
// print header (if needed)
|
||||
p.printHeader()
|
||||
|
||||
// print data rows
|
||||
var rows [][]string
|
||||
if p.isDefinition {
|
||||
if resourcesDef, ok := page.(*models.ResourceDefResponse); ok {
|
||||
rows = p.formatRowsForDef(resourcesDef, true)
|
||||
} else {
|
||||
fmt.Println("error: wrong data type to format rows")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if resourcesDist, ok := page.(*models.ResourceDistResponse); ok {
|
||||
rows = p.formatRowsForDist(resourcesDist, true)
|
||||
} else {
|
||||
fmt.Println("error: wrong data type to format rows")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
fmt.Fprintln(p.w, strings.Join(row, "\t"))
|
||||
}
|
||||
p.w.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// format data rows for ResourceDistResponse
|
||||
func (p *ResourcesPagePrinter) formatRowsForDist(resourcesDist *models.ResourceDistResponse, truncatFlag bool) [][]string {
|
||||
var rows [][]string
|
||||
if p.wideFormat {
|
||||
truncatFlag = false
|
||||
}
|
||||
for _, resourceDist := range resourcesDist.Data.Rows {
|
||||
row := make([]string, 0, len(p.fieldFormats))
|
||||
for _, format := range p.fieldFormats {
|
||||
width := p.columnWidths[format.name]
|
||||
switch format.name {
|
||||
case models.HEADER_RESOURCE_NAME:
|
||||
row = appendOrDefault(row, FormatStringField(resourceDist.ResourceName, width, truncatFlag, true), "-")
|
||||
case models.HEADER_RESOURCE_VALUE:
|
||||
row = appendOrDefault(row, FormatStringField(resourceDist.ResourceValue.String(), width, truncatFlag, true), "-")
|
||||
case models.HEADER_RESOURCE_LOC:
|
||||
row = appendOrDefault(row, FormatStringField(resourceDist.ResourceLoc.String(), width, truncatFlag, true), "-")
|
||||
}
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// format data rows for ResourceDefResponse
|
||||
func (p *ResourcesPagePrinter) formatRowsForDef(resourcesDef *models.ResourceDefResponse, truncatFlag bool) [][]string {
|
||||
var rows [][]string
|
||||
if p.wideFormat {
|
||||
truncatFlag = false
|
||||
}
|
||||
for _, resourceDef := range resourcesDef.Data.Rows {
|
||||
row := make([]string, 0, len(p.fieldFormats))
|
||||
for _, format := range p.fieldFormats {
|
||||
width := p.columnWidths[format.name]
|
||||
align := format.align
|
||||
switch format.name {
|
||||
case models.HEADER_RESOURCE_NAME:
|
||||
row = appendOrDefault(row, FormatStringField(resourceDef.ResourceName, width, truncatFlag, true), "-")
|
||||
case models.HEADER_RESOURCE_DEF_DESC:
|
||||
row = appendOrDefault(row, FormatStringField(resourceDef.Description, width, truncatFlag, true), "-")
|
||||
case models.HEADER_RESOURCE_DEF_TYPE:
|
||||
row = appendOrDefault(row, FormatStringField(resourceDef.ResourceType, width, truncatFlag, true), "-")
|
||||
case models.HEADER_RESOURCE_DEF_ORDER:
|
||||
row = appendOrDefault(row, FormatStringField(resourceDef.OrderType, width, truncatFlag, true), "-")
|
||||
case models.HEADER_RESOURCE_DEF_BUILTIN:
|
||||
// Extract builtin flag from the Flag object
|
||||
builtinValue := "false"
|
||||
if resourceDef.Flag != nil {
|
||||
if builtin, ok := resourceDef.Flag["builtin"]; ok {
|
||||
builtinValue = fmt.Sprintf("%v", builtin)
|
||||
}
|
||||
}
|
||||
row = appendOrDefault(row, FormatStringField(builtinValue, width, truncatFlag, true), "-")
|
||||
case models.HEADER_RESOURCE_DEF_INTERVAL:
|
||||
row = appendOrDefault(row, fmt.Sprintf("%"+align+"*d", width, resourceDef.Interval), "0")
|
||||
}
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// print JSON page for ResourceDistResponse
|
||||
func (p *ResourcesPagePrinter) printJsonPageForDist(resourcesDist *models.ResourceDistResponse) {
|
||||
// if the format is JSON, directly convert the current page to JSON
|
||||
headers := make([]string, len(p.fieldFormats))
|
||||
for i, format := range p.fieldFormats {
|
||||
headers[i] = format.name
|
||||
}
|
||||
|
||||
rows := p.formatRowsForDist(resourcesDist, false)
|
||||
p.PrintJsonPageGeneric(rows, headers)
|
||||
}
|
||||
|
||||
// print JSON page for ResourceDefResponse
|
||||
func (p *ResourcesPagePrinter) printJsonPageForDef(resourcesDef *models.ResourceDefResponse) {
|
||||
// if the format is JSON, directly convert the current page to JSON
|
||||
headers := make([]string, len(p.fieldFormats))
|
||||
for i, format := range p.fieldFormats {
|
||||
headers[i] = format.name
|
||||
}
|
||||
|
||||
rows := p.formatRowsForDef(resourcesDef, false)
|
||||
p.PrintJsonPageGeneric(rows, headers)
|
||||
}
|
||||
|
||||
func printResourcesDefVerboseOutput(resourceDef *models.ResourceDefResponse) {
|
||||
for _, resourceDef := range resourceDef.Data.Rows {
|
||||
fmt.Printf("RESOURCE: %s\n", resourceDef.ResourceName)
|
||||
fmt.Printf("DESCRIPTION: %s\n", resourceDef.Description)
|
||||
// table 1
|
||||
headers := models.ResourceDefDetailHeaders
|
||||
row1 := make([]string, 0, len(headers))
|
||||
widths1 := make([]int, len(headers))
|
||||
for i, h := range headers {
|
||||
var val string
|
||||
switch h {
|
||||
case models.HEADER_RESOURCE_DEF_TYPE:
|
||||
val = resourceDef.ResourceType
|
||||
case models.HEADER_RESOURCE_DEF_ORDER:
|
||||
val = resourceDef.OrderType
|
||||
case models.HEADER_RESOURCE_DEF_BUILTIN:
|
||||
// Extract builtin flag from the Flag object
|
||||
builtinValue := "false"
|
||||
if resourceDef.Flag != nil {
|
||||
if builtin, ok := resourceDef.Flag["builtin"]; ok {
|
||||
builtinValue = fmt.Sprintf("%v", builtin)
|
||||
}
|
||||
}
|
||||
val = builtinValue
|
||||
case models.HEADER_RESOURCE_DEF_CONSUMABLE:
|
||||
// Extract consumable flag from the Flag object
|
||||
consumableValue := "false"
|
||||
if resourceDef.Flag != nil {
|
||||
if consumable, ok := resourceDef.Flag["consumable"]; ok {
|
||||
consumableValue = fmt.Sprintf("%v", consumable)
|
||||
}
|
||||
}
|
||||
val = consumableValue
|
||||
case models.HEADER_RESOURCE_DEF_DYNAMIC:
|
||||
// Extract dynamic flag from the Flag object
|
||||
dynamicValue := "false"
|
||||
if resourceDef.Flag != nil {
|
||||
if dynamic, ok := resourceDef.Flag["dynamic"]; ok {
|
||||
dynamicValue = fmt.Sprintf("%v", dynamic)
|
||||
}
|
||||
}
|
||||
val = dynamicValue
|
||||
case models.HEADER_RESOURCE_DEF_RELEASE:
|
||||
// Extract release flag from the Flag object
|
||||
releaseValue := "false"
|
||||
if resourceDef.Flag != nil {
|
||||
if release, ok := resourceDef.Flag["release"]; ok {
|
||||
releaseValue = fmt.Sprintf("%v", release)
|
||||
}
|
||||
}
|
||||
val = releaseValue
|
||||
case models.HEADER_RESOURCE_DEF_INTERVAL:
|
||||
val = fmt.Sprintf("%d", resourceDef.Interval)
|
||||
}
|
||||
widths1[i] = max(len(h), len(val))
|
||||
row1 = append(row1, FormatStringField(val, widths1[i], false, true))
|
||||
}
|
||||
tp1 := TablePrinter{Headers: headers, Rows: [][]string{row1}, ColWidths: widths1}
|
||||
tp1.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
}
|
||||
191
src/frontend/xcecli/common/output/page_print_roles.go
Normal file
191
src/frontend/xcecli/common/output/page_print_roles.go
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
package output
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"xcecli/models"
|
||||
)
|
||||
|
||||
// HostsPagePrinter
|
||||
type RolesPagePrinter struct {
|
||||
*BasePagePrinter
|
||||
}
|
||||
|
||||
// NewHostsPagePrinter
|
||||
func NewRolesPagePrinter(opt ...BasePageOption) *RolesPagePrinter {
|
||||
return &RolesPagePrinter{
|
||||
BasePagePrinter: newBasePagePrinter(opt...),
|
||||
}
|
||||
}
|
||||
|
||||
// PrepareForPrinting
|
||||
func (p *RolesPagePrinter) PrepareForPrinting(firstPage interface{}) {
|
||||
roles, ok := firstPage.(*models.RolesResponse)
|
||||
if !ok {
|
||||
fmt.Println("error: wrong data type to RolesPagePrinter")
|
||||
return
|
||||
}
|
||||
|
||||
// set field formats
|
||||
p.fieldFormats = p.prepareFieldFormats()
|
||||
|
||||
// init column widths
|
||||
p.initColumnWidths(roles)
|
||||
|
||||
// prepare headers
|
||||
p.prepareHeaders()
|
||||
}
|
||||
|
||||
// prepare field formats
|
||||
func (p *RolesPagePrinter) prepareFieldFormats() []*fieldFormat {
|
||||
// here define the default fields based on the host data
|
||||
var fieldFormats []*fieldFormat
|
||||
if p.fields == "" {
|
||||
// default fields
|
||||
defaultFields := []string{
|
||||
models.HEADER_ROLE_NAME,
|
||||
models.HEADER_ROLE_PERM_NAME,
|
||||
models.HEADER_ROLE_DOMAIN_TYPE,
|
||||
models.HEADER_ROLE_DOMAIN_VALUES,
|
||||
}
|
||||
for _, field := range defaultFields {
|
||||
fieldFormats = append(fieldFormats, &fieldFormat{name: field, align: "-"})
|
||||
}
|
||||
} else {
|
||||
// parse user specified fields
|
||||
for _, field := range strings.Split(p.fields, ",") {
|
||||
fieldFormats = append(fieldFormats, parseFieldFormat(field))
|
||||
}
|
||||
}
|
||||
return fieldFormats
|
||||
}
|
||||
|
||||
// init column widths
|
||||
func (p *RolesPagePrinter) initColumnWidths(roles *models.RolesResponse) {
|
||||
// init column widths to header length
|
||||
for _, format := range p.fieldFormats {
|
||||
p.columnWidths[format.name] = len(format.name)
|
||||
if format.width > 0 {
|
||||
p.columnWidths[format.name] = format.width
|
||||
}
|
||||
}
|
||||
|
||||
// calculate the max width of the first page data
|
||||
for _, role := range roles.Data.Rows {
|
||||
for _, format := range p.fieldFormats {
|
||||
width := 0
|
||||
switch format.name {
|
||||
case models.HEADER_ROLE_NAME:
|
||||
width = len(role.RoleName)
|
||||
case models.HEADER_ROLE_PERM_NAME:
|
||||
width = len(role.PermName)
|
||||
case models.HEADER_ROLE_DOMAIN_TYPE:
|
||||
width = len(role.DomainType)
|
||||
}
|
||||
if !p.wideFormat && width > 15 {
|
||||
width = 15
|
||||
}
|
||||
|
||||
if width > p.columnWidths[format.name] && format.width == 0 {
|
||||
p.columnWidths[format.name] = width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PrintPage
|
||||
func (p *RolesPagePrinter) PrintPage(page interface{}) {
|
||||
roles, ok := page.(*models.RolesResponse)
|
||||
if !ok {
|
||||
fmt.Println("error: wrong data type to RolesPagePrinter")
|
||||
return
|
||||
}
|
||||
|
||||
p.pageCount++
|
||||
|
||||
if p.verbose {
|
||||
printRolesVerboseOutput(roles)
|
||||
return
|
||||
}
|
||||
|
||||
if p.formatJson {
|
||||
p.printJsonPage(roles)
|
||||
return
|
||||
} else {
|
||||
// print header (if needed)
|
||||
p.printHeader()
|
||||
// print data rows
|
||||
rows := p.formatRows(roles, true)
|
||||
for _, row := range rows {
|
||||
fmt.Fprintln(p.w, strings.Join(row, "\t"))
|
||||
}
|
||||
p.w.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// format data rows
|
||||
func (p *RolesPagePrinter) formatRows(roles *models.RolesResponse, truncatFlag bool) [][]string {
|
||||
var rows [][]string
|
||||
if p.wideFormat {
|
||||
truncatFlag = false
|
||||
}
|
||||
|
||||
for _, role := range roles.Data.Rows {
|
||||
// If role has no permissions, create one row with empty permission fields
|
||||
|
||||
row := make([]string, 0, len(p.fieldFormats))
|
||||
for _, format := range p.fieldFormats {
|
||||
width := p.columnWidths[format.name]
|
||||
switch format.name {
|
||||
case models.HEADER_ROLE_NAME:
|
||||
row = appendOrDefault(row, FormatStringField(role.RoleName, width, truncatFlag, true), "-")
|
||||
case models.HEADER_ROLE_PERM_NAME:
|
||||
row = appendOrDefault(row, FormatStringField(role.PermName, width, truncatFlag, true), "-")
|
||||
case models.HEADER_ROLE_DOMAIN_TYPE:
|
||||
row = appendOrDefault(row, FormatStringField(role.DomainType, width, truncatFlag, true), "-")
|
||||
case models.HEADER_ROLE_DOMAIN_VALUES:
|
||||
domainValues := "-"
|
||||
if role.DomainValues != nil {
|
||||
// Handle as []string since that's what the API returns
|
||||
if vals, ok := role.DomainValues.([]string); ok && len(vals) > 0 {
|
||||
domainValues = strings.Join(vals, ", ")
|
||||
} else if vals, ok := role.DomainValues.([]interface{}); ok && len(vals) > 0 {
|
||||
// Alternative: handle as []interface{} and convert to strings
|
||||
strVals := make([]string, len(vals))
|
||||
for i, v := range vals {
|
||||
strVals[i] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
domainValues = strings.Join(strVals, ", ")
|
||||
}
|
||||
}
|
||||
row = appendOrDefault(row, FormatStringField(domainValues, width, truncatFlag, true), "-")
|
||||
}
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// print JSON page
|
||||
func (p *RolesPagePrinter) printJsonPage(roles *models.RolesResponse) {
|
||||
// if the format is JSON, directly convert the current page to JSON
|
||||
headers := make([]string, len(p.fieldFormats))
|
||||
for i, format := range p.fieldFormats {
|
||||
headers[i] = format.name
|
||||
}
|
||||
|
||||
rows := p.formatRows(roles, false)
|
||||
p.PrintJsonPageGeneric(rows, headers)
|
||||
}
|
||||
|
||||
func printRolesVerboseOutput(roles *models.RolesResponse) {
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
||||
defer w.Flush()
|
||||
|
||||
for _, role := range roles.Data.Rows {
|
||||
fmt.Printf("Role: %s\n", role.RoleName)
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
146
src/frontend/xcecli/common/output/page_print_users.go
Normal file
146
src/frontend/xcecli/common/output/page_print_users.go
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
package output
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"xcecli/models"
|
||||
)
|
||||
|
||||
type UserRolesPagePrinter struct {
|
||||
*BasePagePrinter
|
||||
}
|
||||
|
||||
func NewUserRolesPagePrinter(opt ...BasePageOption) *UserRolesPagePrinter {
|
||||
return &UserRolesPagePrinter{
|
||||
BasePagePrinter: newBasePagePrinter(opt...),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *UserRolesPagePrinter) PrepareForPrinting(firstPage interface{}) {
|
||||
userroles, ok := firstPage.(*models.UserRolesResponse)
|
||||
if !ok {
|
||||
fmt.Println("error: wrong data type to UserRolesPagePrinter")
|
||||
return
|
||||
}
|
||||
|
||||
// set field formats
|
||||
p.fieldFormats = p.prepareFieldFormats()
|
||||
|
||||
// init column widths
|
||||
p.initColumnWidths(userroles)
|
||||
|
||||
// prepare headers
|
||||
p.prepareHeaders()
|
||||
}
|
||||
|
||||
// prepare field formats
|
||||
func (p *UserRolesPagePrinter) prepareFieldFormats() []*fieldFormat {
|
||||
var fieldFormats []*fieldFormat
|
||||
if p.fields == "" {
|
||||
// default fields
|
||||
defaultFields := []string{
|
||||
models.HEADER_USER_NAME,
|
||||
models.HEADER_USERROLE_NAME,
|
||||
}
|
||||
for _, field := range defaultFields {
|
||||
fieldFormats = append(fieldFormats, &fieldFormat{name: field, align: "-"})
|
||||
}
|
||||
} else {
|
||||
// parse user specified fields
|
||||
for _, field := range strings.Split(p.fields, ",") {
|
||||
fieldFormats = append(fieldFormats, parseFieldFormat(field))
|
||||
}
|
||||
}
|
||||
return fieldFormats
|
||||
}
|
||||
|
||||
// init column widths
|
||||
func (p *UserRolesPagePrinter) initColumnWidths(users *models.UserRolesResponse) {
|
||||
// init column widths to header length
|
||||
for _, format := range p.fieldFormats {
|
||||
p.columnWidths[format.name] = len(format.name)
|
||||
if format.width > 0 {
|
||||
p.columnWidths[format.name] = format.width
|
||||
}
|
||||
}
|
||||
|
||||
// calculate the max width of the first page data
|
||||
for _, user := range users.Data.Rows {
|
||||
for _, format := range p.fieldFormats {
|
||||
width := 0
|
||||
switch format.name {
|
||||
case models.HEADER_USER_NAME:
|
||||
width = len(user.UserName)
|
||||
case models.HEADER_USERROLE_NAME:
|
||||
width = len(user.UserRoleName)
|
||||
}
|
||||
if !p.wideFormat && width > 15 {
|
||||
width = 15
|
||||
}
|
||||
|
||||
if width > p.columnWidths[format.name] && format.width == 0 {
|
||||
p.columnWidths[format.name] = width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PrintPage
|
||||
func (p *UserRolesPagePrinter) PrintPage(page interface{}) {
|
||||
userroles, ok := page.(*models.UserRolesResponse)
|
||||
if !ok {
|
||||
fmt.Println("error: wrong data type to UserRolesPagePrinter")
|
||||
return
|
||||
}
|
||||
|
||||
p.pageCount++
|
||||
|
||||
if p.formatJson {
|
||||
p.printJsonPage(userroles)
|
||||
return
|
||||
} else {
|
||||
// print header (if needed)
|
||||
p.printHeader()
|
||||
// print data rows
|
||||
rows := p.formatRows(userroles, true)
|
||||
for _, row := range rows {
|
||||
fmt.Fprintln(p.w, strings.Join(row, "\t"))
|
||||
}
|
||||
p.w.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
// format data rows
|
||||
func (p *UserRolesPagePrinter) formatRows(userroles *models.UserRolesResponse, truncatFlag bool) [][]string {
|
||||
var rows [][]string
|
||||
if p.wideFormat {
|
||||
truncatFlag = false
|
||||
}
|
||||
|
||||
for _, user := range userroles.Data.Rows {
|
||||
row := make([]string, 0, len(p.fieldFormats))
|
||||
for _, format := range p.fieldFormats {
|
||||
width := p.columnWidths[format.name]
|
||||
switch format.name {
|
||||
case models.HEADER_USER_NAME:
|
||||
row = appendOrDefault(row, FormatStringField(user.UserName, width, truncatFlag, true), "-")
|
||||
case models.HEADER_USERROLE_NAME:
|
||||
row = appendOrDefault(row, FormatStringField(user.UserRoleName, width, truncatFlag, true), "-")
|
||||
}
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// print JSON page
|
||||
func (p *UserRolesPagePrinter) printJsonPage(userroles *models.UserRolesResponse) {
|
||||
// if the format is JSON, directly convert the current page to JSON
|
||||
headers := make([]string, len(p.fieldFormats))
|
||||
for i, format := range p.fieldFormats {
|
||||
headers[i] = format.name
|
||||
}
|
||||
|
||||
rows := p.formatRows(userroles, false)
|
||||
p.PrintJsonPageGeneric(rows, headers)
|
||||
}
|
||||
24
src/frontend/xcecli/common/output/time.go
Normal file
24
src/frontend/xcecli/common/output/time.go
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package output
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// parseJobTime parses a job time string supporting RFC3339 and space-separated formats.
|
||||
func parseJobTime(s string) (time.Time, error) {
|
||||
if s == "" {
|
||||
return time.Time{}, fmt.Errorf("empty time string")
|
||||
}
|
||||
for _, f := range []string{time.RFC3339Nano, time.RFC3339, "2006-01-02 15:04:05", "2006-01-02 15:04:05 MST"} {
|
||||
if t, err := time.Parse(f, s); err == nil {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
return time.Time{}, fmt.Errorf("cannot parse time: %q", s)
|
||||
}
|
||||
|
||||
// formatJobTime formats a time.Time as "Jan 02 15:04" for table display.
|
||||
func formatJobTime(t time.Time) string {
|
||||
return fmt.Sprintf("%s %02d %02d:%02d", t.Format("Jan"), t.Day(), t.Hour(), t.Minute())
|
||||
}
|
||||
39
src/frontend/xcecli/common/output/time_test.go
Normal file
39
src/frontend/xcecli/common/output/time_test.go
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package output
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestParseJobTime_RFC3339(t *testing.T) {
|
||||
got, err := parseJobTime("2026-04-08T17:00:10.159507024+08:00")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.Year() != 2026 || got.Month() != time.April || got.Day() != 8 {
|
||||
t.Errorf("wrong date: %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseJobTime_SpaceFormat(t *testing.T) {
|
||||
got, err := parseJobTime("2026-04-08 17:00:10")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.Hour() != 17 {
|
||||
t.Errorf("wrong hour: %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseJobTime_Empty(t *testing.T) {
|
||||
if _, err := parseJobTime(""); err == nil {
|
||||
t.Error("expected error for empty string")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormatJobTime(t *testing.T) {
|
||||
tm := time.Date(2026, time.April, 8, 17, 0, 10, 0, time.UTC)
|
||||
if got := formatJobTime(tm); got != "Apr 08 17:00" {
|
||||
t.Errorf("got %q want %q", got, "Apr 08 17:00")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue