Add API, CLI and web UI integration for objects

This change adds the API endpoints, the CLI commands and the web UI elements
needed to manage objects in GARMs internal storage.

This storage system is meant to be used to distribute the garm-agent and as a
single source of truth for provider binaries, when we will add the ability for GARM
to scale out.

Potentially, we can also use this in air gapped systems to distribute the runner binaries
for forges that don't have their own internal storage system (like GHES).

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
This commit is contained in:
Gabriel Adrian Samfira 2025-10-07 17:07:40 +00:00 committed by Gabriel
parent f66f95baff
commit 6c46cf9be1
138 changed files with 7911 additions and 267 deletions

View file

@ -0,0 +1,338 @@
// Copyright 2025 Cloudbase Solutions SRL
//
// 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 controllers
import (
"encoding/json"
"fmt"
"io"
"log/slog"
"math"
"net/http"
"strconv"
"strings"
"github.com/gorilla/mux"
gErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/params"
)
// swagger:route GET /objects objects ListFileObjects
//
// List file objects.
//
// Parameters:
// + name: tags
// description: List of tags to filter by.
// type: array
// items:
// type: string
// in: query
// required: false
// + name: page
// description: The page at which to list.
// type: integer
// in: query
// required: false
// + name: pageSize
// description: Number of items per page.
// type: integer
// in: query
// required: false
//
// Responses:
// 200: FileObjectPaginatedResponse
// 400: APIErrorResponse
func (a *APIController) ListFileObjects(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var pageLocation int64
var pageSize int64 = 25
tags := r.URL.Query().Get("tags")
pageArg := r.URL.Query().Get("page")
pageSizeArg := r.URL.Query().Get("pageSize")
if pageArg != "" {
pageInt, err := strconv.ParseInt(pageArg, 10, 64)
if err == nil && pageInt >= 0 {
pageLocation = pageInt
}
}
if pageSizeArg != "" {
pageSizeInt, err := strconv.ParseInt(pageSizeArg, 10, 64)
if err == nil && pageSizeInt >= 0 {
pageSize = pageSizeInt
}
}
parsedTags := parseTagsArg(tags)
files, err := a.r.ListFileObjects(ctx, uint64(pageLocation), uint64(pageSize), parsedTags)
if err != nil {
handleError(ctx, w, err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(files); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
}
// swagger:route DELETE /objects/{objectID} objects DeleteFileObject
//
// Delete a file object.
//
// Parameters:
// + name: objectID
// description: The ID of the file object.
// type: string
// in: path
// required: true
//
// Responses:
// default: APIErrorResponse
func (a *APIController) DeleteFileObject(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
objectID, err := getObjectIDFromVars(vars)
if err != nil {
slog.ErrorContext(ctx, "failed to get object ID", "error", err)
handleError(ctx, w, gErrors.NewBadRequestError("invalid objectID: %s", err))
return
}
if err := a.r.DeleteFileObject(ctx, objectID); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to delete file object")
handleError(ctx, w, err)
return
}
w.WriteHeader(http.StatusNoContent)
}
// swagger:route GET /objects/{objectID} objects GetFileObject
//
// Get a file object.
//
// Parameters:
// + name: objectID
// description: The ID of the file object.
// type: string
// in: path
// required: true
//
// Responses:
// 200: FileObject
// 400: APIErrorResponse
func (a *APIController) GetFileObject(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
objectID, err := getObjectIDFromVars(vars)
if err != nil {
slog.ErrorContext(ctx, "failed to get object ID", "error", err)
handleError(ctx, w, gErrors.NewBadRequestError("invalid objectID: %s", err))
return
}
file, err := a.r.GetFileObject(ctx, objectID)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to get file object")
handleError(ctx, w, err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(file); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
}
// swagger:route PUT /objects/{objectID} objects UpdateFileObject
//
// Update a file object.
//
// Parameters:
// + name: objectID
// description: The ID of the file object.
// type: string
// in: path
// required: true
// + name: Body
// description: Parameters used when updating a file object.
// type: UpdateFileObjectParams
// in: body
// required: true
//
// Responses:
// 200: FileObject
// 400: APIErrorResponse
func (a *APIController) UpdateFileObject(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
objectID, err := getObjectIDFromVars(vars)
if err != nil {
slog.ErrorContext(ctx, "failed to get object ID", "error", err)
handleError(ctx, w, gErrors.NewBadRequestError("invalid objectID: %s", err))
return
}
var param params.UpdateFileObjectParams
if err := json.NewDecoder(r.Body).Decode(&param); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to decode request")
handleError(ctx, w, gErrors.ErrBadRequest)
return
}
if len(param.Tags) > 0 {
for idx, val := range param.Tags {
param.Tags[idx] = strings.ToLower(strings.TrimSpace(val))
}
}
file, err := a.r.UpdateFileObject(ctx, objectID, param)
if err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to get file object")
handleError(ctx, w, err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(file); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
}
func (a *APIController) CreateFileObject(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
ctx := r.Context()
fileName := r.Header.Get("X-File-Name")
if fileName == "" {
handleError(ctx, w, gErrors.NewBadRequestError("missing X-File-Name header"))
return
}
description := r.Header.Get("X-File-Description")
contentLengthStr := r.Header.Get("Content-Length")
if contentLengthStr == "" {
handleError(ctx, w, gErrors.NewBadRequestError("missing Content-Length header in request"))
return
}
tags := r.Header.Get("X-Tags")
parsedTags := parseTagsArg(tags)
fileSize, err := strconv.ParseInt(contentLengthStr, 10, 64)
if err != nil {
handleError(ctx, w, gErrors.NewBadRequestError("invalid Content-Length"))
return
}
param := params.CreateFileObjectParams{
Name: fileName,
Size: fileSize,
Tags: parsedTags,
}
if len(description) > 0 {
param.Description = description
}
fileObj, err := a.r.CreateFileObject(ctx, param, r.Body)
if err != nil {
slog.ErrorContext(ctx, "failed to create blob", "error", err)
handleError(ctx, w, err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(fileObj); err != nil {
slog.With(slog.Any("error", err)).ErrorContext(ctx, "failed to encode response")
}
}
func (a *APIController) DownloadFileObject(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)
objectID, err := getObjectIDFromVars(vars)
if err != nil {
slog.ErrorContext(ctx, "failed to get object ID", "error", err)
handleError(ctx, w, gErrors.NewBadRequestError("invalid objectID: %s", err))
return
}
objectDetails, err := a.r.GetFileObject(ctx, objectID)
if err != nil {
handleError(ctx, w, err)
return
}
objectHandle, err := a.r.GetFileObjectReader(ctx, objectID)
if err != nil {
handleError(ctx, w, err)
return
}
defer objectHandle.Close()
w.Header().Set("Content-Type", objectDetails.FileType)
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", objectDetails.Name))
w.Header().Set("Content-Length", strconv.FormatInt(objectDetails.Size, 10))
if r.Method == http.MethodHead {
return
}
copied, err := io.Copy(w, objectHandle)
if err != nil {
slog.ErrorContext(ctx, "failed to stream data", "error", err)
}
if copied < objectDetails.Size {
slog.WarnContext(ctx, "some data was not streamed", "object_id", objectDetails.ID, "object_size", objectDetails.Size, "streamed_bytes", copied)
}
}
func parseTagsArg(tags string) []string {
var parsedTags []string
foundTag := make(map[string]struct{})
if tags != "" {
tagList := strings.SplitSeq(tags, ",")
for val := range tagList {
if val == "" {
continue
}
low := strings.ToLower(strings.TrimSpace(val))
if _, ok := foundTag[low]; ok {
continue
}
parsedTags = append(parsedTags, low)
foundTag[low] = struct{}{}
}
}
return parsedTags
}
func parseAsUint(val string) (uint, error) {
parsedObjID, err := strconv.ParseUint(val, 10, 64)
if err != nil {
return 0, fmt.Errorf("invalid object ID; must be a number")
}
if parsedObjID > math.MaxUint {
return 0, fmt.Errorf("the object ID is too large")
}
return uint(parsedObjID), nil
}
func getObjectIDFromVars(vars map[string]string) (uint, error) {
objectID, ok := vars["objectID"]
if !ok {
return 0, fmt.Errorf("no objectID specified")
}
return parseAsUint(objectID)
}

View file

@ -216,6 +216,28 @@ func NewAPIRouter(han *controllers.APIController, authMiddleware, initMiddleware
apiRouter.Handle("/metrics-token/", http.HandlerFunc(han.MetricsTokenHandler)).Methods("GET", "OPTIONS")
apiRouter.Handle("/metrics-token", http.HandlerFunc(han.MetricsTokenHandler)).Methods("GET", "OPTIONS")
/////////////
// Objects //
/////////////
// List objects
apiRouter.Handle("/objects/", http.HandlerFunc(han.ListFileObjects)).Methods("GET", "OPTIONS")
apiRouter.Handle("/objects", http.HandlerFunc(han.ListFileObjects)).Methods("GET", "OPTIONS")
// Create object
apiRouter.Handle("/objects/", http.HandlerFunc(han.CreateFileObject)).Methods("POST", "OPTIONS")
apiRouter.Handle("/objects", http.HandlerFunc(han.CreateFileObject)).Methods("POST", "OPTIONS")
// Delete object
apiRouter.Handle("/objects/{objectID}/", http.HandlerFunc(han.DeleteFileObject)).Methods("DELETE", "OPTIONS")
apiRouter.Handle("/objects/{objectID}", http.HandlerFunc(han.DeleteFileObject)).Methods("DELETE", "OPTIONS")
// Download object
apiRouter.Handle("/objects/{objectID}/download/", http.HandlerFunc(han.DownloadFileObject)).Methods("GET", "OPTIONS", "HEAD")
apiRouter.Handle("/objects/{objectID}/download", http.HandlerFunc(han.DownloadFileObject)).Methods("GET", "OPTIONS", "HEAD")
// Get object
apiRouter.Handle("/objects/{objectID}/", http.HandlerFunc(han.GetFileObject)).Methods("GET", "OPTIONS")
apiRouter.Handle("/objects/{objectID}", http.HandlerFunc(han.GetFileObject)).Methods("GET", "OPTIONS")
// Update object
apiRouter.Handle("/objects/{objectID}/", http.HandlerFunc(han.UpdateFileObject)).Methods("PUT", "OPTIONS")
apiRouter.Handle("/objects/{objectID}", http.HandlerFunc(han.UpdateFileObject)).Methods("PUT", "OPTIONS")
//////////
// Jobs //
//////////

View file

@ -364,6 +364,34 @@ definitions:
import:
package: github.com/cloudbase/garm/params
alias: garm_params
FileObjectPaginatedResponse:
type: object
x-go-type:
type: FileObjectPaginatedResponse
import:
package: github.com/cloudbase/garm/params
alias: garm_params
FileObject:
type: object
x-go-type:
type: FileObject
import:
package: github.com/cloudbase/garm/params
alias: garm_params
UpdateFileObjectParams:
type: object
x-go-type:
type: UpdateFileObjectParams
import:
package: github.com/cloudbase/garm/params
alias: garm_params
CreateFileObjectParams:
type: object
x-go-type:
type: CreateFileObjectParams
import:
package: github.com/cloudbase/garm/params
alias: garm_params
Templates:
type: array
x-go-type:

View file

@ -23,6 +23,13 @@ definitions:
alias: garm_params
package: github.com/cloudbase/garm/params
type: CreateEnterpriseParams
CreateFileObjectParams:
type: object
x-go-type:
import:
alias: garm_params
package: github.com/cloudbase/garm/params
type: CreateFileObjectParams
CreateGiteaCredentialsParams:
type: object
x-go-type:
@ -118,6 +125,20 @@ definitions:
alias: garm_params
package: github.com/cloudbase/garm/params
type: Enterprises
FileObject:
type: object
x-go-type:
import:
alias: garm_params
package: github.com/cloudbase/garm/params
type: FileObject
FileObjectPaginatedResponse:
type: object
x-go-type:
import:
alias: garm_params
package: github.com/cloudbase/garm/params
type: FileObjectPaginatedResponse
ForgeCredentials:
type: object
x-go-type:
@ -318,6 +339,13 @@ definitions:
alias: garm_params
package: github.com/cloudbase/garm/params
type: UpdateEntityParams
UpdateFileObjectParams:
type: object
x-go-type:
import:
alias: garm_params
package: github.com/cloudbase/garm/params
type: UpdateFileObjectParams
UpdateGiteaCredentialsParams:
type: object
x-go-type:
@ -1290,6 +1318,99 @@ paths:
summary: Returns a JWT token that can be used to access the metrics endpoint.
tags:
- metrics-token
/objects:
get:
operationId: ListFileObjects
parameters:
- description: List of tags to filter by.
in: query
name: tags
type: string
- description: The page at which to list.
in: query
name: page
type: integer
- description: Number of items per page.
in: query
name: pageSize
type: integer
responses:
"200":
description: FileObjectPaginatedResponse
schema:
$ref: '#/definitions/FileObjectPaginatedResponse'
"400":
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: List file objects.
tags:
- objects
/objects/{objectID}:
delete:
operationId: DeleteFileObject
parameters:
- description: The ID of the file object.
in: path
name: objectID
required: true
type: string
responses:
default:
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: Delete a file object.
tags:
- objects
get:
operationId: GetFileObject
parameters:
- description: The ID of the file object.
in: path
name: objectID
required: true
type: string
responses:
"200":
description: FileObject
schema:
$ref: '#/definitions/FileObject'
"400":
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: Get a file object.
tags:
- objects
put:
operationId: UpdateFileObject
parameters:
- description: The ID of the file object.
in: path
name: objectID
required: true
type: string
- description: Parameters used when updating a file object.
in: body
name: Body
required: true
schema:
$ref: '#/definitions/UpdateFileObjectParams'
description: Parameters used when updating a file object.
type: object
responses:
"200":
description: FileObject
schema:
$ref: '#/definitions/FileObject'
"400":
description: APIErrorResponse
schema:
$ref: '#/definitions/APIErrorResponse'
summary: Update a file object.
tags:
- objects
/organizations:
get:
operationId: ListOrgs

View file

@ -20,6 +20,7 @@ import (
"github.com/cloudbase/garm/client/jobs"
"github.com/cloudbase/garm/client/login"
"github.com/cloudbase/garm/client/metrics_token"
"github.com/cloudbase/garm/client/objects"
"github.com/cloudbase/garm/client/organizations"
"github.com/cloudbase/garm/client/pools"
"github.com/cloudbase/garm/client/providers"
@ -80,6 +81,7 @@ func New(transport runtime.ClientTransport, formats strfmt.Registry) *GarmAPI {
cli.Jobs = jobs.New(transport, formats)
cli.Login = login.New(transport, formats)
cli.MetricsToken = metrics_token.New(transport, formats)
cli.Objects = objects.New(transport, formats)
cli.Organizations = organizations.New(transport, formats)
cli.Pools = pools.New(transport, formats)
cli.Providers = providers.New(transport, formats)
@ -150,6 +152,8 @@ type GarmAPI struct {
MetricsToken metrics_token.ClientService
Objects objects.ClientService
Organizations organizations.ClientService
Pools pools.ClientService
@ -178,6 +182,7 @@ func (c *GarmAPI) SetTransport(transport runtime.ClientTransport) {
c.Jobs.SetTransport(transport)
c.Login.SetTransport(transport)
c.MetricsToken.SetTransport(transport)
c.Objects.SetTransport(transport)
c.Organizations.SetTransport(transport)
c.Pools.SetTransport(transport)
c.Providers.SetTransport(transport)

View file

@ -0,0 +1,151 @@
// Code generated by go-swagger; DO NOT EDIT.
package objects
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
"time"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
)
// NewDeleteFileObjectParams creates a new DeleteFileObjectParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewDeleteFileObjectParams() *DeleteFileObjectParams {
return &DeleteFileObjectParams{
timeout: cr.DefaultTimeout,
}
}
// NewDeleteFileObjectParamsWithTimeout creates a new DeleteFileObjectParams object
// with the ability to set a timeout on a request.
func NewDeleteFileObjectParamsWithTimeout(timeout time.Duration) *DeleteFileObjectParams {
return &DeleteFileObjectParams{
timeout: timeout,
}
}
// NewDeleteFileObjectParamsWithContext creates a new DeleteFileObjectParams object
// with the ability to set a context for a request.
func NewDeleteFileObjectParamsWithContext(ctx context.Context) *DeleteFileObjectParams {
return &DeleteFileObjectParams{
Context: ctx,
}
}
// NewDeleteFileObjectParamsWithHTTPClient creates a new DeleteFileObjectParams object
// with the ability to set a custom HTTPClient for a request.
func NewDeleteFileObjectParamsWithHTTPClient(client *http.Client) *DeleteFileObjectParams {
return &DeleteFileObjectParams{
HTTPClient: client,
}
}
/*
DeleteFileObjectParams contains all the parameters to send to the API endpoint
for the delete file object operation.
Typically these are written to a http.Request.
*/
type DeleteFileObjectParams struct {
/* ObjectID.
The ID of the file object.
*/
ObjectID string
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the delete file object params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *DeleteFileObjectParams) WithDefaults() *DeleteFileObjectParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the delete file object params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *DeleteFileObjectParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the delete file object params
func (o *DeleteFileObjectParams) WithTimeout(timeout time.Duration) *DeleteFileObjectParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the delete file object params
func (o *DeleteFileObjectParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the delete file object params
func (o *DeleteFileObjectParams) WithContext(ctx context.Context) *DeleteFileObjectParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the delete file object params
func (o *DeleteFileObjectParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the delete file object params
func (o *DeleteFileObjectParams) WithHTTPClient(client *http.Client) *DeleteFileObjectParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the delete file object params
func (o *DeleteFileObjectParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithObjectID adds the objectID to the delete file object params
func (o *DeleteFileObjectParams) WithObjectID(objectID string) *DeleteFileObjectParams {
o.SetObjectID(objectID)
return o
}
// SetObjectID adds the objectId to the delete file object params
func (o *DeleteFileObjectParams) SetObjectID(objectID string) {
o.ObjectID = objectID
}
// WriteToRequest writes these params to a swagger request
func (o *DeleteFileObjectParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
// path param objectID
if err := r.SetPathParam("objectID", o.ObjectID); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View file

@ -0,0 +1,106 @@
// Code generated by go-swagger; DO NOT EDIT.
package objects
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
"fmt"
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
apiserver_params "github.com/cloudbase/garm/apiserver/params"
)
// DeleteFileObjectReader is a Reader for the DeleteFileObject structure.
type DeleteFileObjectReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *DeleteFileObjectReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
result := NewDeleteFileObjectDefault(response.Code())
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
if response.Code()/100 == 2 {
return result, nil
}
return nil, result
}
// NewDeleteFileObjectDefault creates a DeleteFileObjectDefault with default headers values
func NewDeleteFileObjectDefault(code int) *DeleteFileObjectDefault {
return &DeleteFileObjectDefault{
_statusCode: code,
}
}
/*
DeleteFileObjectDefault describes a response with status code -1, with default header values.
APIErrorResponse
*/
type DeleteFileObjectDefault struct {
_statusCode int
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this delete file object default response has a 2xx status code
func (o *DeleteFileObjectDefault) IsSuccess() bool {
return o._statusCode/100 == 2
}
// IsRedirect returns true when this delete file object default response has a 3xx status code
func (o *DeleteFileObjectDefault) IsRedirect() bool {
return o._statusCode/100 == 3
}
// IsClientError returns true when this delete file object default response has a 4xx status code
func (o *DeleteFileObjectDefault) IsClientError() bool {
return o._statusCode/100 == 4
}
// IsServerError returns true when this delete file object default response has a 5xx status code
func (o *DeleteFileObjectDefault) IsServerError() bool {
return o._statusCode/100 == 5
}
// IsCode returns true when this delete file object default response a status code equal to that given
func (o *DeleteFileObjectDefault) IsCode(code int) bool {
return o._statusCode == code
}
// Code gets the status code for the delete file object default response
func (o *DeleteFileObjectDefault) Code() int {
return o._statusCode
}
func (o *DeleteFileObjectDefault) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[DELETE /objects/{objectID}][%d] DeleteFileObject default %s", o._statusCode, payload)
}
func (o *DeleteFileObjectDefault) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[DELETE /objects/{objectID}][%d] DeleteFileObject default %s", o._statusCode, payload)
}
func (o *DeleteFileObjectDefault) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *DeleteFileObjectDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -0,0 +1,151 @@
// Code generated by go-swagger; DO NOT EDIT.
package objects
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
"time"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
)
// NewGetFileObjectParams creates a new GetFileObjectParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewGetFileObjectParams() *GetFileObjectParams {
return &GetFileObjectParams{
timeout: cr.DefaultTimeout,
}
}
// NewGetFileObjectParamsWithTimeout creates a new GetFileObjectParams object
// with the ability to set a timeout on a request.
func NewGetFileObjectParamsWithTimeout(timeout time.Duration) *GetFileObjectParams {
return &GetFileObjectParams{
timeout: timeout,
}
}
// NewGetFileObjectParamsWithContext creates a new GetFileObjectParams object
// with the ability to set a context for a request.
func NewGetFileObjectParamsWithContext(ctx context.Context) *GetFileObjectParams {
return &GetFileObjectParams{
Context: ctx,
}
}
// NewGetFileObjectParamsWithHTTPClient creates a new GetFileObjectParams object
// with the ability to set a custom HTTPClient for a request.
func NewGetFileObjectParamsWithHTTPClient(client *http.Client) *GetFileObjectParams {
return &GetFileObjectParams{
HTTPClient: client,
}
}
/*
GetFileObjectParams contains all the parameters to send to the API endpoint
for the get file object operation.
Typically these are written to a http.Request.
*/
type GetFileObjectParams struct {
/* ObjectID.
The ID of the file object.
*/
ObjectID string
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the get file object params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *GetFileObjectParams) WithDefaults() *GetFileObjectParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the get file object params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *GetFileObjectParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the get file object params
func (o *GetFileObjectParams) WithTimeout(timeout time.Duration) *GetFileObjectParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the get file object params
func (o *GetFileObjectParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the get file object params
func (o *GetFileObjectParams) WithContext(ctx context.Context) *GetFileObjectParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the get file object params
func (o *GetFileObjectParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the get file object params
func (o *GetFileObjectParams) WithHTTPClient(client *http.Client) *GetFileObjectParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the get file object params
func (o *GetFileObjectParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithObjectID adds the objectID to the get file object params
func (o *GetFileObjectParams) WithObjectID(objectID string) *GetFileObjectParams {
o.SetObjectID(objectID)
return o
}
// SetObjectID adds the objectId to the get file object params
func (o *GetFileObjectParams) SetObjectID(objectID string) {
o.ObjectID = objectID
}
// WriteToRequest writes these params to a swagger request
func (o *GetFileObjectParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
// path param objectID
if err := r.SetPathParam("objectID", o.ObjectID); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View file

@ -0,0 +1,179 @@
// Code generated by go-swagger; DO NOT EDIT.
package objects
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
"fmt"
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
apiserver_params "github.com/cloudbase/garm/apiserver/params"
garm_params "github.com/cloudbase/garm/params"
)
// GetFileObjectReader is a Reader for the GetFileObject structure.
type GetFileObjectReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *GetFileObjectReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewGetFileObjectOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
case 400:
result := NewGetFileObjectBadRequest()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return nil, result
default:
return nil, runtime.NewAPIError("[GET /objects/{objectID}] GetFileObject", response, response.Code())
}
}
// NewGetFileObjectOK creates a GetFileObjectOK with default headers values
func NewGetFileObjectOK() *GetFileObjectOK {
return &GetFileObjectOK{}
}
/*
GetFileObjectOK describes a response with status code 200, with default header values.
FileObject
*/
type GetFileObjectOK struct {
Payload garm_params.FileObject
}
// IsSuccess returns true when this get file object o k response has a 2xx status code
func (o *GetFileObjectOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this get file object o k response has a 3xx status code
func (o *GetFileObjectOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this get file object o k response has a 4xx status code
func (o *GetFileObjectOK) IsClientError() bool {
return false
}
// IsServerError returns true when this get file object o k response has a 5xx status code
func (o *GetFileObjectOK) IsServerError() bool {
return false
}
// IsCode returns true when this get file object o k response a status code equal to that given
func (o *GetFileObjectOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the get file object o k response
func (o *GetFileObjectOK) Code() int {
return 200
}
func (o *GetFileObjectOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /objects/{objectID}][%d] getFileObjectOK %s", 200, payload)
}
func (o *GetFileObjectOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /objects/{objectID}][%d] getFileObjectOK %s", 200, payload)
}
func (o *GetFileObjectOK) GetPayload() garm_params.FileObject {
return o.Payload
}
func (o *GetFileObjectOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}
// NewGetFileObjectBadRequest creates a GetFileObjectBadRequest with default headers values
func NewGetFileObjectBadRequest() *GetFileObjectBadRequest {
return &GetFileObjectBadRequest{}
}
/*
GetFileObjectBadRequest describes a response with status code 400, with default header values.
APIErrorResponse
*/
type GetFileObjectBadRequest struct {
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this get file object bad request response has a 2xx status code
func (o *GetFileObjectBadRequest) IsSuccess() bool {
return false
}
// IsRedirect returns true when this get file object bad request response has a 3xx status code
func (o *GetFileObjectBadRequest) IsRedirect() bool {
return false
}
// IsClientError returns true when this get file object bad request response has a 4xx status code
func (o *GetFileObjectBadRequest) IsClientError() bool {
return true
}
// IsServerError returns true when this get file object bad request response has a 5xx status code
func (o *GetFileObjectBadRequest) IsServerError() bool {
return false
}
// IsCode returns true when this get file object bad request response a status code equal to that given
func (o *GetFileObjectBadRequest) IsCode(code int) bool {
return code == 400
}
// Code gets the status code for the get file object bad request response
func (o *GetFileObjectBadRequest) Code() int {
return 400
}
func (o *GetFileObjectBadRequest) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /objects/{objectID}][%d] getFileObjectBadRequest %s", 400, payload)
}
func (o *GetFileObjectBadRequest) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /objects/{objectID}][%d] getFileObjectBadRequest %s", 400, payload)
}
func (o *GetFileObjectBadRequest) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *GetFileObjectBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -0,0 +1,232 @@
// Code generated by go-swagger; DO NOT EDIT.
package objects
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
"time"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// NewListFileObjectsParams creates a new ListFileObjectsParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewListFileObjectsParams() *ListFileObjectsParams {
return &ListFileObjectsParams{
timeout: cr.DefaultTimeout,
}
}
// NewListFileObjectsParamsWithTimeout creates a new ListFileObjectsParams object
// with the ability to set a timeout on a request.
func NewListFileObjectsParamsWithTimeout(timeout time.Duration) *ListFileObjectsParams {
return &ListFileObjectsParams{
timeout: timeout,
}
}
// NewListFileObjectsParamsWithContext creates a new ListFileObjectsParams object
// with the ability to set a context for a request.
func NewListFileObjectsParamsWithContext(ctx context.Context) *ListFileObjectsParams {
return &ListFileObjectsParams{
Context: ctx,
}
}
// NewListFileObjectsParamsWithHTTPClient creates a new ListFileObjectsParams object
// with the ability to set a custom HTTPClient for a request.
func NewListFileObjectsParamsWithHTTPClient(client *http.Client) *ListFileObjectsParams {
return &ListFileObjectsParams{
HTTPClient: client,
}
}
/*
ListFileObjectsParams contains all the parameters to send to the API endpoint
for the list file objects operation.
Typically these are written to a http.Request.
*/
type ListFileObjectsParams struct {
/* Page.
The page at which to list.
*/
Page *int64
/* PageSize.
Number of items per page.
*/
PageSize *int64
/* Tags.
List of tags to filter by.
*/
Tags *string
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the list file objects params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ListFileObjectsParams) WithDefaults() *ListFileObjectsParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the list file objects params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *ListFileObjectsParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the list file objects params
func (o *ListFileObjectsParams) WithTimeout(timeout time.Duration) *ListFileObjectsParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the list file objects params
func (o *ListFileObjectsParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the list file objects params
func (o *ListFileObjectsParams) WithContext(ctx context.Context) *ListFileObjectsParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the list file objects params
func (o *ListFileObjectsParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the list file objects params
func (o *ListFileObjectsParams) WithHTTPClient(client *http.Client) *ListFileObjectsParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the list file objects params
func (o *ListFileObjectsParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithPage adds the page to the list file objects params
func (o *ListFileObjectsParams) WithPage(page *int64) *ListFileObjectsParams {
o.SetPage(page)
return o
}
// SetPage adds the page to the list file objects params
func (o *ListFileObjectsParams) SetPage(page *int64) {
o.Page = page
}
// WithPageSize adds the pageSize to the list file objects params
func (o *ListFileObjectsParams) WithPageSize(pageSize *int64) *ListFileObjectsParams {
o.SetPageSize(pageSize)
return o
}
// SetPageSize adds the pageSize to the list file objects params
func (o *ListFileObjectsParams) SetPageSize(pageSize *int64) {
o.PageSize = pageSize
}
// WithTags adds the tags to the list file objects params
func (o *ListFileObjectsParams) WithTags(tags *string) *ListFileObjectsParams {
o.SetTags(tags)
return o
}
// SetTags adds the tags to the list file objects params
func (o *ListFileObjectsParams) SetTags(tags *string) {
o.Tags = tags
}
// WriteToRequest writes these params to a swagger request
func (o *ListFileObjectsParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
if o.Page != nil {
// query param page
var qrPage int64
if o.Page != nil {
qrPage = *o.Page
}
qPage := swag.FormatInt64(qrPage)
if qPage != "" {
if err := r.SetQueryParam("page", qPage); err != nil {
return err
}
}
}
if o.PageSize != nil {
// query param pageSize
var qrPageSize int64
if o.PageSize != nil {
qrPageSize = *o.PageSize
}
qPageSize := swag.FormatInt64(qrPageSize)
if qPageSize != "" {
if err := r.SetQueryParam("pageSize", qPageSize); err != nil {
return err
}
}
}
if o.Tags != nil {
// query param tags
var qrTags string
if o.Tags != nil {
qrTags = *o.Tags
}
qTags := qrTags
if qTags != "" {
if err := r.SetQueryParam("tags", qTags); err != nil {
return err
}
}
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View file

@ -0,0 +1,179 @@
// Code generated by go-swagger; DO NOT EDIT.
package objects
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
"fmt"
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
apiserver_params "github.com/cloudbase/garm/apiserver/params"
garm_params "github.com/cloudbase/garm/params"
)
// ListFileObjectsReader is a Reader for the ListFileObjects structure.
type ListFileObjectsReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *ListFileObjectsReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewListFileObjectsOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
case 400:
result := NewListFileObjectsBadRequest()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return nil, result
default:
return nil, runtime.NewAPIError("[GET /objects] ListFileObjects", response, response.Code())
}
}
// NewListFileObjectsOK creates a ListFileObjectsOK with default headers values
func NewListFileObjectsOK() *ListFileObjectsOK {
return &ListFileObjectsOK{}
}
/*
ListFileObjectsOK describes a response with status code 200, with default header values.
FileObjectPaginatedResponse
*/
type ListFileObjectsOK struct {
Payload garm_params.FileObjectPaginatedResponse
}
// IsSuccess returns true when this list file objects o k response has a 2xx status code
func (o *ListFileObjectsOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this list file objects o k response has a 3xx status code
func (o *ListFileObjectsOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this list file objects o k response has a 4xx status code
func (o *ListFileObjectsOK) IsClientError() bool {
return false
}
// IsServerError returns true when this list file objects o k response has a 5xx status code
func (o *ListFileObjectsOK) IsServerError() bool {
return false
}
// IsCode returns true when this list file objects o k response a status code equal to that given
func (o *ListFileObjectsOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the list file objects o k response
func (o *ListFileObjectsOK) Code() int {
return 200
}
func (o *ListFileObjectsOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /objects][%d] listFileObjectsOK %s", 200, payload)
}
func (o *ListFileObjectsOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /objects][%d] listFileObjectsOK %s", 200, payload)
}
func (o *ListFileObjectsOK) GetPayload() garm_params.FileObjectPaginatedResponse {
return o.Payload
}
func (o *ListFileObjectsOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}
// NewListFileObjectsBadRequest creates a ListFileObjectsBadRequest with default headers values
func NewListFileObjectsBadRequest() *ListFileObjectsBadRequest {
return &ListFileObjectsBadRequest{}
}
/*
ListFileObjectsBadRequest describes a response with status code 400, with default header values.
APIErrorResponse
*/
type ListFileObjectsBadRequest struct {
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this list file objects bad request response has a 2xx status code
func (o *ListFileObjectsBadRequest) IsSuccess() bool {
return false
}
// IsRedirect returns true when this list file objects bad request response has a 3xx status code
func (o *ListFileObjectsBadRequest) IsRedirect() bool {
return false
}
// IsClientError returns true when this list file objects bad request response has a 4xx status code
func (o *ListFileObjectsBadRequest) IsClientError() bool {
return true
}
// IsServerError returns true when this list file objects bad request response has a 5xx status code
func (o *ListFileObjectsBadRequest) IsServerError() bool {
return false
}
// IsCode returns true when this list file objects bad request response a status code equal to that given
func (o *ListFileObjectsBadRequest) IsCode(code int) bool {
return code == 400
}
// Code gets the status code for the list file objects bad request response
func (o *ListFileObjectsBadRequest) Code() int {
return 400
}
func (o *ListFileObjectsBadRequest) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /objects][%d] listFileObjectsBadRequest %s", 400, payload)
}
func (o *ListFileObjectsBadRequest) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[GET /objects][%d] listFileObjectsBadRequest %s", 400, payload)
}
func (o *ListFileObjectsBadRequest) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *ListFileObjectsBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -0,0 +1,222 @@
// Code generated by go-swagger; DO NOT EDIT.
package objects
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"fmt"
"github.com/go-openapi/runtime"
httptransport "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
)
// New creates a new objects API client.
func New(transport runtime.ClientTransport, formats strfmt.Registry) ClientService {
return &Client{transport: transport, formats: formats}
}
// New creates a new objects API client with basic auth credentials.
// It takes the following parameters:
// - host: http host (github.com).
// - basePath: any base path for the API client ("/v1", "/v3").
// - scheme: http scheme ("http", "https").
// - user: user for basic authentication header.
// - password: password for basic authentication header.
func NewClientWithBasicAuth(host, basePath, scheme, user, password string) ClientService {
transport := httptransport.New(host, basePath, []string{scheme})
transport.DefaultAuthentication = httptransport.BasicAuth(user, password)
return &Client{transport: transport, formats: strfmt.Default}
}
// New creates a new objects API client with a bearer token for authentication.
// It takes the following parameters:
// - host: http host (github.com).
// - basePath: any base path for the API client ("/v1", "/v3").
// - scheme: http scheme ("http", "https").
// - bearerToken: bearer token for Bearer authentication header.
func NewClientWithBearerToken(host, basePath, scheme, bearerToken string) ClientService {
transport := httptransport.New(host, basePath, []string{scheme})
transport.DefaultAuthentication = httptransport.BearerToken(bearerToken)
return &Client{transport: transport, formats: strfmt.Default}
}
/*
Client for objects API
*/
type Client struct {
transport runtime.ClientTransport
formats strfmt.Registry
}
// ClientOption may be used to customize the behavior of Client methods.
type ClientOption func(*runtime.ClientOperation)
// ClientService is the interface for Client methods
type ClientService interface {
DeleteFileObject(params *DeleteFileObjectParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) error
GetFileObject(params *GetFileObjectParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*GetFileObjectOK, error)
ListFileObjects(params *ListFileObjectsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListFileObjectsOK, error)
UpdateFileObject(params *UpdateFileObjectParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*UpdateFileObjectOK, error)
SetTransport(transport runtime.ClientTransport)
}
/*
DeleteFileObject deletes a file object
*/
func (a *Client) DeleteFileObject(params *DeleteFileObjectParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) error {
// TODO: Validate the params before sending
if params == nil {
params = NewDeleteFileObjectParams()
}
op := &runtime.ClientOperation{
ID: "DeleteFileObject",
Method: "DELETE",
PathPattern: "/objects/{objectID}",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &DeleteFileObjectReader{formats: a.formats},
AuthInfo: authInfo,
Context: params.Context,
Client: params.HTTPClient,
}
for _, opt := range opts {
opt(op)
}
_, err := a.transport.Submit(op)
if err != nil {
return err
}
return nil
}
/*
GetFileObject gets a file object
*/
func (a *Client) GetFileObject(params *GetFileObjectParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*GetFileObjectOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewGetFileObjectParams()
}
op := &runtime.ClientOperation{
ID: "GetFileObject",
Method: "GET",
PathPattern: "/objects/{objectID}",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &GetFileObjectReader{formats: a.formats},
AuthInfo: authInfo,
Context: params.Context,
Client: params.HTTPClient,
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
success, ok := result.(*GetFileObjectOK)
if ok {
return success, nil
}
// unexpected success response
// safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue
msg := fmt.Sprintf("unexpected success response for GetFileObject: API contract not enforced by server. Client expected to get an error, but got: %T", result)
panic(msg)
}
/*
ListFileObjects lists file objects
*/
func (a *Client) ListFileObjects(params *ListFileObjectsParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*ListFileObjectsOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewListFileObjectsParams()
}
op := &runtime.ClientOperation{
ID: "ListFileObjects",
Method: "GET",
PathPattern: "/objects",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &ListFileObjectsReader{formats: a.formats},
AuthInfo: authInfo,
Context: params.Context,
Client: params.HTTPClient,
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
success, ok := result.(*ListFileObjectsOK)
if ok {
return success, nil
}
// unexpected success response
// safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue
msg := fmt.Sprintf("unexpected success response for ListFileObjects: API contract not enforced by server. Client expected to get an error, but got: %T", result)
panic(msg)
}
/*
UpdateFileObject updates a file object
*/
func (a *Client) UpdateFileObject(params *UpdateFileObjectParams, authInfo runtime.ClientAuthInfoWriter, opts ...ClientOption) (*UpdateFileObjectOK, error) {
// TODO: Validate the params before sending
if params == nil {
params = NewUpdateFileObjectParams()
}
op := &runtime.ClientOperation{
ID: "UpdateFileObject",
Method: "PUT",
PathPattern: "/objects/{objectID}",
ProducesMediaTypes: []string{"application/json"},
ConsumesMediaTypes: []string{"application/json"},
Schemes: []string{"http"},
Params: params,
Reader: &UpdateFileObjectReader{formats: a.formats},
AuthInfo: authInfo,
Context: params.Context,
Client: params.HTTPClient,
}
for _, opt := range opts {
opt(op)
}
result, err := a.transport.Submit(op)
if err != nil {
return nil, err
}
success, ok := result.(*UpdateFileObjectOK)
if ok {
return success, nil
}
// unexpected success response
// safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue
msg := fmt.Sprintf("unexpected success response for UpdateFileObject: API contract not enforced by server. Client expected to get an error, but got: %T", result)
panic(msg)
}
// SetTransport changes the transport on the client
func (a *Client) SetTransport(transport runtime.ClientTransport) {
a.transport = transport
}

View file

@ -0,0 +1,173 @@
// Code generated by go-swagger; DO NOT EDIT.
package objects
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"net/http"
"time"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime"
cr "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
garm_params "github.com/cloudbase/garm/params"
)
// NewUpdateFileObjectParams creates a new UpdateFileObjectParams object,
// with the default timeout for this client.
//
// Default values are not hydrated, since defaults are normally applied by the API server side.
//
// To enforce default values in parameter, use SetDefaults or WithDefaults.
func NewUpdateFileObjectParams() *UpdateFileObjectParams {
return &UpdateFileObjectParams{
timeout: cr.DefaultTimeout,
}
}
// NewUpdateFileObjectParamsWithTimeout creates a new UpdateFileObjectParams object
// with the ability to set a timeout on a request.
func NewUpdateFileObjectParamsWithTimeout(timeout time.Duration) *UpdateFileObjectParams {
return &UpdateFileObjectParams{
timeout: timeout,
}
}
// NewUpdateFileObjectParamsWithContext creates a new UpdateFileObjectParams object
// with the ability to set a context for a request.
func NewUpdateFileObjectParamsWithContext(ctx context.Context) *UpdateFileObjectParams {
return &UpdateFileObjectParams{
Context: ctx,
}
}
// NewUpdateFileObjectParamsWithHTTPClient creates a new UpdateFileObjectParams object
// with the ability to set a custom HTTPClient for a request.
func NewUpdateFileObjectParamsWithHTTPClient(client *http.Client) *UpdateFileObjectParams {
return &UpdateFileObjectParams{
HTTPClient: client,
}
}
/*
UpdateFileObjectParams contains all the parameters to send to the API endpoint
for the update file object operation.
Typically these are written to a http.Request.
*/
type UpdateFileObjectParams struct {
/* Body.
Parameters used when updating a file object.
*/
Body garm_params.UpdateFileObjectParams
/* ObjectID.
The ID of the file object.
*/
ObjectID string
timeout time.Duration
Context context.Context
HTTPClient *http.Client
}
// WithDefaults hydrates default values in the update file object params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *UpdateFileObjectParams) WithDefaults() *UpdateFileObjectParams {
o.SetDefaults()
return o
}
// SetDefaults hydrates default values in the update file object params (not the query body).
//
// All values with no default are reset to their zero value.
func (o *UpdateFileObjectParams) SetDefaults() {
// no default values defined for this parameter
}
// WithTimeout adds the timeout to the update file object params
func (o *UpdateFileObjectParams) WithTimeout(timeout time.Duration) *UpdateFileObjectParams {
o.SetTimeout(timeout)
return o
}
// SetTimeout adds the timeout to the update file object params
func (o *UpdateFileObjectParams) SetTimeout(timeout time.Duration) {
o.timeout = timeout
}
// WithContext adds the context to the update file object params
func (o *UpdateFileObjectParams) WithContext(ctx context.Context) *UpdateFileObjectParams {
o.SetContext(ctx)
return o
}
// SetContext adds the context to the update file object params
func (o *UpdateFileObjectParams) SetContext(ctx context.Context) {
o.Context = ctx
}
// WithHTTPClient adds the HTTPClient to the update file object params
func (o *UpdateFileObjectParams) WithHTTPClient(client *http.Client) *UpdateFileObjectParams {
o.SetHTTPClient(client)
return o
}
// SetHTTPClient adds the HTTPClient to the update file object params
func (o *UpdateFileObjectParams) SetHTTPClient(client *http.Client) {
o.HTTPClient = client
}
// WithBody adds the body to the update file object params
func (o *UpdateFileObjectParams) WithBody(body garm_params.UpdateFileObjectParams) *UpdateFileObjectParams {
o.SetBody(body)
return o
}
// SetBody adds the body to the update file object params
func (o *UpdateFileObjectParams) SetBody(body garm_params.UpdateFileObjectParams) {
o.Body = body
}
// WithObjectID adds the objectID to the update file object params
func (o *UpdateFileObjectParams) WithObjectID(objectID string) *UpdateFileObjectParams {
o.SetObjectID(objectID)
return o
}
// SetObjectID adds the objectId to the update file object params
func (o *UpdateFileObjectParams) SetObjectID(objectID string) {
o.ObjectID = objectID
}
// WriteToRequest writes these params to a swagger request
func (o *UpdateFileObjectParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error {
if err := r.SetTimeout(o.timeout); err != nil {
return err
}
var res []error
if err := r.SetBodyParam(o.Body); err != nil {
return err
}
// path param objectID
if err := r.SetPathParam("objectID", o.ObjectID); err != nil {
return err
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}

View file

@ -0,0 +1,179 @@
// Code generated by go-swagger; DO NOT EDIT.
package objects
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"encoding/json"
"fmt"
"io"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
apiserver_params "github.com/cloudbase/garm/apiserver/params"
garm_params "github.com/cloudbase/garm/params"
)
// UpdateFileObjectReader is a Reader for the UpdateFileObject structure.
type UpdateFileObjectReader struct {
formats strfmt.Registry
}
// ReadResponse reads a server response into the received o.
func (o *UpdateFileObjectReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) {
switch response.Code() {
case 200:
result := NewUpdateFileObjectOK()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return result, nil
case 400:
result := NewUpdateFileObjectBadRequest()
if err := result.readResponse(response, consumer, o.formats); err != nil {
return nil, err
}
return nil, result
default:
return nil, runtime.NewAPIError("[PUT /objects/{objectID}] UpdateFileObject", response, response.Code())
}
}
// NewUpdateFileObjectOK creates a UpdateFileObjectOK with default headers values
func NewUpdateFileObjectOK() *UpdateFileObjectOK {
return &UpdateFileObjectOK{}
}
/*
UpdateFileObjectOK describes a response with status code 200, with default header values.
FileObject
*/
type UpdateFileObjectOK struct {
Payload garm_params.FileObject
}
// IsSuccess returns true when this update file object o k response has a 2xx status code
func (o *UpdateFileObjectOK) IsSuccess() bool {
return true
}
// IsRedirect returns true when this update file object o k response has a 3xx status code
func (o *UpdateFileObjectOK) IsRedirect() bool {
return false
}
// IsClientError returns true when this update file object o k response has a 4xx status code
func (o *UpdateFileObjectOK) IsClientError() bool {
return false
}
// IsServerError returns true when this update file object o k response has a 5xx status code
func (o *UpdateFileObjectOK) IsServerError() bool {
return false
}
// IsCode returns true when this update file object o k response a status code equal to that given
func (o *UpdateFileObjectOK) IsCode(code int) bool {
return code == 200
}
// Code gets the status code for the update file object o k response
func (o *UpdateFileObjectOK) Code() int {
return 200
}
func (o *UpdateFileObjectOK) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[PUT /objects/{objectID}][%d] updateFileObjectOK %s", 200, payload)
}
func (o *UpdateFileObjectOK) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[PUT /objects/{objectID}][%d] updateFileObjectOK %s", 200, payload)
}
func (o *UpdateFileObjectOK) GetPayload() garm_params.FileObject {
return o.Payload
}
func (o *UpdateFileObjectOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}
// NewUpdateFileObjectBadRequest creates a UpdateFileObjectBadRequest with default headers values
func NewUpdateFileObjectBadRequest() *UpdateFileObjectBadRequest {
return &UpdateFileObjectBadRequest{}
}
/*
UpdateFileObjectBadRequest describes a response with status code 400, with default header values.
APIErrorResponse
*/
type UpdateFileObjectBadRequest struct {
Payload apiserver_params.APIErrorResponse
}
// IsSuccess returns true when this update file object bad request response has a 2xx status code
func (o *UpdateFileObjectBadRequest) IsSuccess() bool {
return false
}
// IsRedirect returns true when this update file object bad request response has a 3xx status code
func (o *UpdateFileObjectBadRequest) IsRedirect() bool {
return false
}
// IsClientError returns true when this update file object bad request response has a 4xx status code
func (o *UpdateFileObjectBadRequest) IsClientError() bool {
return true
}
// IsServerError returns true when this update file object bad request response has a 5xx status code
func (o *UpdateFileObjectBadRequest) IsServerError() bool {
return false
}
// IsCode returns true when this update file object bad request response a status code equal to that given
func (o *UpdateFileObjectBadRequest) IsCode(code int) bool {
return code == 400
}
// Code gets the status code for the update file object bad request response
func (o *UpdateFileObjectBadRequest) Code() int {
return 400
}
func (o *UpdateFileObjectBadRequest) Error() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[PUT /objects/{objectID}][%d] updateFileObjectBadRequest %s", 400, payload)
}
func (o *UpdateFileObjectBadRequest) String() string {
payload, _ := json.Marshal(o.Payload)
return fmt.Sprintf("[PUT /objects/{objectID}][%d] updateFileObjectBadRequest %s", 400, payload)
}
func (o *UpdateFileObjectBadRequest) GetPayload() apiserver_params.APIErrorResponse {
return o.Payload
}
func (o *UpdateFileObjectBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error {
// response payload
if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF {
return err
}
return nil
}

View file

@ -0,0 +1,585 @@
// Copyright 2025 Cloudbase Solutions SRL
//
// 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 cmd
import (
"encoding/json"
"fmt"
"io"
"mime"
"net/http"
"net/http/httputil"
"os"
"strings"
"sync"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/spf13/cobra"
apiClientObject "github.com/cloudbase/garm/client/objects"
"github.com/cloudbase/garm/cmd/garm-cli/common"
"github.com/cloudbase/garm/params"
)
var (
filePath string
fileName string
fileObjTags string
fileObjDescription string
fileObjPage int64
fileObjPageSize int64
outObjectPath string
forceOverwrite bool
quietMode bool
)
// progressReader wraps an io.Reader and reports progress
type progressReader struct {
reader io.Reader
total int64
current int64
lastPrinted int
mu sync.Mutex
}
func newProgressReader(r io.Reader, total int64) *progressReader {
return &progressReader{
reader: r,
total: total,
}
}
func (pr *progressReader) Read(p []byte) (int, error) {
n, err := pr.reader.Read(p)
pr.mu.Lock()
pr.current += int64(n)
pr.mu.Unlock()
pr.printProgress()
return n, err
}
func (pr *progressReader) printProgress() {
pr.mu.Lock()
defer pr.mu.Unlock()
if pr.total == 0 {
return
}
percent := int(float64(pr.current) / float64(pr.total) * 100)
// Only print every 5% or at 100%
if percent != pr.lastPrinted && (percent%5 == 0 || percent == 100) {
mb := float64(pr.current) / 1024 / 1024
totalMB := float64(pr.total) / 1024 / 1024
fmt.Printf("\rUploading: %d%% (%.2f MB / %.2f MB)", percent, mb, totalMB)
if percent == 100 {
fmt.Println() // New line at completion
}
pr.lastPrinted = percent
}
}
// giteaCredentialsCmd represents the gitea credentials command
var fileObjectCmd = &cobra.Command{
Use: "object",
Short: "Manage simple object storage",
Long: `Manage simple object storage.
This command allows you to use GARM as a simple, private internal-use object storage
system streamed to and from the database using blob I/O. The primary goal of this is
to allow users to store provider binaries, agent binaries runner tools and any other
type of files needed for a functional GARM deployment.
It is not meant to be used to serve files outside of the needs of GARM.`,
Run: nil,
}
var fileObjListCmd = &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List file objects",
RunE: func(_ *cobra.Command, _ []string) error {
if needsInit {
return errNeedsInitError
}
listReq := apiClientObject.NewListFileObjectsParams()
listReq.Tags = &fileObjTags
listReq.Page = &fileObjPage
listReq.PageSize = &fileObjPageSize
response, err := apiCli.Objects.ListFileObjects(listReq, authToken)
if err != nil {
return err
}
formatFileObjsList(response.Payload)
return nil
},
}
var fileObjDeleteCmd = &cobra.Command{
Use: "remove",
Aliases: []string{"delete", "del", "rm"},
Short: "List file objects",
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
objID := args[0]
delReq := apiClientObject.NewDeleteFileObjectParams().WithObjectID(objID)
err := apiCli.Objects.DeleteFileObject(delReq, authToken)
if err != nil {
return err
}
return nil
},
}
var fileObjShowCmd = &cobra.Command{
Use: "show",
Short: "Show a file object",
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
objID := args[0]
getReq := apiClientObject.NewGetFileObjectParams().WithObjectID(objID)
resp, err := apiCli.Objects.GetFileObject(getReq, authToken)
if err != nil {
return err
}
formatOneObject(resp.Payload)
return nil
},
}
var fileObjUpdateCmd = &cobra.Command{
Use: "update",
Short: "Update a file object",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
objID := args[0]
hasChanges := false
updateParams := params.UpdateFileObjectParams{}
updateReq := apiClientObject.NewUpdateFileObjectParams().WithObjectID(objID)
if cmd.Flags().Changed("name") {
hasChanges = true
updateParams.Name = &fileName
}
if cmd.Flags().Changed("tags") && fileObjTags != "" {
hasChanges = true
updateParams.Tags = strings.Split(fileObjTags, ",")
}
if cmd.Flags().Changed("description") {
hasChanges = true
updateParams.Description = &fileObjDescription
}
if !hasChanges {
fmt.Println("no changes made")
return nil
}
updateReq.Body = updateParams
resp, err := apiCli.Objects.UpdateFileObject(updateReq, authToken)
if err != nil {
return err
}
formatOneObject(resp.Payload)
return nil
},
}
var fileObjCreateCmd = &cobra.Command{
Use: "create",
Aliases: []string{"add", "upload"},
Short: "Upload a file to the server",
RunE: func(_ *cobra.Command, _ []string) error {
if needsInit {
return errNeedsInitError
}
if filePath == "" {
return fmt.Errorf("missing file path")
}
stat, err := os.Stat(filePath)
if err != nil {
return fmt.Errorf("failed to acces file: %w", err)
}
file, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("failed to open file: %w", err)
}
defer file.Close()
var tags []string
if fileObjTags != "" {
tags = strings.Split(fileObjTags, ",")
}
// Wrap file reader with progress tracking
progressR := newProgressReader(file, stat.Size())
// Create request with progress-tracked file stream
req, err := rawHTTPClient.NewRequest(http.MethodPost, "/objects/", progressR)
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("X-File-Name", fileName)
if len(fileObjDescription) > 2<<12 {
return fmt.Errorf("description is too large (max 8KB)")
}
if fileObjDescription != "" {
req.Header.Set("X-File-Description", fileObjDescription)
}
if len(tags) > 0 {
req.Header.Set("X-Tags", strings.Join(tags, ","))
}
req.ContentLength = stat.Size()
// Debug: dump request
if debug {
// Don't dump body for large uploads
b, err2 := httputil.DumpRequestOut(req, false)
if err2 != nil {
return fmt.Errorf("failed to dump request: %w", err2)
}
fmt.Fprintf(os.Stderr, "DEBUG REQUEST:\n%s\n", string(b))
}
// Show initial progress
fmt.Printf("Uploading %s (%.2f MB)...\n", fileName, float64(stat.Size())/1024/1024)
resp, err := http.DefaultClient.Do(req)
if err != nil {
if debug {
fmt.Fprintf(os.Stderr, "DEBUG ERROR: %v\n", err)
}
return fmt.Errorf("failed to upload: %w", err)
}
defer resp.Body.Close()
// Debug: dump response
if debug {
b, err2 := httputil.DumpResponse(resp, true)
if err2 != nil {
return fmt.Errorf("failed to dump response: %w", err2)
}
fmt.Fprintf(os.Stderr, "DEBUG RESPONSE:\n%s\n", string(b))
}
data, err := io.ReadAll(resp.Body)
if err != nil {
if debug {
fmt.Fprintf(os.Stderr, "DEBUG ERROR reading response body: %v\n", err)
}
return fmt.Errorf("failed to read response body: %w", err)
}
// Check for non-2xx status codes
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
if debug {
fmt.Fprintf(os.Stderr, "DEBUG ERROR: HTTP %d: %s\n", resp.StatusCode, string(data))
}
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(data))
}
var fileResp params.FileObject
if err := json.Unmarshal(data, &fileResp); err != nil {
if debug {
fmt.Fprintf(os.Stderr, "DEBUG ERROR decoding response: %v\nResponse body: %s\n", err, string(data))
}
return fmt.Errorf("failed to decode response: %w", err)
}
formatOneObject(fileResp)
return nil
},
}
var fileObjDownloadCmd = &cobra.Command{
Use: "download",
Short: "Download a file from the server",
Args: cobra.ExactArgs(1),
SilenceUsage: true,
RunE: func(_ *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
objectID := args[0]
// Create request for download
req, err := rawHTTPClient.NewRequest(http.MethodGet, fmt.Sprintf("/objects/%s/download", objectID), nil)
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
// Debug: dump request
if debug {
b, err2 := httputil.DumpRequestOut(req, false)
if err2 != nil {
return fmt.Errorf("failed to dump request: %w", err2)
}
fmt.Fprintf(os.Stderr, "DEBUG REQUEST:\n%s\n", string(b))
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
if debug {
fmt.Fprintf(os.Stderr, "DEBUG ERROR: %v\n", err)
}
return fmt.Errorf("failed to download: %w", err)
}
defer resp.Body.Close()
// Debug: dump response headers (not body for large files)
if debug {
b, err2 := httputil.DumpResponse(resp, false)
if err2 != nil {
return fmt.Errorf("failed to dump response: %w", err2)
}
fmt.Fprintf(os.Stderr, "DEBUG RESPONSE:\n%s\n", string(b))
}
// Check for non-2xx status codes
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
data, _ := io.ReadAll(resp.Body)
if debug {
fmt.Fprintf(os.Stderr, "DEBUG ERROR: HTTP %d: %s\n", resp.StatusCode, string(data))
}
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(data))
}
// Get filename from Content-Disposition header if not specified
filename := outObjectPath
if filename == "" {
contentDisp := resp.Header.Get("Content-Disposition")
if contentDisp != "" {
// Parse Content-Disposition header
_, params, err := mime.ParseMediaType(contentDisp)
if err == nil && params["filename"] != "" {
filename = params["filename"]
}
}
if filename == "" {
return fmt.Errorf("no output file specified and server did not provide filename")
}
}
// Check if path exists
if stat, err := os.Stat(filename); err == nil {
if stat.IsDir() {
return fmt.Errorf("output path is a directory: %s", filename)
}
if !forceOverwrite {
return fmt.Errorf("file already exists: %s (use --force-overwrite to overwrite)", filename)
}
}
// Create output file
outFile, err := os.Create(filename)
if err != nil {
return fmt.Errorf("failed to create output file: %w", err)
}
defer outFile.Close()
// Get content length for progress tracking
contentLength := resp.ContentLength
if contentLength <= 0 {
// Try to parse from header if not set
contentLengthStr := resp.Header.Get("Content-Length")
if contentLengthStr != "" {
fmt.Sscanf(contentLengthStr, "%d", &contentLength)
}
}
// Show initial progress
if !quietMode {
if contentLength > 0 {
fmt.Printf("Downloading %s (%.2f MB)...\n", filename, float64(contentLength)/1024/1024)
} else {
fmt.Printf("Downloading %s...\n", filename)
}
}
// Wrap reader with progress tracking
var reader io.Reader = resp.Body
if contentLength > 0 && !quietMode {
progressR := newProgressReader(resp.Body, contentLength)
// Change progress message for download
reader = &downloadProgressReader{progressR}
}
// Copy to file
written, err := io.Copy(outFile, reader)
if err != nil {
if debug {
fmt.Fprintf(os.Stderr, "DEBUG ERROR writing to file: %v\n", err)
}
return fmt.Errorf("failed to write file: %w", err)
}
if !quietMode {
fmt.Printf("Downloaded %s (%.2f MB)\n", filename, float64(written)/1024/1024)
}
return nil
},
}
// downloadProgressReader wraps progressReader to change the message
type downloadProgressReader struct {
*progressReader
}
func (dpr *downloadProgressReader) Read(p []byte) (int, error) {
n, err := dpr.reader.Read(p)
dpr.mu.Lock()
dpr.current += int64(n)
dpr.mu.Unlock()
dpr.printProgress()
return n, err
}
func (dpr *downloadProgressReader) printProgress() {
dpr.mu.Lock()
defer dpr.mu.Unlock()
if dpr.total == 0 {
return
}
percent := int(float64(dpr.current) / float64(dpr.total) * 100)
// Only print every 5% or at 100%
if percent != dpr.lastPrinted && (percent%5 == 0 || percent == 100) {
mb := float64(dpr.current) / 1024 / 1024
totalMB := float64(dpr.total) / 1024 / 1024
fmt.Printf("\rDownloading: %d%% (%.2f MB / %.2f MB)", percent, mb, totalMB)
if percent == 100 {
fmt.Println() // New line at completion
}
dpr.lastPrinted = percent
}
}
func init() {
fileObjCreateCmd.Flags().StringVar(&fileName, "name", "", "Name of the file")
fileObjCreateCmd.Flags().StringVar(&fileObjDescription, "description", "", "A short description for the file")
fileObjCreateCmd.Flags().StringVar(&filePath, "path", "", "The path on disk to the file")
fileObjCreateCmd.Flags().StringVar(&fileObjTags, "tags", "", "Comma separated tag list (ie: test,binary,os_type=linux,example)")
fileObjCreateCmd.MarkFlagRequired("name")
fileObjCreateCmd.MarkFlagRequired("path")
fileObjDownloadCmd.Flags().StringVar(&outObjectPath, "out-file", "", "Output file path (optional, defaults to filename from server)")
fileObjDownloadCmd.Flags().BoolVar(&forceOverwrite, "force-overwrite", false, "Overwrite existing file")
fileObjDownloadCmd.Flags().BoolVarP(&quietMode, "quiet", "q", false, "Suppress download progress output")
fileObjUpdateCmd.Flags().StringVar(&fileName, "name", "", "New name of the file")
fileObjUpdateCmd.Flags().StringVar(&fileObjTags, "tags", "", "Set new tags. The tags are a comma separated list (ie: test,binary,os_type=linux,example)")
fileObjUpdateCmd.Flags().StringVar(&fileObjDescription, "description", "", "A short description for the file")
fileObjListCmd.Flags().StringVar(&fileObjTags, "tags", "", "Comma separated list of tags to use as search items (optional)")
fileObjListCmd.Flags().Int64Var(&fileObjPage, "page", 0, "The file object page to display")
fileObjListCmd.Flags().Int64Var(&fileObjPageSize, "page-size", 25, "Total number of results per page")
fileObjectCmd.AddCommand(fileObjCreateCmd)
fileObjectCmd.AddCommand(fileObjDownloadCmd)
fileObjectCmd.AddCommand(fileObjUpdateCmd)
fileObjectCmd.AddCommand(fileObjListCmd)
fileObjectCmd.AddCommand(fileObjShowCmd)
fileObjectCmd.AddCommand(fileObjDeleteCmd)
rootCmd.AddCommand(fileObjectCmd)
}
func formatOneObject(fileObj params.FileObject) {
if outputFormat == common.OutputFormatJSON {
printAsJSON(fileObj)
return
}
t := table.NewWriter()
t.Style().Options.SeparateHeader = true
header := table.Row{"Field", "Value"}
t.AppendHeader(header)
t.AppendRow([]interface{}{"ID", fileObj.ID})
t.AppendRow([]interface{}{"Name", fileObj.Name})
t.AppendRow([]interface{}{"Created At", fileObj.CreatedAt})
t.AppendRow([]interface{}{"Updated At", fileObj.UpdatedAt})
t.AppendRow([]interface{}{"Size", fileObj.Size})
t.AppendRow([]interface{}{"SHA256SUM", fileObj.SHA256})
t.AppendRow([]interface{}{"File Type", fileObj.FileType})
t.AppendRow([]interface{}{"Description", fileObj.Description})
if len(fileObj.Tags) > 0 {
t.AppendRow([]interface{}{"Tags", strings.Join(fileObj.Tags, ", ")})
}
t.SetColumnConfigs([]table.ColumnConfig{
{Number: 1, AutoMerge: true},
{Number: 2, AutoMerge: false, WidthMax: 100},
})
fmt.Println(t.Render())
}
func formatFileObjsList(files params.FileObjectPaginatedResponse) {
if outputFormat == common.OutputFormatJSON {
printAsJSON(files)
return
}
t := table.NewWriter()
// Define column count
numCols := 6
t.Style().Options.SeparateHeader = true
t.Style().Options.SeparateRows = true
// Page header - fill all columns with the same text
pageHeaderText := fmt.Sprintf("Page %d of %d", files.CurrentPage, files.Pages)
pageHeader := make(table.Row, numCols)
for i := range pageHeader {
pageHeader[i] = pageHeaderText
}
t.AppendHeader(pageHeader, table.RowConfig{
AutoMerge: true,
AutoMergeAlign: text.AlignCenter,
})
// Column headers
header := table.Row{"ID", "Name", "Size", "Tags", "Created", "Updated"}
t.AppendHeader(header)
// Right-align numeric columns
t.SetColumnConfigs([]table.ColumnConfig{
{Number: 1, Align: text.AlignRight},
{Number: 3, Align: text.AlignRight},
})
for _, val := range files.Results {
row := table.Row{val.ID, val.Name, formatSize(val.Size), strings.Join(val.Tags, ", "), val.CreatedAt.Format("2006-01-02 15:04:05"), val.UpdatedAt.Format("2006-01-02 15:04:05")}
t.AppendRow(row)
}
fmt.Println(t.Render())
}
func formatSize(bytes int64) string {
const unit = 1024
if bytes < unit {
return fmt.Sprintf("%d B", bytes)
}
div, exp := int64(unit), 0
for n := bytes / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
}

View file

@ -17,8 +17,10 @@ package cmd
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"time"
"github.com/go-openapi/runtime"
openapiRuntimeClient "github.com/go-openapi/runtime/client"
@ -47,6 +49,7 @@ var (
poolBalancerType string
outputFormat common.OutputFormat = common.OutputFormatTable
errNeedsInitError = fmt.Errorf("please log into a garm installation first")
rawHTTPClient *rawClient
)
// rootCmd represents the base command when called without any subcommands
@ -90,6 +93,19 @@ func initAPIClient(baseURL, token string) {
WithSchemes([]string{baseURLParsed.Scheme})
apiCli = apiClient.NewHTTPClientWithConfig(nil, transportCfg)
authToken = openapiRuntimeClient.BearerToken(token)
joined, err := url.JoinPath(baseURL, apiClient.DefaultBasePath)
if err != nil {
fmt.Printf("Failed to join base url %s with %s: %s", baseURL, apiClient.DefaultBasePath, err)
os.Exit(1)
}
rawHTTPClient = &rawClient{
BaseURL: joined,
Token: token,
HTTPClient: &http.Client{
Timeout: 30 * time.Second,
},
}
}
func initConfig() {

View file

@ -2,7 +2,10 @@ package cmd
import (
"fmt"
"io"
"math"
"net/http"
"net/url"
"strconv"
"strings"
@ -147,3 +150,27 @@ func resolveEnterprise(nameOrID, endpoint string) (string, error) {
return response.Payload[0].ID, nil
}
type rawClient struct {
BaseURL string
Token string
HTTPClient *http.Client
}
// NewRequest creates a new HTTP request with auth and proper URL
func (c *rawClient) NewRequest(method, path string, body io.Reader) (*http.Request, error) {
url, err := url.JoinPath(c.BaseURL, path)
if err != nil {
return nil, fmt.Errorf("failed to join URL: %w", err)
}
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
// Add JWT token
req.Header.Set("Authorization", "Bearer "+c.Token)
return req, nil
}

View file

@ -346,9 +346,12 @@ func main() {
Handler: handlers.CORS(methodsOk, headersOk, allowedOrigins)(router),
ReadHeaderTimeout: 5 * time.Second,
MaxHeaderBytes: 1 << 20, // 1 MB
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
// Increased timeouts to support large file uploads/downloads
// ReadTimeout covers the entire request read including body
ReadTimeout: 30 * time.Minute,
// WriteTimeout covers the entire response write
WriteTimeout: 30 * time.Minute,
IdleTimeout: 60 * time.Second,
}
listener, err := net.Listen("tcp", srv.Addr)

View file

@ -4,8 +4,10 @@ package mocks
import (
context "context"
io "io"
garm_provider_commonparams "github.com/cloudbase/garm-provider-common/params"
mock "github.com/stretchr/testify/mock"
params "github.com/cloudbase/garm/params"
@ -403,6 +405,64 @@ func (_c *Store_CreateEntityScaleSet_Call) RunAndReturn(run func(context.Context
return _c
}
// CreateFileObject provides a mock function with given fields: ctx, param, reader
func (_m *Store) CreateFileObject(ctx context.Context, param params.CreateFileObjectParams, reader io.Reader) (params.FileObject, error) {
ret := _m.Called(ctx, param, reader)
if len(ret) == 0 {
panic("no return value specified for CreateFileObject")
}
var r0 params.FileObject
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, params.CreateFileObjectParams, io.Reader) (params.FileObject, error)); ok {
return rf(ctx, param, reader)
}
if rf, ok := ret.Get(0).(func(context.Context, params.CreateFileObjectParams, io.Reader) params.FileObject); ok {
r0 = rf(ctx, param, reader)
} else {
r0 = ret.Get(0).(params.FileObject)
}
if rf, ok := ret.Get(1).(func(context.Context, params.CreateFileObjectParams, io.Reader) error); ok {
r1 = rf(ctx, param, reader)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Store_CreateFileObject_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateFileObject'
type Store_CreateFileObject_Call struct {
*mock.Call
}
// CreateFileObject is a helper method to define mock.On call
// - ctx context.Context
// - param params.CreateFileObjectParams
// - reader io.Reader
func (_e *Store_Expecter) CreateFileObject(ctx interface{}, param interface{}, reader interface{}) *Store_CreateFileObject_Call {
return &Store_CreateFileObject_Call{Call: _e.mock.On("CreateFileObject", ctx, param, reader)}
}
func (_c *Store_CreateFileObject_Call) Run(run func(ctx context.Context, param params.CreateFileObjectParams, reader io.Reader)) *Store_CreateFileObject_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(params.CreateFileObjectParams), args[2].(io.Reader))
})
return _c
}
func (_c *Store_CreateFileObject_Call) Return(fileObjParam params.FileObject, err error) *Store_CreateFileObject_Call {
_c.Call.Return(fileObjParam, err)
return _c
}
func (_c *Store_CreateFileObject_Call) RunAndReturn(run func(context.Context, params.CreateFileObjectParams, io.Reader) (params.FileObject, error)) *Store_CreateFileObject_Call {
_c.Call.Return(run)
return _c
}
// CreateGiteaCredentials provides a mock function with given fields: ctx, param
func (_m *Store) CreateGiteaCredentials(ctx context.Context, param params.CreateGiteaCredentialsParams) (params.ForgeCredentials, error) {
ret := _m.Called(ctx, param)
@ -1180,6 +1240,53 @@ func (_c *Store_DeleteEntityPool_Call) RunAndReturn(run func(context.Context, pa
return _c
}
// DeleteFileObject provides a mock function with given fields: ctx, objID
func (_m *Store) DeleteFileObject(ctx context.Context, objID uint) error {
ret := _m.Called(ctx, objID)
if len(ret) == 0 {
panic("no return value specified for DeleteFileObject")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, uint) error); ok {
r0 = rf(ctx, objID)
} else {
r0 = ret.Error(0)
}
return r0
}
// Store_DeleteFileObject_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteFileObject'
type Store_DeleteFileObject_Call struct {
*mock.Call
}
// DeleteFileObject is a helper method to define mock.On call
// - ctx context.Context
// - objID uint
func (_e *Store_Expecter) DeleteFileObject(ctx interface{}, objID interface{}) *Store_DeleteFileObject_Call {
return &Store_DeleteFileObject_Call{Call: _e.mock.On("DeleteFileObject", ctx, objID)}
}
func (_c *Store_DeleteFileObject_Call) Run(run func(ctx context.Context, objID uint)) *Store_DeleteFileObject_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(uint))
})
return _c
}
func (_c *Store_DeleteFileObject_Call) Return(_a0 error) *Store_DeleteFileObject_Call {
_c.Call.Return(_a0)
return _c
}
func (_c *Store_DeleteFileObject_Call) RunAndReturn(run func(context.Context, uint) error) *Store_DeleteFileObject_Call {
_c.Call.Return(run)
return _c
}
// DeleteGiteaCredentials provides a mock function with given fields: ctx, id
func (_m *Store) DeleteGiteaCredentials(ctx context.Context, id uint) error {
ret := _m.Called(ctx, id)
@ -2035,6 +2142,63 @@ func (_c *Store_GetEntityPool_Call) RunAndReturn(run func(context.Context, param
return _c
}
// GetFileObject provides a mock function with given fields: ctx, objID
func (_m *Store) GetFileObject(ctx context.Context, objID uint) (params.FileObject, error) {
ret := _m.Called(ctx, objID)
if len(ret) == 0 {
panic("no return value specified for GetFileObject")
}
var r0 params.FileObject
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, uint) (params.FileObject, error)); ok {
return rf(ctx, objID)
}
if rf, ok := ret.Get(0).(func(context.Context, uint) params.FileObject); ok {
r0 = rf(ctx, objID)
} else {
r0 = ret.Get(0).(params.FileObject)
}
if rf, ok := ret.Get(1).(func(context.Context, uint) error); ok {
r1 = rf(ctx, objID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Store_GetFileObject_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFileObject'
type Store_GetFileObject_Call struct {
*mock.Call
}
// GetFileObject is a helper method to define mock.On call
// - ctx context.Context
// - objID uint
func (_e *Store_Expecter) GetFileObject(ctx interface{}, objID interface{}) *Store_GetFileObject_Call {
return &Store_GetFileObject_Call{Call: _e.mock.On("GetFileObject", ctx, objID)}
}
func (_c *Store_GetFileObject_Call) Run(run func(ctx context.Context, objID uint)) *Store_GetFileObject_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(uint))
})
return _c
}
func (_c *Store_GetFileObject_Call) Return(_a0 params.FileObject, _a1 error) *Store_GetFileObject_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *Store_GetFileObject_Call) RunAndReturn(run func(context.Context, uint) (params.FileObject, error)) *Store_GetFileObject_Call {
_c.Call.Return(run)
return _c
}
// GetForgeEntity provides a mock function with given fields: _a0, entityType, entityID
func (_m *Store) GetForgeEntity(_a0 context.Context, entityType params.ForgeEntityType, entityID string) (params.ForgeEntity, error) {
ret := _m.Called(_a0, entityType, entityID)
@ -3756,6 +3920,64 @@ func (_c *Store_ListEntityScaleSets_Call) RunAndReturn(run func(context.Context,
return _c
}
// ListFileObjects provides a mock function with given fields: ctx, page, pageSize
func (_m *Store) ListFileObjects(ctx context.Context, page uint64, pageSize uint64) (params.FileObjectPaginatedResponse, error) {
ret := _m.Called(ctx, page, pageSize)
if len(ret) == 0 {
panic("no return value specified for ListFileObjects")
}
var r0 params.FileObjectPaginatedResponse
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) (params.FileObjectPaginatedResponse, error)); ok {
return rf(ctx, page, pageSize)
}
if rf, ok := ret.Get(0).(func(context.Context, uint64, uint64) params.FileObjectPaginatedResponse); ok {
r0 = rf(ctx, page, pageSize)
} else {
r0 = ret.Get(0).(params.FileObjectPaginatedResponse)
}
if rf, ok := ret.Get(1).(func(context.Context, uint64, uint64) error); ok {
r1 = rf(ctx, page, pageSize)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Store_ListFileObjects_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListFileObjects'
type Store_ListFileObjects_Call struct {
*mock.Call
}
// ListFileObjects is a helper method to define mock.On call
// - ctx context.Context
// - page uint64
// - pageSize uint64
func (_e *Store_Expecter) ListFileObjects(ctx interface{}, page interface{}, pageSize interface{}) *Store_ListFileObjects_Call {
return &Store_ListFileObjects_Call{Call: _e.mock.On("ListFileObjects", ctx, page, pageSize)}
}
func (_c *Store_ListFileObjects_Call) Run(run func(ctx context.Context, page uint64, pageSize uint64)) *Store_ListFileObjects_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(uint64), args[2].(uint64))
})
return _c
}
func (_c *Store_ListFileObjects_Call) Return(_a0 params.FileObjectPaginatedResponse, _a1 error) *Store_ListFileObjects_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *Store_ListFileObjects_Call) RunAndReturn(run func(context.Context, uint64, uint64) (params.FileObjectPaginatedResponse, error)) *Store_ListFileObjects_Call {
_c.Call.Return(run)
return _c
}
// ListGiteaCredentials provides a mock function with given fields: ctx
func (_m *Store) ListGiteaCredentials(ctx context.Context) ([]params.ForgeCredentials, error) {
ret := _m.Called(ctx)
@ -4392,6 +4614,65 @@ func (_c *Store_LockJob_Call) RunAndReturn(run func(context.Context, int64, stri
return _c
}
// OpenFileObjectContent provides a mock function with given fields: ctx, objID
func (_m *Store) OpenFileObjectContent(ctx context.Context, objID uint) (io.ReadCloser, error) {
ret := _m.Called(ctx, objID)
if len(ret) == 0 {
panic("no return value specified for OpenFileObjectContent")
}
var r0 io.ReadCloser
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, uint) (io.ReadCloser, error)); ok {
return rf(ctx, objID)
}
if rf, ok := ret.Get(0).(func(context.Context, uint) io.ReadCloser); ok {
r0 = rf(ctx, objID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(io.ReadCloser)
}
}
if rf, ok := ret.Get(1).(func(context.Context, uint) error); ok {
r1 = rf(ctx, objID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Store_OpenFileObjectContent_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OpenFileObjectContent'
type Store_OpenFileObjectContent_Call struct {
*mock.Call
}
// OpenFileObjectContent is a helper method to define mock.On call
// - ctx context.Context
// - objID uint
func (_e *Store_Expecter) OpenFileObjectContent(ctx interface{}, objID interface{}) *Store_OpenFileObjectContent_Call {
return &Store_OpenFileObjectContent_Call{Call: _e.mock.On("OpenFileObjectContent", ctx, objID)}
}
func (_c *Store_OpenFileObjectContent_Call) Run(run func(ctx context.Context, objID uint)) *Store_OpenFileObjectContent_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(uint))
})
return _c
}
func (_c *Store_OpenFileObjectContent_Call) Return(_a0 io.ReadCloser, _a1 error) *Store_OpenFileObjectContent_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *Store_OpenFileObjectContent_Call) RunAndReturn(run func(context.Context, uint) (io.ReadCloser, error)) *Store_OpenFileObjectContent_Call {
_c.Call.Return(run)
return _c
}
// PoolInstanceCount provides a mock function with given fields: ctx, poolID
func (_m *Store) PoolInstanceCount(ctx context.Context, poolID string) (int64, error) {
ret := _m.Called(ctx, poolID)
@ -4449,6 +4730,65 @@ func (_c *Store_PoolInstanceCount_Call) RunAndReturn(run func(context.Context, s
return _c
}
// SearchFileObjectByTags provides a mock function with given fields: ctx, tags, page, pageSize
func (_m *Store) SearchFileObjectByTags(ctx context.Context, tags []string, page uint64, pageSize uint64) (params.FileObjectPaginatedResponse, error) {
ret := _m.Called(ctx, tags, page, pageSize)
if len(ret) == 0 {
panic("no return value specified for SearchFileObjectByTags")
}
var r0 params.FileObjectPaginatedResponse
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, []string, uint64, uint64) (params.FileObjectPaginatedResponse, error)); ok {
return rf(ctx, tags, page, pageSize)
}
if rf, ok := ret.Get(0).(func(context.Context, []string, uint64, uint64) params.FileObjectPaginatedResponse); ok {
r0 = rf(ctx, tags, page, pageSize)
} else {
r0 = ret.Get(0).(params.FileObjectPaginatedResponse)
}
if rf, ok := ret.Get(1).(func(context.Context, []string, uint64, uint64) error); ok {
r1 = rf(ctx, tags, page, pageSize)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Store_SearchFileObjectByTags_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SearchFileObjectByTags'
type Store_SearchFileObjectByTags_Call struct {
*mock.Call
}
// SearchFileObjectByTags is a helper method to define mock.On call
// - ctx context.Context
// - tags []string
// - page uint64
// - pageSize uint64
func (_e *Store_Expecter) SearchFileObjectByTags(ctx interface{}, tags interface{}, page interface{}, pageSize interface{}) *Store_SearchFileObjectByTags_Call {
return &Store_SearchFileObjectByTags_Call{Call: _e.mock.On("SearchFileObjectByTags", ctx, tags, page, pageSize)}
}
func (_c *Store_SearchFileObjectByTags_Call) Run(run func(ctx context.Context, tags []string, page uint64, pageSize uint64)) *Store_SearchFileObjectByTags_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].([]string), args[2].(uint64), args[3].(uint64))
})
return _c
}
func (_c *Store_SearchFileObjectByTags_Call) Return(_a0 params.FileObjectPaginatedResponse, _a1 error) *Store_SearchFileObjectByTags_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *Store_SearchFileObjectByTags_Call) RunAndReturn(run func(context.Context, []string, uint64, uint64) (params.FileObjectPaginatedResponse, error)) *Store_SearchFileObjectByTags_Call {
_c.Call.Return(run)
return _c
}
// SetScaleSetDesiredRunnerCount provides a mock function with given fields: ctx, scaleSetID, desiredRunnerCount
func (_m *Store) SetScaleSetDesiredRunnerCount(ctx context.Context, scaleSetID uint, desiredRunnerCount int) error {
ret := _m.Called(ctx, scaleSetID, desiredRunnerCount)
@ -4826,6 +5166,64 @@ func (_c *Store_UpdateEntityScaleSet_Call) RunAndReturn(run func(context.Context
return _c
}
// UpdateFileObject provides a mock function with given fields: ctx, objID, param
func (_m *Store) UpdateFileObject(ctx context.Context, objID uint, param params.UpdateFileObjectParams) (params.FileObject, error) {
ret := _m.Called(ctx, objID, param)
if len(ret) == 0 {
panic("no return value specified for UpdateFileObject")
}
var r0 params.FileObject
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, uint, params.UpdateFileObjectParams) (params.FileObject, error)); ok {
return rf(ctx, objID, param)
}
if rf, ok := ret.Get(0).(func(context.Context, uint, params.UpdateFileObjectParams) params.FileObject); ok {
r0 = rf(ctx, objID, param)
} else {
r0 = ret.Get(0).(params.FileObject)
}
if rf, ok := ret.Get(1).(func(context.Context, uint, params.UpdateFileObjectParams) error); ok {
r1 = rf(ctx, objID, param)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Store_UpdateFileObject_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateFileObject'
type Store_UpdateFileObject_Call struct {
*mock.Call
}
// UpdateFileObject is a helper method to define mock.On call
// - ctx context.Context
// - objID uint
// - param params.UpdateFileObjectParams
func (_e *Store_Expecter) UpdateFileObject(ctx interface{}, objID interface{}, param interface{}) *Store_UpdateFileObject_Call {
return &Store_UpdateFileObject_Call{Call: _e.mock.On("UpdateFileObject", ctx, objID, param)}
}
func (_c *Store_UpdateFileObject_Call) Run(run func(ctx context.Context, objID uint, param params.UpdateFileObjectParams)) *Store_UpdateFileObject_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(uint), args[2].(params.UpdateFileObjectParams))
})
return _c
}
func (_c *Store_UpdateFileObject_Call) Return(_a0 params.FileObject, _a1 error) *Store_UpdateFileObject_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *Store_UpdateFileObject_Call) RunAndReturn(run func(context.Context, uint, params.UpdateFileObjectParams) (params.FileObject, error)) *Store_UpdateFileObject_Call {
_c.Call.Return(run)
return _c
}
// UpdateGiteaCredentials provides a mock function with given fields: ctx, id, param
func (_m *Store) UpdateGiteaCredentials(ctx context.Context, id uint, param params.UpdateGiteaCredentialsParams) (params.ForgeCredentials, error) {
ret := _m.Called(ctx, id, param)

View file

@ -183,7 +183,7 @@ type FileObjectStore interface {
ListFileObjects(ctx context.Context, page, pageSize uint64) (params.FileObjectPaginatedResponse, error)
SearchFileObjectByTags(ctx context.Context, tags []string, page, pageSize uint64) (params.FileObjectPaginatedResponse, error)
GetFileObject(ctx context.Context, objID uint) (params.FileObject, error)
CreateFileObject(ctx context.Context, name string, size int64, tags []string, reader io.Reader) (params.FileObject, error)
CreateFileObject(ctx context.Context, param params.CreateFileObjectParams, reader io.Reader) (fileObjParam params.FileObject, err error)
UpdateFileObject(ctx context.Context, objID uint, param params.UpdateFileObjectParams) (params.FileObject, error)
DeleteFileObject(ctx context.Context, objID uint) error
OpenFileObjectContent(ctx context.Context, objID uint) (io.ReadCloser, error)

View file

@ -8,6 +8,7 @@ import (
"errors"
"fmt"
"io"
"math"
"github.com/mattn/go-sqlite3"
"gorm.io/gorm"
@ -18,17 +19,18 @@ import (
"github.com/cloudbase/garm/util"
)
func (s *sqlDatabase) CreateFileObject(ctx context.Context, name string, size int64, tags []string, reader io.Reader) (fileObjParam params.FileObject, err error) {
// func (s *sqlDatabase) CreateFileObject(ctx context.Context, name string, size int64, tags []string, reader io.Reader) (fileObjParam params.FileObject, err error) {
func (s *sqlDatabase) CreateFileObject(ctx context.Context, param params.CreateFileObjectParams, reader io.Reader) (fileObjParam params.FileObject, err error) {
// Read first 8KB for type detection
buffer := make([]byte, 8192)
n, _ := io.ReadFull(reader, buffer)
fileType := util.DetectFileType(buffer[:n])
// Create document with pre-allocated blob
fileObj := FileObject{
Name: name,
FileType: fileType,
Size: size,
Content: make([]byte, size),
Name: param.Name,
Description: param.Description,
FileType: fileType,
Size: param.Size,
}
defer func() {
@ -37,10 +39,19 @@ func (s *sqlDatabase) CreateFileObject(ctx context.Context, name string, size in
}
}()
// Create the file first, without any space allocated for the blob.
if err := s.conn.Create(&fileObj).Error; err != nil {
return params.FileObject{}, fmt.Errorf("failed to create file object: %w", err)
}
// allocate space for the blob using the zeroblob() function. This will allow us to avoid
// having to allocate potentially huge byte arrays in memory and writing that huge blob to
// disk.
query := fmt.Sprintf(`UPDATE %q SET content = zeroblob(?) WHERE id = ?`, fileObj.TableName())
if err := s.conn.Exec(query, param.Size, fileObj.ID).Error; err != nil {
return params.FileObject{}, fmt.Errorf("failed to allocate disk space: %w", err)
}
// Stream file to blob and compute SHA256
conn, err := s.sqlDB.Conn(ctx)
if err != nil {
@ -54,7 +65,7 @@ func (s *sqlDatabase) CreateFileObject(ctx context.Context, name string, size in
blob, err := sqliteConn.Blob("main", fileObj.TableName(), "content", int64(fileObj.ID), 1)
if err != nil {
return err
return fmt.Errorf("failed to open blob: %w", err)
}
defer blob.Close()
@ -63,14 +74,14 @@ func (s *sqlDatabase) CreateFileObject(ctx context.Context, name string, size in
// Write the buffered data first
if _, err := blob.Write(buffer[:n]); err != nil {
return err
return fmt.Errorf("failed to write blob initial buffer: %w", err)
}
hasher.Write(buffer[:n])
// Stream the rest with hash computation
_, err = io.Copy(io.MultiWriter(blob, hasher), reader)
if err != nil {
return err
return fmt.Errorf("failed to write blob: %w", err)
}
// Get final hash
@ -87,7 +98,7 @@ func (s *sqlDatabase) CreateFileObject(ctx context.Context, name string, size in
}
// Create tag entries
for _, tag := range tags {
for _, tag := range param.Tags {
fileObjTag := FileObjectTag{
FileObjectID: fileObj.ID,
Tag: tag,
@ -129,6 +140,10 @@ func (s *sqlDatabase) UpdateFileObject(_ context.Context, objID uint, param para
fileObj.Name = *param.Name
}
if param.Description != nil {
fileObj.Description = *param.Description
}
// Update tags if provided
if param.Tags != nil {
// Delete existing tags
@ -239,9 +254,20 @@ func (s *sqlDatabase) SearchFileObjectByTags(_ context.Context, tags []string, p
offset := (page - 1) * pageSize
queryPageSize := math.MaxInt
if pageSize <= math.MaxInt {
queryPageSize = int(pageSize)
}
var queryOffset int
if offset <= math.MaxInt {
queryOffset = int(offset)
} else {
return params.FileObjectPaginatedResponse{}, fmt.Errorf("offset excedes max int size: %d", math.MaxInt)
}
if err := query.
Limit(int(pageSize)).
Offset(int(offset)).
Limit(queryPageSize).
Offset(queryOffset).
Order("created_at DESC").
Omit("content").
Find(&fileObjectRes).Error; err != nil {
@ -343,10 +369,23 @@ func (s *sqlDatabase) ListFileObjects(_ context.Context, page, pageSize uint64)
}
offset := (page - 1) * pageSize
queryPageSize := math.MaxInt
if pageSize <= math.MaxInt {
queryPageSize = int(pageSize)
}
var queryOffset int
if offset <= math.MaxInt {
queryOffset = int(offset)
} else {
return params.FileObjectPaginatedResponse{}, fmt.Errorf("offset excedes max int size: %d", math.MaxInt)
}
var fileObjs []FileObject
if err := s.conn.Preload("TagsList").Omit("content").
Limit(int(pageSize)).
Offset(int(offset)).
Limit(queryPageSize).
Offset(queryOffset).
Order("created_at DESC").
Find(&fileObjs).Error; err != nil {
return params.FileObjectPaginatedResponse{}, fmt.Errorf("failed to list file objects: %w", err)
@ -384,13 +423,14 @@ func (s *sqlDatabase) sqlFileObjectToCommonParams(obj FileObject) params.FileObj
tags[idx] = val.Tag
}
return params.FileObject{
ID: obj.ID,
CreatedAt: obj.CreatedAt,
UpdatedAt: obj.UpdatedAt,
Name: obj.Name,
Size: obj.Size,
FileType: obj.FileType,
SHA256: obj.SHA256,
Tags: tags,
ID: obj.ID,
CreatedAt: obj.CreatedAt,
UpdatedAt: obj.UpdatedAt,
Name: obj.Name,
Size: obj.Size,
FileType: obj.FileType,
SHA256: obj.SHA256,
Description: obj.Description,
Tags: tags,
}
}

View file

@ -67,7 +67,12 @@ func (s *FileStoreTestSuite) SetupTest() {
// File 1: Small text file with tags
content1 := []byte("Hello, World! This is test file 1.")
fileObj1, err := s.Store.CreateFileObject(s.ctx, "test-file-1.txt", int64(len(content1)), []string{"test", "text"}, bytes.NewReader(content1))
param := params.CreateFileObjectParams{
Name: "test-file-1.txt",
Size: int64(len(content1)),
Tags: []string{"test", "text"},
}
fileObj1, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content1))
if err != nil {
s.FailNow(fmt.Sprintf("failed to create test file 1: %s", err))
}
@ -75,7 +80,12 @@ func (s *FileStoreTestSuite) SetupTest() {
// File 2: Binary-like content with different tags
content2 := []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00} // PNG header-like
fileObj2, err := s.Store.CreateFileObject(s.ctx, "test-image.png", int64(len(content2)), []string{"image", "binary"}, bytes.NewReader(content2))
param = params.CreateFileObjectParams{
Name: "test-image.png",
Size: int64(len(content2)),
Tags: []string{"image", "binary"},
}
fileObj2, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content2))
if err != nil {
s.FailNow(fmt.Sprintf("failed to create test file 2: %s", err))
}
@ -83,7 +93,12 @@ func (s *FileStoreTestSuite) SetupTest() {
// File 3: No tags
content3 := []byte("File without tags.")
fileObj3, err := s.Store.CreateFileObject(s.ctx, "no-tags.txt", int64(len(content3)), []string{}, bytes.NewReader(content3))
param = params.CreateFileObjectParams{
Name: "no-tags.txt",
Size: int64(len(content3)),
Tags: []string{},
}
fileObj3, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content3))
if err != nil {
s.FailNow(fmt.Sprintf("failed to create test file 3: %s", err))
}
@ -98,7 +113,12 @@ func (s *FileStoreTestSuite) TestCreateFileObject() {
content := []byte("New test file content")
tags := []string{"new", "test"}
fileObj, err := s.Store.CreateFileObject(s.ctx, "new-file.txt", int64(len(content)), tags, bytes.NewReader(content))
param := params.CreateFileObjectParams{
Name: "new-file.txt",
Size: int64(len(content)),
Tags: tags,
}
fileObj, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content))
s.Require().Nil(err)
s.Require().NotZero(fileObj.ID)
s.Require().Equal("new-file.txt", fileObj.Name)
@ -115,7 +135,12 @@ func (s *FileStoreTestSuite) TestCreateFileObject() {
func (s *FileStoreTestSuite) TestCreateFileObjectEmpty() {
content := []byte{}
fileObj, err := s.Store.CreateFileObject(s.ctx, "empty-file.txt", 0, []string{}, bytes.NewReader(content))
param := params.CreateFileObjectParams{
Name: "empty-file.txt",
Size: 0,
Tags: []string{},
}
fileObj, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content))
s.Require().Nil(err)
s.Require().NotZero(fileObj.ID)
s.Require().Equal("empty-file.txt", fileObj.Name)
@ -141,7 +166,12 @@ func (s *FileStoreTestSuite) TestGetFileObjectNotFound() {
func (s *FileStoreTestSuite) TestOpenFileObjectContent() {
// Create a file with known content
content := []byte("Test content for reading")
fileObj, err := s.Store.CreateFileObject(s.ctx, "read-test.txt", int64(len(content)), []string{"read"}, bytes.NewReader(content))
param := params.CreateFileObjectParams{
Name: "read-test.txt",
Size: int64(len(content)),
Tags: []string{"read"},
}
fileObj, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content))
s.Require().Nil(err)
// Open and read the content
@ -182,7 +212,12 @@ func (s *FileStoreTestSuite) TestListFileObjectsPagination() {
// Create more files to test pagination
for i := 0; i < 5; i++ {
content := []byte(fmt.Sprintf("File %d", i))
_, err := s.Store.CreateFileObject(s.ctx, fmt.Sprintf("page-test-%d.txt", i), int64(len(content)), []string{"pagination"}, bytes.NewReader(content))
param := params.CreateFileObjectParams{
Name: fmt.Sprintf("page-test-%d.txt", i),
Size: int64(len(content)),
Tags: []string{"pagination"},
}
_, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content))
s.Require().Nil(err)
}
@ -313,7 +348,12 @@ func (s *FileStoreTestSuite) TestUpdateFileObjectEmptyName() {
func (s *FileStoreTestSuite) TestDeleteFileObject() {
// Create a file to delete
content := []byte("To be deleted")
fileObj, err := s.Store.CreateFileObject(s.ctx, "delete-me.txt", int64(len(content)), []string{"delete"}, bytes.NewReader(content))
param := params.CreateFileObjectParams{
Name: "delete-me.txt",
Size: int64(len(content)),
Tags: []string{"delete"},
}
fileObj, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content))
s.Require().Nil(err)
// Delete the file
@ -339,8 +379,12 @@ func (s *FileStoreTestSuite) TestCreateFileObjectLargeContent() {
for i := range content {
content[i] = byte(i % 256)
}
fileObj, err := s.Store.CreateFileObject(s.ctx, "large-file.bin", int64(size), []string{"large", "binary"}, bytes.NewReader(content))
param := params.CreateFileObjectParams{
Name: "large-file.bin",
Size: int64(size),
Tags: []string{"large", "binary"},
}
fileObj, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content))
s.Require().Nil(err)
s.Require().Equal(int64(size), fileObj.Size)
@ -357,7 +401,12 @@ func (s *FileStoreTestSuite) TestCreateFileObjectLargeContent() {
func (s *FileStoreTestSuite) TestFileObjectImmutableFields() {
// Create a file
content := []byte("Immutable test content")
fileObj, err := s.Store.CreateFileObject(s.ctx, "immutable-test.txt", int64(len(content)), []string{"original"}, bytes.NewReader(content))
param := params.CreateFileObjectParams{
Name: "immutable-test.txt",
Size: int64(len(content)),
Tags: []string{"original"},
}
fileObj, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content))
s.Require().Nil(err)
originalSize := fileObj.Size
@ -390,19 +439,39 @@ func (s *FileStoreTestSuite) TestFileObjectImmutableFields() {
func (s *FileStoreTestSuite) TestSearchFileObjectByTags() {
// Create files with specific tags for searching
content1 := []byte("File with tag1 and tag2")
file1, err := s.Store.CreateFileObject(s.ctx, "search-file-1.txt", int64(len(content1)), []string{"tag1", "tag2"}, bytes.NewReader(content1))
param := params.CreateFileObjectParams{
Name: "search-file-1.txt",
Size: int64(len(content1)),
Tags: []string{"tag1", "tag2"},
}
file1, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content1))
s.Require().Nil(err)
content2 := []byte("File with tag1, tag2, and tag3")
file2, err := s.Store.CreateFileObject(s.ctx, "search-file-2.txt", int64(len(content2)), []string{"tag1", "tag2", "tag3"}, bytes.NewReader(content2))
param = params.CreateFileObjectParams{
Name: "search-file-2.txt",
Size: int64(len(content2)),
Tags: []string{"tag1", "tag2", "tag3"},
}
file2, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content2))
s.Require().Nil(err)
content3 := []byte("File with only tag1")
file3, err := s.Store.CreateFileObject(s.ctx, "search-file-3.txt", int64(len(content3)), []string{"tag1"}, bytes.NewReader(content3))
param = params.CreateFileObjectParams{
Name: "search-file-3.txt",
Size: int64(len(content3)),
Tags: []string{"tag1"},
}
file3, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content3))
s.Require().Nil(err)
content4 := []byte("File with tag3 only")
_, err = s.Store.CreateFileObject(s.ctx, "search-file-4.txt", int64(len(content4)), []string{"tag3"}, bytes.NewReader(content4))
param = params.CreateFileObjectParams{
Name: "search-file-4.txt",
Size: int64(len(content4)),
Tags: []string{"tag3"},
}
_, err = s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content4))
s.Require().Nil(err)
// Search for files with tag1 - should return 3 files
@ -423,15 +492,30 @@ func (s *FileStoreTestSuite) TestSearchFileObjectByTags() {
func (s *FileStoreTestSuite) TestSearchFileObjectByTagsMultipleTags() {
// Create files with various tag combinations
content1 := []byte("File with search1 and search2")
file1, err := s.Store.CreateFileObject(s.ctx, "multi-search-1.txt", int64(len(content1)), []string{"search1", "search2"}, bytes.NewReader(content1))
param := params.CreateFileObjectParams{
Name: "multi-search-1.txt",
Size: int64(len(content1)),
Tags: []string{"search1", "search2"},
}
file1, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content1))
s.Require().Nil(err)
content2 := []byte("File with search1, search2, and search3")
file2, err := s.Store.CreateFileObject(s.ctx, "multi-search-2.txt", int64(len(content2)), []string{"search1", "search2", "search3"}, bytes.NewReader(content2))
param = params.CreateFileObjectParams{
Name: "multi-search-2.txt",
Size: int64(len(content2)),
Tags: []string{"search1", "search2", "search3"},
}
file2, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content2))
s.Require().Nil(err)
content3 := []byte("File with only search1")
_, err = s.Store.CreateFileObject(s.ctx, "multi-search-3.txt", int64(len(content3)), []string{"search1"}, bytes.NewReader(content3))
param = params.CreateFileObjectParams{
Name: "multi-search-3.txt",
Size: int64(len(content3)),
Tags: []string{"search1"},
}
_, err = s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content3))
s.Require().Nil(err)
// Search for files with both search1 AND search2 - should return only 2 files
@ -469,7 +553,12 @@ func (s *FileStoreTestSuite) TestSearchFileObjectByTagsPagination() {
// Create multiple files with the same tag
for i := 0; i < 5; i++ {
content := []byte(fmt.Sprintf("Pagination test file %d", i))
_, err := s.Store.CreateFileObject(s.ctx, fmt.Sprintf("page-search-%d.txt", i), int64(len(content)), []string{"pagination-test"}, bytes.NewReader(content))
param := params.CreateFileObjectParams{
Name: fmt.Sprintf("page-search-%d.txt", i),
Size: int64(len(content)),
Tags: []string{"pagination-test"},
}
_, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content))
s.Require().Nil(err)
}
@ -497,7 +586,12 @@ func (s *FileStoreTestSuite) TestSearchFileObjectByTagsPagination() {
func (s *FileStoreTestSuite) TestSearchFileObjectByTagsDefaultPagination() {
// Create a file with a unique tag
content := []byte("Default pagination test")
_, err := s.Store.CreateFileObject(s.ctx, "default-page-search.txt", int64(len(content)), []string{"default-pagination"}, bytes.NewReader(content))
param := params.CreateFileObjectParams{
Name: "default-page-search.txt",
Size: int64(len(content)),
Tags: []string{"default-pagination"},
}
_, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content))
s.Require().Nil(err)
// Test default values (page 0 should become 1, pageSize 0 should become 20)
@ -511,19 +605,39 @@ func (s *FileStoreTestSuite) TestSearchFileObjectByTagsDefaultPagination() {
func (s *FileStoreTestSuite) TestSearchFileObjectByTagsAllTagsRequired() {
// Test that search requires ALL specified tags (AND logic, not OR)
content1 := []byte("Has A and B")
file1, err := s.Store.CreateFileObject(s.ctx, "and-test-1.txt", int64(len(content1)), []string{"tagA", "tagB"}, bytes.NewReader(content1))
param := params.CreateFileObjectParams{
Name: "and-test-1.txt",
Size: int64(len(content1)),
Tags: []string{"tagA", "tagB"},
}
file1, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content1))
s.Require().Nil(err)
content2 := []byte("Has A, B, and C")
file2, err := s.Store.CreateFileObject(s.ctx, "and-test-2.txt", int64(len(content2)), []string{"tagA", "tagB", "tagC"}, bytes.NewReader(content2))
param = params.CreateFileObjectParams{
Name: "and-test-2.txt",
Size: int64(len(content2)),
Tags: []string{"tagA", "tagB", "tagC"},
}
file2, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content2))
s.Require().Nil(err)
content3 := []byte("Has only A")
_, err = s.Store.CreateFileObject(s.ctx, "and-test-3.txt", int64(len(content3)), []string{"tagA"}, bytes.NewReader(content3))
param = params.CreateFileObjectParams{
Name: "and-test-3.txt",
Size: int64(len(content3)),
Tags: []string{"tagA"},
}
_, err = s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content3))
s.Require().Nil(err)
content4 := []byte("Has only B")
_, err = s.Store.CreateFileObject(s.ctx, "and-test-4.txt", int64(len(content4)), []string{"tagB"}, bytes.NewReader(content4))
param = params.CreateFileObjectParams{
Name: "and-test-4.txt",
Size: int64(len(content4)),
Tags: []string{"tagB"},
}
_, err = s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content4))
s.Require().Nil(err)
// Search for files with BOTH tagA AND tagB
@ -546,15 +660,30 @@ func (s *FileStoreTestSuite) TestSearchFileObjectByTagsAllTagsRequired() {
func (s *FileStoreTestSuite) TestSearchFileObjectByTagsCaseInsensitive() {
// Test case insensitivity of tag search (COLLATE NOCASE)
content1 := []byte("File with lowercase tag")
file1, err := s.Store.CreateFileObject(s.ctx, "case-test-1.txt", int64(len(content1)), []string{"TestTag"}, bytes.NewReader(content1))
param := params.CreateFileObjectParams{
Name: "case-test-1.txt",
Size: int64(len(content1)),
Tags: []string{"TestTag"},
}
file1, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content1))
s.Require().Nil(err)
content2 := []byte("File with UPPERCASE tag")
file2, err := s.Store.CreateFileObject(s.ctx, "case-test-2.txt", int64(len(content2)), []string{"TESTTAG"}, bytes.NewReader(content2))
param = params.CreateFileObjectParams{
Name: "case-test-2.txt",
Size: int64(len(content2)),
Tags: []string{"TESTTAG"},
}
file2, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content2))
s.Require().Nil(err)
content3 := []byte("File with MixedCase tag")
file3, err := s.Store.CreateFileObject(s.ctx, "case-test-3.txt", int64(len(content3)), []string{"testTAG"}, bytes.NewReader(content3))
param = params.CreateFileObjectParams{
Name: "case-test-3.txt",
Size: int64(len(content3)),
Tags: []string{"testTAG"},
}
file3, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content3))
s.Require().Nil(err)
// Search for lowercase - should return all files (case insensitive)
@ -586,15 +715,30 @@ func (s *FileStoreTestSuite) TestSearchFileObjectByTagsOrderByCreatedAt() {
tag := "order-test"
content1 := []byte("First file")
file1, err := s.Store.CreateFileObject(s.ctx, "order-1.txt", int64(len(content1)), []string{tag}, bytes.NewReader(content1))
param := params.CreateFileObjectParams{
Name: "order-1.txt",
Size: int64(len(content1)),
Tags: []string{tag},
}
file1, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content1))
s.Require().Nil(err)
content2 := []byte("Second file")
file2, err := s.Store.CreateFileObject(s.ctx, "order-2.txt", int64(len(content2)), []string{tag}, bytes.NewReader(content2))
param = params.CreateFileObjectParams{
Name: "order-2.txt",
Size: int64(len(content2)),
Tags: []string{tag},
}
file2, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content2))
s.Require().Nil(err)
content3 := []byte("Third file")
file3, err := s.Store.CreateFileObject(s.ctx, "order-3.txt", int64(len(content3)), []string{tag}, bytes.NewReader(content3))
param = params.CreateFileObjectParams{
Name: "order-3.txt",
Size: int64(len(content3)),
Tags: []string{tag},
}
file3, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content3))
s.Require().Nil(err)
// Search and verify order (should be DESC by created_at, so newest first)
@ -629,7 +773,12 @@ func (s *FileStoreTestSuite) TestPaginationFieldsLastPage() {
// Create exactly 5 files
for i := 0; i < 5; i++ {
content := []byte(fmt.Sprintf("Last page test %d", i))
_, err := s.Store.CreateFileObject(s.ctx, fmt.Sprintf("last-page-test-%d.txt", i), int64(len(content)), []string{"last-page"}, bytes.NewReader(content))
param := params.CreateFileObjectParams{
Name: fmt.Sprintf("last-page-test-%d.txt", i),
Size: int64(len(content)),
Tags: []string{"last-page"},
}
_, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content))
s.Require().Nil(err)
}
@ -649,7 +798,12 @@ func (s *FileStoreTestSuite) TestPaginationFieldsLastPage() {
func (s *FileStoreTestSuite) TestPaginationFieldsSinglePage() {
// Test when all results fit in a single page
content := []byte("Single page test")
_, err := s.Store.CreateFileObject(s.ctx, "single-page-test.txt", int64(len(content)), []string{"single-page-unique-tag"}, bytes.NewReader(content))
param := params.CreateFileObjectParams{
Name: "single-page-test.txt",
Size: int64(len(content)),
Tags: []string{"single-page-unique-tag"},
}
_, err := s.Store.CreateFileObject(s.ctx, param, bytes.NewReader(content))
s.Require().Nil(err)
result, err := s.Store.SearchFileObjectByTags(s.ctx, []string{"single-page-unique-tag"}, 1, 20)

View file

@ -51,20 +51,20 @@ type GithubTestSuite struct {
db common.Store
}
func (s *GithubTestSuite) TearDownTest() {
watcher.CloseWatcher()
}
func (s *GithubTestSuite) SetupTest() {
ctx := context.Background()
watcher.InitWatcher(ctx)
db, err := NewSQLDatabase(context.Background(), garmTesting.GetTestSqliteDBConfig(s.T()))
db, err := NewSQLDatabase(ctx, garmTesting.GetTestSqliteDBConfig(s.T()))
if err != nil {
s.FailNow(fmt.Sprintf("failed to create db connection: %s", err))
}
s.db = db
}
func (s *GithubTestSuite) TearDownTest() {
watcher.CloseWatcher()
}
func (s *GithubTestSuite) TestDefaultEndpointGetsCreatedAutomaticallyIfNoOtherEndpointExists() {
ctx := garmTesting.ImpersonateAdminContext(context.Background(), s.db, s.T())
endpoint, err := s.db.GetGithubEndpoint(ctx, defaultGithubEndpoint)
@ -953,9 +953,11 @@ func TestCredentialsAndEndpointMigration(t *testing.T) {
// Set the config credentials in the cfg. This is what happens in the main function.
// of GARM as well.
cfg.MigrateCredentials = credentials
ctx := context.Background()
watcher.InitWatcher(ctx)
defer watcher.CloseWatcher()
db, err := NewSQLDatabase(ctx, cfg)
if err != nil {
t.Fatalf("failed to create db connection: %s", err)

View file

@ -79,13 +79,13 @@ func (s *InstancesTestSuite) SetupTest() {
ctx := context.Background()
watcher.InitWatcher(ctx)
// create testing sqlite database
db, err := NewSQLDatabase(context.Background(), garmTesting.GetTestSqliteDBConfig(s.T()))
db, err := NewSQLDatabase(ctx, garmTesting.GetTestSqliteDBConfig(s.T()))
if err != nil {
s.FailNow(fmt.Sprintf("failed to create db connection: %s", err))
}
s.Store = db
adminCtx := garmTesting.ImpersonateAdminContext(context.Background(), db, s.T())
adminCtx := garmTesting.ImpersonateAdminContext(ctx, db, s.T())
s.adminCtx = adminCtx
githubEndpoint := garmTesting.CreateDefaultGithubEndpoint(adminCtx, db, s.T())

View file

@ -462,7 +462,9 @@ type GiteaCredentials struct {
type FileObject struct {
gorm.Model
// Name is the name of the file
Name string `gotm:"type:text,index:idx_fo_name"`
Name string `gorm:"type:text;index:idx_fo_name"`
// Description is a description for the file
Description string `gorm:"type:text"`
// FileType holds the MIME type or file type description
FileType string `gorm:"type:text"`
// Size is the file size in bytes

View file

@ -88,13 +88,13 @@ func (s *OrgTestSuite) SetupTest() {
watcher.InitWatcher(ctx)
// create testing sqlite database
dbConfig := garmTesting.GetTestSqliteDBConfig(s.T())
db, err := NewSQLDatabase(context.Background(), dbConfig)
db, err := NewSQLDatabase(ctx, dbConfig)
if err != nil {
s.FailNow(fmt.Sprintf("failed to create db connection: %s", err))
}
s.Store = db
adminCtx := garmTesting.ImpersonateAdminContext(context.Background(), db, s.T())
adminCtx := garmTesting.ImpersonateAdminContext(ctx, db, s.T())
s.adminCtx = adminCtx
s.adminUserID = auth.UserID(adminCtx)
s.Require().NotEmpty(s.adminUserID)

View file

@ -1308,15 +1308,17 @@ type Template struct {
// swagger:model Templates
type Templates []Template
// swagger:model FileObject
type FileObject struct {
ID uint `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Name string `json:"name"`
Size int64 `json:"size"`
Tags []string `json:"tags"`
SHA256 string `json:"sha256,omitempty"`
FileType string `json:"file_type"`
ID uint `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Size int64 `json:"size"`
Tags []string `json:"tags"`
SHA256 string `json:"sha256,omitempty"`
FileType string `json:"file_type"`
}
type PaginatedResponse[T any] struct {

View file

@ -867,9 +867,11 @@ func (u *UpdateTemplateParams) Validate() error {
return nil
}
// swagger:model UpdateFileObjectParams
type UpdateFileObjectParams struct {
Name *string `json:"name"`
Tags []string `json:"tags"`
Name *string `json:"name"`
Description *string `json:"description"`
Tags []string `json:"tags"`
}
func (u *UpdateFileObjectParams) Validate() error {
@ -879,3 +881,11 @@ func (u *UpdateFileObjectParams) Validate() error {
return nil
}
// swagger:model CreateFileObjectParams
type CreateFileObjectParams struct {
Name string `json:"name"`
Description string `json:"description"`
Size int64 `json:"size"`
Tags []string `json:"tags"`
}

102
runner/object_store.go Normal file
View file

@ -0,0 +1,102 @@
// Copyright 2025 Cloudbase Solutions SRL
//
// 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 runner
import (
"context"
"fmt"
"io"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/auth"
"github.com/cloudbase/garm/params"
)
func (r *Runner) CreateFileObject(ctx context.Context, param params.CreateFileObjectParams, reader io.Reader) (params.FileObject, error) {
if !auth.IsAdmin(ctx) {
return params.FileObject{}, runnerErrors.ErrUnauthorized
}
fileObj, err := r.store.CreateFileObject(ctx, param, reader)
if err != nil {
return params.FileObject{}, fmt.Errorf("failed to create file object: %w", err)
}
return fileObj, nil
}
func (r *Runner) GetFileObject(ctx context.Context, objID uint) (params.FileObject, error) {
if !auth.IsAdmin(ctx) {
return params.FileObject{}, runnerErrors.ErrUnauthorized
}
fileObj, err := r.store.GetFileObject(ctx, objID)
if err != nil {
return params.FileObject{}, fmt.Errorf("failed to get file object: %w", err)
}
return fileObj, nil
}
func (r *Runner) DeleteFileObject(ctx context.Context, objID uint) error {
if !auth.IsAdmin(ctx) {
return runnerErrors.ErrUnauthorized
}
if err := r.store.DeleteFileObject(ctx, objID); err != nil {
return fmt.Errorf("failed to delete file object: %w", err)
}
return nil
}
func (r *Runner) ListFileObjects(ctx context.Context, page, pageSize uint64, tags []string) (params.FileObjectPaginatedResponse, error) {
if !auth.IsAdmin(ctx) {
return params.FileObjectPaginatedResponse{}, runnerErrors.ErrUnauthorized
}
var resp params.FileObjectPaginatedResponse
var err error
if len(tags) == 0 {
resp, err = r.store.ListFileObjects(ctx, page, pageSize)
} else {
resp, err = r.store.SearchFileObjectByTags(ctx, tags, page, pageSize)
}
if err != nil {
return params.FileObjectPaginatedResponse{}, fmt.Errorf("failed to list objects: %w", err)
}
return resp, nil
}
func (r *Runner) UpdateFileObject(ctx context.Context, objID uint, param params.UpdateFileObjectParams) (params.FileObject, error) {
if !auth.IsAdmin(ctx) {
return params.FileObject{}, runnerErrors.ErrUnauthorized
}
resp, err := r.store.UpdateFileObject(ctx, objID, param)
if err != nil {
return params.FileObject{}, fmt.Errorf("failed to update object: %w", err)
}
return resp, nil
}
func (r *Runner) GetFileObjectReader(ctx context.Context, objID uint) (io.ReadCloser, error) {
if !auth.IsAdmin(ctx) {
return nil, runnerErrors.ErrUnauthorized
}
readCloser, err := r.store.OpenFileObjectContent(ctx, objID)
if err != nil {
return nil, fmt.Errorf("failed to open file object: %w", err)
}
return readCloser, nil
}

353
runner/object_store_test.go Normal file
View file

@ -0,0 +1,353 @@
// Copyright 2025 Cloudbase Solutions SRL
//
// 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.
//go:build testing
// +build testing
package runner
import (
"bytes"
"context"
"fmt"
"io"
"testing"
"github.com/stretchr/testify/suite"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/database"
dbCommon "github.com/cloudbase/garm/database/common"
garmTesting "github.com/cloudbase/garm/internal/testing"
"github.com/cloudbase/garm/params"
)
type ObjectStoreTestFixtures struct {
AdminContext context.Context
UnauthorizedContext context.Context
Store dbCommon.Store
CreateObjectParams params.CreateFileObjectParams
UpdateObjectParams params.UpdateFileObjectParams
TestFileObject params.FileObject
TestFileContent []byte
}
type ObjectStoreTestSuite struct {
suite.Suite
Fixtures *ObjectStoreTestFixtures
Runner *Runner
}
func (s *ObjectStoreTestSuite) SetupTest() {
// create testing sqlite database
dbCfg := garmTesting.GetTestSqliteDBConfig(s.T())
db, err := database.NewDatabase(context.Background(), dbCfg)
if err != nil {
s.FailNow(fmt.Sprintf("failed to create db connection: %s", err))
}
adminCtx := garmTesting.ImpersonateAdminContext(context.Background(), db, s.T())
// Create a test file object
testContent := []byte("test file content for object store")
param := params.CreateFileObjectParams{
Name: "test-file.bin",
Size: int64(len(testContent)),
Tags: []string{"test", "binary"},
}
fileObj, err := db.CreateFileObject(adminCtx, param, bytes.NewReader(testContent))
if err != nil {
s.FailNow(fmt.Sprintf("failed to create test file object: %s", err))
}
updatedName := "updated-file.txt"
// Setup fixtures
fixtures := &ObjectStoreTestFixtures{
AdminContext: adminCtx,
UnauthorizedContext: context.Background(),
Store: db,
CreateObjectParams: params.CreateFileObjectParams{
Name: "new-file.txt",
Size: 100,
Tags: []string{"new", "test"},
},
UpdateObjectParams: params.UpdateFileObjectParams{
Name: &updatedName,
Tags: []string{"updated", "test"},
},
TestFileObject: fileObj,
TestFileContent: testContent,
}
s.Fixtures = fixtures
// Setup test runner
runner := &Runner{
ctx: fixtures.AdminContext,
store: fixtures.Store,
}
s.Runner = runner
}
func (s *ObjectStoreTestSuite) TestCreateFileObject() {
content := []byte("new file content")
reader := bytes.NewReader(content)
createParams := params.CreateFileObjectParams{
Name: "create-test.txt",
Size: int64(len(content)),
Tags: []string{"create", "test"},
}
fileObj, err := s.Runner.CreateFileObject(s.Fixtures.AdminContext, createParams, reader)
s.Require().Nil(err)
s.Require().NotEmpty(fileObj.ID)
s.Require().Equal(createParams.Name, fileObj.Name)
s.Require().Equal(createParams.Size, fileObj.Size)
s.Require().ElementsMatch(createParams.Tags, fileObj.Tags)
s.Require().NotEmpty(fileObj.SHA256)
}
func (s *ObjectStoreTestSuite) TestCreateFileObjectUnauthorized() {
content := []byte("unauthorized content")
reader := bytes.NewReader(content)
_, err := s.Runner.CreateFileObject(s.Fixtures.UnauthorizedContext, s.Fixtures.CreateObjectParams, reader)
s.Require().NotNil(err)
s.Require().ErrorIs(err, runnerErrors.ErrUnauthorized)
}
func (s *ObjectStoreTestSuite) TestGetFileObject() {
fileObj, err := s.Runner.GetFileObject(s.Fixtures.AdminContext, s.Fixtures.TestFileObject.ID)
s.Require().Nil(err)
s.Require().Equal(s.Fixtures.TestFileObject.ID, fileObj.ID)
s.Require().Equal(s.Fixtures.TestFileObject.Name, fileObj.Name)
s.Require().Equal(s.Fixtures.TestFileObject.Size, fileObj.Size)
s.Require().ElementsMatch(s.Fixtures.TestFileObject.Tags, fileObj.Tags)
}
func (s *ObjectStoreTestSuite) TestGetFileObjectUnauthorized() {
_, err := s.Runner.GetFileObject(s.Fixtures.UnauthorizedContext, s.Fixtures.TestFileObject.ID)
s.Require().NotNil(err)
s.Require().ErrorIs(err, runnerErrors.ErrUnauthorized)
}
func (s *ObjectStoreTestSuite) TestGetFileObjectNotFound() {
_, err := s.Runner.GetFileObject(s.Fixtures.AdminContext, 99999)
s.Require().NotNil(err)
s.Require().Contains(err.Error(), "failed to get file object")
}
func (s *ObjectStoreTestSuite) TestDeleteFileObject() {
// Create a file to delete
content := []byte("file to delete")
param := params.CreateFileObjectParams{
Name: "delete-test.txt",
Size: int64(len(content)),
Tags: []string{"delete"},
}
fileObj, err := s.Fixtures.Store.CreateFileObject(s.Fixtures.AdminContext, param, bytes.NewReader(content))
s.Require().Nil(err)
err = s.Runner.DeleteFileObject(s.Fixtures.AdminContext, fileObj.ID)
s.Require().Nil(err)
// Verify it's deleted
_, err = s.Fixtures.Store.GetFileObject(s.Fixtures.AdminContext, fileObj.ID)
s.Require().NotNil(err)
}
func (s *ObjectStoreTestSuite) TestDeleteFileObjectUnauthorized() {
err := s.Runner.DeleteFileObject(s.Fixtures.UnauthorizedContext, s.Fixtures.TestFileObject.ID)
s.Require().NotNil(err)
s.Require().ErrorIs(err, runnerErrors.ErrUnauthorized)
}
func (s *ObjectStoreTestSuite) TestDeleteFileObjectNotFound() {
// Delete of non-existent object is a noop and returns nil (idempotent)
err := s.Runner.DeleteFileObject(s.Fixtures.AdminContext, 99999)
s.Require().Nil(err)
}
func (s *ObjectStoreTestSuite) TestListFileObjects() {
// Create additional test files
for i := 1; i <= 3; i++ {
content := []byte(fmt.Sprintf("test file %d", i))
param := params.CreateFileObjectParams{
Name: fmt.Sprintf("list-test-%d.txt", i),
Size: int64(len(content)),
Tags: []string{"list", "test"},
}
_, err := s.Fixtures.Store.CreateFileObject(
s.Fixtures.AdminContext,
param,
bytes.NewReader(content),
)
s.Require().Nil(err)
}
resp, err := s.Runner.ListFileObjects(s.Fixtures.AdminContext, 0, 25, nil)
s.Require().Nil(err)
s.Require().NotNil(resp.Results)
s.Require().GreaterOrEqual(len(resp.Results), 4) // At least the test file + 3 new ones
}
func (s *ObjectStoreTestSuite) TestListFileObjectsUnauthorized() {
_, err := s.Runner.ListFileObjects(s.Fixtures.UnauthorizedContext, 0, 25, nil)
s.Require().NotNil(err)
s.Require().ErrorIs(err, runnerErrors.ErrUnauthorized)
}
func (s *ObjectStoreTestSuite) TestListFileObjectsWithTags() {
// Create files with specific tags
specificTag := "specific-list-tag"
for i := 1; i <= 2; i++ {
content := []byte(fmt.Sprintf("tagged file %d", i))
param := params.CreateFileObjectParams{
Name: fmt.Sprintf("tagged-list-%d.txt", i),
Size: int64(len(content)),
Tags: []string{specificTag, "test"},
}
_, err := s.Fixtures.Store.CreateFileObject(
s.Fixtures.AdminContext,
param,
bytes.NewReader(content),
)
s.Require().Nil(err)
}
resp, err := s.Runner.ListFileObjects(s.Fixtures.AdminContext, 0, 25, []string{specificTag})
s.Require().Nil(err)
s.Require().NotNil(resp.Results)
s.Require().GreaterOrEqual(len(resp.Results), 2)
// Verify all results have the specific tag
for _, obj := range resp.Results {
s.Require().Contains(obj.Tags, specificTag)
}
}
func (s *ObjectStoreTestSuite) TestListFileObjectsPagination() {
// Create multiple files for pagination test
for i := 1; i <= 10; i++ {
content := []byte(fmt.Sprintf("pagination file %d", i))
param := params.CreateFileObjectParams{
Name: fmt.Sprintf("page-test-%d.txt", i),
Size: int64(len(content)),
Tags: []string{"pagination"},
}
_, err := s.Fixtures.Store.CreateFileObject(
s.Fixtures.AdminContext,
param,
bytes.NewReader(content),
)
s.Require().Nil(err)
}
// Get first page
resp1, err := s.Runner.ListFileObjects(s.Fixtures.AdminContext, 1, 5, []string{"pagination"})
s.Require().Nil(err)
s.Require().Len(resp1.Results, 5)
// Get second page
resp2, err := s.Runner.ListFileObjects(s.Fixtures.AdminContext, 2, 5, []string{"pagination"})
s.Require().Nil(err)
s.Require().Len(resp2.Results, 5)
// Verify different results on different pages
s.Require().NotEqual(resp1.Results[0].ID, resp2.Results[0].ID)
}
func (s *ObjectStoreTestSuite) TestUpdateFileObject() {
// Create a file to update
content := []byte("original content")
param := params.CreateFileObjectParams{
Name: "update-test.txt",
Size: int64(len(content)),
Tags: []string{"original"},
}
fileObj, err := s.Fixtures.Store.CreateFileObject(
s.Fixtures.AdminContext,
param,
bytes.NewReader(content),
)
s.Require().Nil(err)
newName := "updated-name.txt"
updateParams := params.UpdateFileObjectParams{
Name: &newName,
Tags: []string{"updated", "modified"},
}
updatedObj, err := s.Runner.UpdateFileObject(s.Fixtures.AdminContext, fileObj.ID, updateParams)
s.Require().Nil(err)
s.Require().Equal(*updateParams.Name, updatedObj.Name)
s.Require().ElementsMatch(updateParams.Tags, updatedObj.Tags)
s.Require().Equal(fileObj.ID, updatedObj.ID)
}
func (s *ObjectStoreTestSuite) TestUpdateFileObjectUnauthorized() {
_, err := s.Runner.UpdateFileObject(s.Fixtures.UnauthorizedContext, s.Fixtures.TestFileObject.ID, s.Fixtures.UpdateObjectParams)
s.Require().NotNil(err)
s.Require().ErrorIs(err, runnerErrors.ErrUnauthorized)
}
func (s *ObjectStoreTestSuite) TestUpdateFileObjectNotFound() {
_, err := s.Runner.UpdateFileObject(s.Fixtures.AdminContext, 99999, s.Fixtures.UpdateObjectParams)
s.Require().NotNil(err)
s.Require().Contains(err.Error(), "failed to update object")
}
func (s *ObjectStoreTestSuite) TestGetFileObjectReader() {
reader, err := s.Runner.GetFileObjectReader(s.Fixtures.AdminContext, s.Fixtures.TestFileObject.ID)
s.Require().Nil(err)
s.Require().NotNil(reader)
defer reader.Close()
// Read the content
content, err := io.ReadAll(reader)
s.Require().Nil(err)
s.Require().Equal(s.Fixtures.TestFileContent, content)
}
func (s *ObjectStoreTestSuite) TestGetFileObjectReaderUnauthorized() {
_, err := s.Runner.GetFileObjectReader(s.Fixtures.UnauthorizedContext, s.Fixtures.TestFileObject.ID)
s.Require().NotNil(err)
s.Require().ErrorIs(err, runnerErrors.ErrUnauthorized)
}
func (s *ObjectStoreTestSuite) TestGetFileObjectReaderNotFound() {
_, err := s.Runner.GetFileObjectReader(s.Fixtures.AdminContext, 99999)
s.Require().NotNil(err)
s.Require().Contains(err.Error(), "failed to open file object")
}
func TestObjectStoreTestSuite(t *testing.T) {
suite.Run(t, new(ObjectStoreTestSuite))
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
import"./DsnmJJEf.js";import{i as H}from"./TJn6xDN9.js";import{p as J,l as j,h as K,f as g,t as h,b as k,c as L,s as u,d as r,k as z,m as E,r as a,q as l,u as p,g as _,e as y}from"./DniTuB_0.js";import{p as d,i as I}from"./DbNhg6mG.js";import{c as M,s as P,d as n}from"./DSfKzFV1.js";var Q=g('<div class="group relative flex-shrink-0"><svg class="w-4 h-4 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> <div class="absolute left-0 top-full mt-1 z-10 invisible group-hover:visible opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none w-64"><div class="bg-gray-900 dark:bg-gray-700 text-white text-xs rounded-md px-3 py-2 shadow-lg"><div class="font-semibold mb-1">Description:</div> <div class="whitespace-pre-wrap break-words max-h-32 overflow-y-auto"> </div></div></div></div>'),S=g('<div class="text-sm text-gray-500 dark:text-gray-400 truncate"> </div>'),V=g('<div class="w-full min-w-0 text-sm font-medium"><div class="flex items-center gap-1.5"><a> </a> <!></div> <!></div>');function te(C,i){J(i,!1);const m=E(),b=E();let e=d(i,"item",8),s=d(i,"entityType",8,"repository"),N=d(i,"showOwner",8,!1),O=d(i,"showId",8,!1),T=d(i,"fontMono",8,!1);function q(){if(!e())return"Unknown";switch(s()){case"repository":return N()?`${e().owner||"Unknown"}/${e().name||"Unknown"}`:e().name||"Unknown";case"organization":case"enterprise":return e().name||"Unknown";case"pool":return O()?e().id||"Unknown":e().name||"Unknown";case"scaleset":return e().name||"Unknown";case"instance":return e().name||"Unknown";case"template":return e().name||"Unknown";case"object":return e().name||"Unknown";default:return e().name||e().id||"Unknown"}}function B(){if(!e())return"#";let t;switch(s()){case"instance":t=e().name;break;default:t=e().id||e().name;break}if(!t)return"#";switch(s()){case"repository":return n(`/repositories/${t}`);case"organization":return n(`/organizations/${t}`);case"enterprise":return n(`/enterprises/${t}`);case"pool":return n(`/pools/${t}`);case"scaleset":return n(`/scalesets/${t}`);case"instance":return n(`/instances/${encodeURIComponent(t)}`);case"template":return n(`/templates/${t}`);case"object":return n(`/objects/${t}`);default:return"#"}}j(()=>{},()=>{z(m,q())}),j(()=>{},()=>{z(b,B())}),K(),H();var f=V(),w=r(f),c=r(w),D=r(c,!0);a(c);var R=u(c,2);{var $=t=>{var o=Q(),v=u(r(o),2),x=r(v),U=u(r(x),2),G=r(U,!0);a(U),a(x),a(v),a(o),h(()=>y(G,(l(e()),p(()=>e().description)))),k(t,o)};I(R,t=>{l(s()),l(e()),p(()=>s()==="object"&&e()?.description)&&t($)})}a(w);var A=u(w,2);{var F=t=>{var o=S(),v=r(o,!0);a(o),h(()=>y(v,(l(e()),p(()=>e().provider_id)))),k(t,o)};I(A,t=>{l(s()),l(e()),p(()=>s()==="instance"&&e()?.provider_id)&&t(F)})}a(f),h(()=>{M(c,"href",_(b)),P(c,1,`truncate text-blue-600 dark:text-blue-400 hover:text-blue-500 dark:hover:text-blue-300 ${T()?"font-mono":""}`),M(c,"title",_(m)),y(D,_(m))}),k(C,f),L()}export{te as E};

View file

@ -1 +1 @@
import"./DsnmJJEf.js";import{i as D}from"./TJn6xDN9.js";import{p as P,f as I,d as s,r as n,s as u,u as l,q as i,t as w,e as S,b as N,c as A}from"./DniTuB_0.js";import{d as f,c as F}from"./BZ2rxtTc.js";import{p as d}from"./DbNhg6mG.js";import{D as E,G,A as j}from"./CKaB5fL4.js";import{E as q}from"./Dh8uHEF5.js";import{S as g}from"./C1GM4Goa.js";var L=I('<div class="bg-white dark:bg-gray-800 shadow rounded-lg"><div class="px-4 py-5 sm:p-6"><div class="flex items-center justify-between mb-4"><h2 class="text-lg font-medium text-gray-900 dark:text-white"> </h2> <a class="text-sm text-blue-600 dark:text-blue-400 hover:text-blue-500 dark:hover:text-blue-300">View all instances</a></div> <!></div></div>');function O(y,a){P(a,!1);let e=d(a,"instances",8),h=d(a,"entityType",8),v=d(a,"onDeleteInstance",8);const b=[{key:"name",title:"Name",cellComponent:q,cellProps:{entityType:"instance",nameField:"name"}},{key:"status",title:"Status",cellComponent:g,cellProps:{statusType:"instance",statusField:"status"}},{key:"runner_status",title:"Runner Status",cellComponent:g,cellProps:{statusType:"instance",statusField:"runner_status"}},{key:"created",title:"Created",cellComponent:G,cellProps:{field:"created_at",type:"date"}},{key:"actions",title:"Actions",align:"right",cellComponent:j,cellProps:{actions:[{type:"delete",label:"Delete",title:"Delete instance",ariaLabel:"Delete instance",action:"delete"}]}}],x={entityType:"instance",primaryText:{field:"name",isClickable:!0,href:"/instances/{name}"},secondaryText:{field:"provider_id"},badges:[{type:"status",field:"status"}],actions:[{type:"delete",handler:t=>m(t)}]};function m(t){v()(t)}function C(t){m(t.detail.item)}D();var r=L(),p=s(r),o=s(p),c=s(o),T=s(c);n(c);var _=u(c,2);n(o);var k=u(o,2);E(k,{get columns(){return b},get data(){return e()},loading:!1,error:"",searchTerm:"",showSearch:!1,showPagination:!1,currentPage:1,get perPage(){return i(e()),l(()=>e().length)},totalPages:1,get totalItems(){return i(e()),l(()=>e().length)},itemName:"instances",emptyTitle:"No instances running",get emptyMessage(){return`No instances running for this ${h()??""}.`},emptyIconType:"cog",get mobileCardConfig(){return x},$$events:{delete:C}}),n(p),n(r),w(t=>{S(T,`Instances (${i(e()),l(()=>e().length)??""})`),F(_,"href",t)},[()=>(i(f),l(()=>f("/instances")))]),N(y,r),A()}export{O as I};
import"./DsnmJJEf.js";import{i as D}from"./TJn6xDN9.js";import{p as P,f as I,d as s,r as n,s as u,u as l,q as i,t as w,e as S,b as N,c as A}from"./DniTuB_0.js";import{d as f,c as F}from"./DSfKzFV1.js";import{p as d}from"./DbNhg6mG.js";import{D as E,G,A as j}from"./BlJ06z1t.js";import{E as q}from"./30NlHgQ_.js";import{S as g}from"./BsX_CrDw.js";var L=I('<div class="bg-white dark:bg-gray-800 shadow rounded-lg"><div class="px-4 py-5 sm:p-6"><div class="flex items-center justify-between mb-4"><h2 class="text-lg font-medium text-gray-900 dark:text-white"> </h2> <a class="text-sm text-blue-600 dark:text-blue-400 hover:text-blue-500 dark:hover:text-blue-300">View all instances</a></div> <!></div></div>');function O(y,a){P(a,!1);let e=d(a,"instances",8),h=d(a,"entityType",8),v=d(a,"onDeleteInstance",8);const b=[{key:"name",title:"Name",cellComponent:q,cellProps:{entityType:"instance",nameField:"name"}},{key:"status",title:"Status",cellComponent:g,cellProps:{statusType:"instance",statusField:"status"}},{key:"runner_status",title:"Runner Status",cellComponent:g,cellProps:{statusType:"instance",statusField:"runner_status"}},{key:"created",title:"Created",cellComponent:G,cellProps:{field:"created_at",type:"date"}},{key:"actions",title:"Actions",align:"right",cellComponent:j,cellProps:{actions:[{type:"delete",label:"Delete",title:"Delete instance",ariaLabel:"Delete instance",action:"delete"}]}}],x={entityType:"instance",primaryText:{field:"name",isClickable:!0,href:"/instances/{name}"},secondaryText:{field:"provider_id"},badges:[{type:"status",field:"status"}],actions:[{type:"delete",handler:t=>m(t)}]};function m(t){v()(t)}function C(t){m(t.detail.item)}D();var r=L(),p=s(r),o=s(p),c=s(o),T=s(c);n(c);var _=u(c,2);n(o);var k=u(o,2);E(k,{get columns(){return b},get data(){return e()},loading:!1,error:"",searchTerm:"",showSearch:!1,showPagination:!1,currentPage:1,get perPage(){return i(e()),l(()=>e().length)},totalPages:1,get totalItems(){return i(e()),l(()=>e().length)},itemName:"instances",emptyTitle:"No instances running",get emptyMessage(){return`No instances running for this ${h()??""}.`},emptyIconType:"cog",get mobileCardConfig(){return x},$$events:{delete:C}}),n(p),n(r),w(t=>{S(T,`Instances (${i(e()),l(()=>e().length)??""})`),F(_,"href",t)},[()=>(i(f),l(()=>f("/instances")))]),N(y,r),A()}export{O as I};

View file

@ -0,0 +1,4 @@
import{d as o}from"./DSfKzFV1.js";function l(r){if(!r)return"N/A";try{return(typeof r=="string"?new Date(r):r).toLocaleString()}catch{return"Invalid Date"}}function f(r,e="w-4 h-4"){return r==="gitea"?`<svg class="${e}" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 640 640"><path d="m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12" style="fill:#fff"/><path d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6M125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1m300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1" style="fill:#609926"/><path d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8s2 16.3 9.1 20c7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3s17.4 1.7 22.5-5.3c5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8z" style="fill:#609926"/></svg>`:r==="github"?`<div class="inline-flex ${e}"><svg class="${e} dark:hidden" width="98" height="96" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 98 96"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#24292f"/></svg><svg class="${e} hidden dark:block" width="98" height="96" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 98 96"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#fff"/></svg></div>`:`<svg class="${e} text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>`}function d(r,e){if(r.repo_name)return r.repo_name;if(r.org_name)return r.org_name;if(r.enterprise_name)return r.enterprise_name;if(r.repo_id&&!r.repo_name&&e?.repositories){const n=e.repositories.find(t=>t.id===r.repo_id);return n?`${n.owner}/${n.name}`:"Unknown Entity"}if(r.org_id&&!r.org_name&&e?.organizations){const n=e.organizations.find(t=>t.id===r.org_id);return n&&n.name?n.name:"Unknown Entity"}if(r.enterprise_id&&!r.enterprise_name&&e?.enterprises){const n=e.enterprises.find(t=>t.id===r.enterprise_id);return n&&n.name?n.name:"Unknown Entity"}return"Unknown Entity"}function p(r){return r.repo_id?"repository":r.org_id?"organization":r.enterprise_id?"enterprise":"unknown"}function g(r){return r.repo_id?o(`/repositories/${r.repo_id}`):r.org_id?o(`/organizations/${r.org_id}`):r.enterprise_id?o(`/enterprises/${r.enterprise_id}`):"#"}function w(r){r&&(r.scrollTop=r.scrollHeight)}function m(r){return{newPerPage:r,newCurrentPage:1}}function v(r){return r.pool_manager_status?.running?{text:"Running",variant:"success"}:{text:"Stopped",variant:"error"}}function _(r){switch(r.toLowerCase()){case"error":return{text:"Error",variant:"error"};case"warning":return{text:"Warning",variant:"warning"};case"info":return{text:"Info",variant:"info"};default:return{text:r,variant:"info"}}}function i(r,e,n){if(!e.trim())return r;const t=e.toLowerCase();return r.filter(s=>typeof n=="function"?n(s).toLowerCase().includes(t):n.some(a=>s[a]?.toString().toLowerCase().includes(t)))}function h(r,e){return i(r,e,["name","owner"])}function x(r,e){return i(r,e,["name"])}function k(r,e){return i(r,e,n=>[n.name||"",n.description||"",n.endpoint?.name||""].join(" "))}function E(r,e){return i(r,e,["name","description","base_url","api_base_url"])}function L(r,e,n){return r.slice((e-1)*n,e*n)}export{E as a,l as b,m as c,_ as d,d as e,k as f,f as g,i as h,p as i,g as j,v as k,x as l,h as m,L as p,w as s};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
import"./DsnmJJEf.js";import{i as v}from"./TJn6xDN9.js";import{p as w,l as m,q as s,g as r,m as g,h as x,A as h,a as T,b as S,c as A,k,u}from"./DniTuB_0.js";import{k as B}from"./BlJ06z1t.js";import{p}from"./DbNhg6mG.js";import{B as C}from"./Dyp1NLoF.js";import{k as b}from"./BC4TpgBU.js";import{f as E}from"./ow_oMtSd.js";function z(_,i){w(i,!1);const c=g(),n=g();let e=p(i,"item",8),d=p(i,"statusType",8,"entity"),a=p(i,"statusField",8,"status");m(()=>(s(e()),s(a())),()=>{k(c,e()?.[a()]||"unknown")}),m(()=>(s(e()),s(d()),r(c),s(a())),()=>{k(n,(()=>{if(!e())return{variant:"error",text:"Unknown"};switch(d()){case"entity":return b(e());case"instance":let t="secondary";switch(r(c).toLowerCase()){case"running":t="success";break;case"stopped":t="info";break;case"creating":case"pending_create":t="warning";break;case"deleting":case"pending_delete":case"pending_force_delete":t="warning";break;case"error":case"deleted":t="error";break;case"active":case"online":t="success";break;case"idle":t="info";break;case"pending":case"installing":t="warning";break;case"failed":case"terminated":case"offline":t="error";break;case"unknown":default:t="secondary";break}return{variant:t,text:E(r(c))};case"enabled":return{variant:e().enabled?"success":"error",text:e().enabled?"Enabled":"Disabled"};case"custom":const o=e()[a()]||"Unknown";if(a()==="auth-type"){const f=o==="pat"||!o?"pat":"app";return{variant:f==="pat"?"success":"info",text:f==="pat"?"PAT":"App"}}return{variant:"info",text:o};default:return b(e())}})())}),x(),v();var l=h(),y=T(l);B(y,()=>(s(e()),s(a()),u(()=>`${e()?.name||"item"}-${e()?.[a()]||"status"}-${e()?.updated_at||"time"}`)),t=>{C(t,{get variant(){return r(n),u(()=>r(n).variant)},get text(){return r(n),u(()=>r(n).text)}})}),S(_,l),A()}export{z as S};

View file

@ -1 +0,0 @@
import"./DsnmJJEf.js";import{i as v}from"./TJn6xDN9.js";import{p as w,l as m,q as s,g as r,m as g,h as x,A as h,a as T,b as S,c as A,k,u}from"./DniTuB_0.js";import{k as B}from"./CKaB5fL4.js";import{p as d}from"./DbNhg6mG.js";import{k as b,B as C}from"./CTcPpzia.js";import{f as E}from"./ow_oMtSd.js";function j(_,i){w(i,!1);const c=g(),n=g();let e=d(i,"item",8),l=d(i,"statusType",8,"entity"),a=d(i,"statusField",8,"status");m(()=>(s(e()),s(a())),()=>{k(c,e()?.[a()]||"unknown")}),m(()=>(s(e()),s(l()),r(c),s(a())),()=>{k(n,(()=>{if(!e())return{variant:"error",text:"Unknown"};switch(l()){case"entity":return b(e());case"instance":let t="secondary";switch(r(c).toLowerCase()){case"running":t="success";break;case"stopped":t="info";break;case"creating":case"pending_create":t="warning";break;case"deleting":case"pending_delete":case"pending_force_delete":t="warning";break;case"error":case"deleted":t="error";break;case"active":case"online":t="success";break;case"idle":t="info";break;case"pending":case"installing":t="warning";break;case"failed":case"terminated":case"offline":t="error";break;case"unknown":default:t="secondary";break}return{variant:t,text:E(r(c))};case"enabled":return{variant:e().enabled?"success":"error",text:e().enabled?"Enabled":"Disabled"};case"custom":const o=e()[a()]||"Unknown";if(a()==="auth-type"){const f=o==="pat"||!o?"pat":"app";return{variant:f==="pat"?"success":"info",text:f==="pat"?"PAT":"App"}}return{variant:"info",text:o};default:return b(e())}})())}),x(),v();var p=h(),y=T(p);B(y,()=>(s(e()),s(a()),u(()=>`${e()?.name||"item"}-${e()?.[a()]||"status"}-${e()?.updated_at||"time"}`)),t=>{C(t,{get variant(){return r(n),u(()=>r(n).variant)},get text(){return r(n),u(()=>r(n).text)}})}),S(_,p),A()}export{j as S};

View file

@ -1 +1 @@
import"./DsnmJJEf.js";import{i as _}from"./TJn6xDN9.js";import{p as h,f as x,t as u,b as g,c as k,s as w,d as o,u as d,q as e,r,e as y}from"./DniTuB_0.js";import{h as b}from"./BZ2rxtTc.js";import{p as m}from"./DbNhg6mG.js";import{g as l}from"./CTcPpzia.js";var z=x('<div class="flex items-center"><div class="flex-shrink-0 mr-2"><!></div> <div class="text-sm text-gray-900 dark:text-white"> </div></div>');function U(v,i){h(i,!1);let t=m(i,"item",8),s=m(i,"iconSize",8,"w-5 h-5");_();var a=z(),n=o(a),f=o(n);b(f,()=>(e(l),e(t()),e(s()),d(()=>l(t()?.endpoint?.endpoint_type||t()?.endpoint_type||"unknown",s())))),r(n);var p=w(n,2),c=o(p,!0);r(p),r(a),u(()=>y(c,(e(t()),d(()=>t()?.endpoint?.name||t()?.endpoint_name||t()?.endpoint_type||"Unknown")))),g(v,a),k()}export{U as E};
import"./DsnmJJEf.js";import{i as _}from"./TJn6xDN9.js";import{p as h,f as x,t as u,b as g,c as k,s as w,d as o,u as d,q as e,r,e as y}from"./DniTuB_0.js";import{h as b}from"./DSfKzFV1.js";import{p as m}from"./DbNhg6mG.js";import{g as l}from"./BC4TpgBU.js";var z=x('<div class="flex items-center"><div class="flex-shrink-0 mr-2"><!></div> <div class="text-sm text-gray-900 dark:text-white"> </div></div>');function U(v,i){h(i,!1);let t=m(i,"item",8),s=m(i,"iconSize",8,"w-5 h-5");_();var a=z(),n=o(a),f=o(n);b(f,()=>(e(l),e(t()),e(s()),d(()=>l(t()?.endpoint?.endpoint_type||t()?.endpoint_type||"unknown",s())))),r(n);var p=w(n,2),c=o(p,!0);r(p),r(a),u(()=>y(c,(e(t()),d(()=>t()?.endpoint?.name||t()?.endpoint_name||t()?.endpoint_type||"Unknown")))),g(v,a),k()}export{U as E};

View file

@ -1 +0,0 @@
import"./DsnmJJEf.js";import{i as u}from"./TJn6xDN9.js";import{p as v,E as m,f as h,d as r,r as d,i,b as k,c as g}from"./DniTuB_0.js";import{i as b}from"./BZ2rxtTc.js";var w=h('<div class="fixed inset-0 bg-black/30 dark:bg-black/50 overflow-y-auto h-full w-full z-50 flex items-center justify-center p-4" role="dialog" aria-modal="true" tabindex="-1"><div class="relative mx-auto bg-white dark:bg-gray-800 rounded-lg shadow-lg" role="document"><!></div></div>');function M(s,t){v(t,!1);const l=m();function n(){l("close")}function c(o){o.stopPropagation()}function f(o){o.key==="Escape"&&l("close")}u();var a=w(),e=r(a),p=r(e);b(p,t,"default",{}),d(e),d(a),i("click",e,c),i("click",a,n),i("keydown",a,f),k(s,a),g()}export{M};

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
import"./DsnmJJEf.js";import{i as j}from"./TJn6xDN9.js";import{p as E,E as G,f as S,d as t,r,s as g,u,q as p,z as m,t as q,e as z,i as f,b as D,c as H}from"./DniTuB_0.js";import{h as y,s as h}from"./BZ2rxtTc.js";import{p as v}from"./DbNhg6mG.js";import{g as o}from"./CTcPpzia.js";var I=S('<fieldset><legend class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"> </legend> <div class="grid grid-cols-2 gap-4"><button type="button"><!> <span class="mt-2 text-sm font-medium text-gray-900 dark:text-white">GitHub</span></button> <button type="button"><!> <span class="mt-2 text-sm font-medium text-gray-900 dark:text-white">Gitea</span></button></div></fieldset>');function M(x,s){E(s,!1);const k=G();let i=v(s,"selectedForgeType",12,""),_=v(s,"label",8,"Select Forge Type");function n(c){i(c),k("select",c)}j();var d=I(),l=t(d),F=t(l,!0);r(l);var b=g(l,2),e=t(b),w=t(e);y(w,()=>(p(o),u(()=>o("github","w-8 h-8")))),m(2),r(e);var a=g(e,2),T=t(a);y(T,()=>(p(o),u(()=>o("gitea","w-8 h-8")))),m(2),r(a),r(b),r(d),q(()=>{z(F,_()),h(e,1,`flex flex-col items-center justify-center p-6 border-2 rounded-lg transition-colors cursor-pointer ${i()==="github"?"border-blue-500 bg-blue-50 dark:bg-blue-900":"border-gray-300 dark:border-gray-600 hover:border-gray-400 dark:hover:border-gray-500"}`),h(a,1,`flex flex-col items-center justify-center p-6 border-2 rounded-lg transition-colors cursor-pointer ${i()==="gitea"?"border-blue-500 bg-blue-50 dark:bg-blue-900":"border-gray-300 dark:border-gray-600 hover:border-gray-400 dark:hover:border-gray-500"}`)}),f("click",e,()=>n("github")),f("click",a,()=>n("gitea")),D(x,d),H()}export{M as F};
import"./DsnmJJEf.js";import{i as j}from"./TJn6xDN9.js";import{p as E,E as G,f as S,d as t,r,s as g,u,q as p,z as m,t as q,e as z,i as f,b as D,c as H}from"./DniTuB_0.js";import{h as y,s as h}from"./DSfKzFV1.js";import{p as v}from"./DbNhg6mG.js";import{g as o}from"./BC4TpgBU.js";var I=S('<fieldset><legend class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"> </legend> <div class="grid grid-cols-2 gap-4"><button type="button"><!> <span class="mt-2 text-sm font-medium text-gray-900 dark:text-white">GitHub</span></button> <button type="button"><!> <span class="mt-2 text-sm font-medium text-gray-900 dark:text-white">Gitea</span></button></div></fieldset>');function M(x,s){E(s,!1);const k=G();let i=v(s,"selectedForgeType",12,""),_=v(s,"label",8,"Select Forge Type");function n(c){i(c),k("select",c)}j();var d=I(),l=t(d),F=t(l,!0);r(l);var b=g(l,2),e=t(b),w=t(e);y(w,()=>(p(o),u(()=>o("github","w-8 h-8")))),m(2),r(e);var a=g(e,2),T=t(a);y(T,()=>(p(o),u(()=>o("gitea","w-8 h-8")))),m(2),r(a),r(b),r(d),q(()=>{z(F,_()),h(e,1,`flex flex-col items-center justify-center p-6 border-2 rounded-lg transition-colors cursor-pointer ${i()==="github"?"border-blue-500 bg-blue-50 dark:bg-blue-900":"border-gray-300 dark:border-gray-600 hover:border-gray-400 dark:hover:border-gray-500"}`),h(a,1,`flex flex-col items-center justify-center p-6 border-2 rounded-lg transition-colors cursor-pointer ${i()==="gitea"?"border-blue-500 bg-blue-50 dark:bg-blue-900":"border-gray-300 dark:border-gray-600 hover:border-gray-400 dark:hover:border-gray-500"}`)}),f("click",e,()=>n("github")),f("click",a,()=>n("gitea")),D(x,d),H()}export{M as F};

View file

@ -1 +1 @@
const w=/^(\[)?(\.\.\.)?(\w+)(?:=(\w+))?(\])?$/;function x(t){const s=[];return{pattern:t==="/"?/^\/$/:new RegExp(`^${_(t).map(a=>{const i=/^\[\.\.\.(\w+)(?:=(\w+))?\]$/.exec(a);if(i)return s.push({name:i[1],matcher:i[2],optional:!1,rest:!0,chained:!0}),"(?:/([^]*))?";const c=/^\[\[(\w+)(?:=(\w+))?\]\]$/.exec(a);if(c)return s.push({name:c[1],matcher:c[2],optional:!0,rest:!1,chained:!0}),"(?:/([^/]+))?";if(!a)return;const n=a.split(/\[(.+?)\](?!\])/);return"/"+n.map((e,u)=>{if(u%2){if(e.startsWith("x+"))return h(String.fromCharCode(parseInt(e.slice(2),16)));if(e.startsWith("u+"))return h(String.fromCharCode(...e.slice(2).split("-").map(g=>parseInt(g,16))));const o=w.exec(e),[,l,p,m,d]=o;return s.push({name:m,matcher:d,optional:!!l,rest:!!p,chained:p?u===1&&n[0]==="":!1}),p?"([^]*?)":l?"([^/]*)?":"([^/]+?)"}return h(e)}).join("")}).join("")}/?$`),params:s}}function $(t){return t!==""&&!/^\([^)]+\)$/.test(t)}function _(t){return t.slice(1).split("/").filter($)}function j(t,s,f){const a={},i=t.slice(1),c=i.filter(r=>r!==void 0);let n=0;for(let r=0;r<s.length;r+=1){const e=s[r];let u=i[r-n];if(e.chained&&e.rest&&n&&(u=i.slice(r-n,r+1).filter(o=>o).join("/"),n=0),u===void 0){e.rest&&(a[e.name]="");continue}if(!e.matcher||f[e.matcher](u)){a[e.name]=u;const o=s[r+1],l=i[r+1];o&&!o.rest&&o.optional&&l&&e.chained&&(n=0),!o&&!l&&Object.keys(a).length===c.length&&(n=0);continue}if(e.optional&&e.chained){n++;continue}return}if(!n)return a}function h(t){return t.normalize().replace(/[[\]]/g,"\\$&").replace(/%/g,"%25").replace(/\//g,"%2[Ff]").replace(/\?/g,"%3[Ff]").replace(/#/g,"%23").replace(/[.*+?^${}()|\\]/g,"\\$&")}const b=/\[(\[)?(\.\.\.)?(\w+?)(?:=(\w+))?\]\]?/g;function k(t,s){return"/"+_(t).map(a=>a.replace(b,(i,c,n,r)=>{const e=s[r];if(!e){if(c||n&&e!==void 0)return"";throw new Error(`Missing parameter '${r}' in route ${t}`)}if(e.startsWith("/")||e.endsWith("/"))throw new Error(`Parameter '${r}' in route ${t} cannot start or end with a slash -- this would cause an invalid route like foo//bar`);return e})).filter(Boolean).join("/")}const v=globalThis.__sveltekit_1odpo7n?.base??"/ui",C=globalThis.__sveltekit_1odpo7n?.assets??v;export{C as a,v as b,j as e,x as p,k as r};
const w=/^(\[)?(\.\.\.)?(\w+)(?:=(\w+))?(\])?$/;function x(t){const s=[];return{pattern:t==="/"?/^\/$/:new RegExp(`^${_(t).map(a=>{const i=/^\[\.\.\.(\w+)(?:=(\w+))?\]$/.exec(a);if(i)return s.push({name:i[1],matcher:i[2],optional:!1,rest:!0,chained:!0}),"(?:/([^]*))?";const c=/^\[\[(\w+)(?:=(\w+))?\]\]$/.exec(a);if(c)return s.push({name:c[1],matcher:c[2],optional:!0,rest:!1,chained:!0}),"(?:/([^/]+))?";if(!a)return;const n=a.split(/\[(.+?)\](?!\])/);return"/"+n.map((e,u)=>{if(u%2){if(e.startsWith("x+"))return h(String.fromCharCode(parseInt(e.slice(2),16)));if(e.startsWith("u+"))return h(String.fromCharCode(...e.slice(2).split("-").map(g=>parseInt(g,16))));const o=w.exec(e),[,l,p,m,d]=o;return s.push({name:m,matcher:d,optional:!!l,rest:!!p,chained:p?u===1&&n[0]==="":!1}),p?"([^]*?)":l?"([^/]*)?":"([^/]+?)"}return h(e)}).join("")}).join("")}/?$`),params:s}}function $(t){return t!==""&&!/^\([^)]+\)$/.test(t)}function _(t){return t.slice(1).split("/").filter($)}function j(t,s,f){const a={},i=t.slice(1),c=i.filter(r=>r!==void 0);let n=0;for(let r=0;r<s.length;r+=1){const e=s[r];let u=i[r-n];if(e.chained&&e.rest&&n&&(u=i.slice(r-n,r+1).filter(o=>o).join("/"),n=0),u===void 0){e.rest&&(a[e.name]="");continue}if(!e.matcher||f[e.matcher](u)){a[e.name]=u;const o=s[r+1],l=i[r+1];o&&!o.rest&&o.optional&&l&&e.chained&&(n=0),!o&&!l&&Object.keys(a).length===c.length&&(n=0);continue}if(e.optional&&e.chained){n++;continue}return}if(!n)return a}function h(t){return t.normalize().replace(/[[\]]/g,"\\$&").replace(/%/g,"%25").replace(/\//g,"%2[Ff]").replace(/\?/g,"%3[Ff]").replace(/#/g,"%23").replace(/[.*+?^${}()|\\]/g,"\\$&")}const b=/\[(\[)?(\.\.\.)?(\w+?)(?:=(\w+))?\]\]?/g;function k(t,s){return"/"+_(t).map(a=>a.replace(b,(i,c,n,r)=>{const e=s[r];if(!e){if(c||n&&e!==void 0)return"";throw new Error(`Missing parameter '${r}' in route ${t}`)}if(e.startsWith("/")||e.endsWith("/"))throw new Error(`Parameter '${r}' in route ${t} cannot start or end with a slash -- this would cause an invalid route like foo//bar`);return e})).filter(Boolean).join("/")}const v=globalThis.__sveltekit_2i6bzq?.base??"/ui",C=globalThis.__sveltekit_2i6bzq?.assets??v;export{C as a,v as b,j as e,x as p,k as r};

View file

@ -1,4 +0,0 @@
import{d,s as w,f as x}from"./BZ2rxtTc.js";import"./DsnmJJEf.js";import{i as k}from"./TJn6xDN9.js";import{p as b,l as _,q as c,h as v,f as y,t as h,b as E,c as B,k as z,m as L,d as M,r as j,g as T,e as U}from"./DniTuB_0.js";import{p as o}from"./DbNhg6mG.js";function q(e){if(!e)return"N/A";try{return(typeof e=="string"?new Date(e):e).toLocaleString()}catch{return"Invalid Date"}}function A(e,r="w-4 h-4"){return e==="gitea"?`<svg class="${r}" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 640 640"><path d="m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12" style="fill:#fff"/><path d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9 0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6M125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1m300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1" style="fill:#609926"/><path d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8s2 16.3 9.1 20c7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3s17.4 1.7 22.5-5.3c5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8z" style="fill:#609926"/></svg>`:e==="github"?`<div class="inline-flex ${r}"><svg class="${r} dark:hidden" width="98" height="96" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 98 96"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#24292f"/></svg><svg class="${r} hidden dark:block" width="98" height="96" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 98 96"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#fff"/></svg></div>`:`<svg class="${r} text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>`}function C(e,r){if(e.repo_name)return e.repo_name;if(e.org_name)return e.org_name;if(e.enterprise_name)return e.enterprise_name;if(e.repo_id&&!e.repo_name&&r?.repositories){const n=r.repositories.find(t=>t.id===e.repo_id);return n?`${n.owner}/${n.name}`:"Unknown Entity"}if(e.org_id&&!e.org_name&&r?.organizations){const n=r.organizations.find(t=>t.id===e.org_id);return n&&n.name?n.name:"Unknown Entity"}if(e.enterprise_id&&!e.enterprise_name&&r?.enterprises){const n=r.enterprises.find(t=>t.id===e.enterprise_id);return n&&n.name?n.name:"Unknown Entity"}return"Unknown Entity"}function H(e){return e.repo_id?"repository":e.org_id?"organization":e.enterprise_id?"enterprise":"unknown"}function P(e){return e.repo_id?d(`/repositories/${e.repo_id}`):e.org_id?d(`/organizations/${e.org_id}`):e.enterprise_id?d(`/enterprises/${e.enterprise_id}`):"#"}function V(e){e&&(e.scrollTop=e.scrollHeight)}function W(e){return{newPerPage:e,newCurrentPage:1}}function G(e){return e.pool_manager_status?.running?{text:"Running",variant:"success"}:{text:"Stopped",variant:"error"}}function J(e){switch(e.toLowerCase()){case"error":return{text:"Error",variant:"error"};case"warning":return{text:"Warning",variant:"warning"};case"info":return{text:"Info",variant:"info"};default:return{text:e,variant:"info"}}}function l(e,r,n){if(!r.trim())return e;const t=r.toLowerCase();return e.filter(a=>typeof n=="function"?n(a).toLowerCase().includes(t):n.some(i=>a[i]?.toString().toLowerCase().includes(t)))}function K(e,r){return l(e,r,["name","owner"])}function O(e,r){return l(e,r,["name"])}function Q(e,r){return l(e,r,n=>[n.name||"",n.description||"",n.endpoint?.name||""].join(" "))}function X(e,r){return l(e,r,["name","description","base_url","api_base_url"])}function Y(e,r,n){return e.slice((r-1)*n,r*n)}var I=y("<span> </span>");function Z(e,r){b(r,!1);const n=L();let t=o(r,"variant",8,"gray"),a=o(r,"size",8,"sm"),i=o(r,"text",8),g=o(r,"ring",8,!1);const u={success:"bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200",error:"bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200",warning:"bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200",info:"bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200",gray:"bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200",blue:"bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200",green:"bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200",red:"bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200",yellow:"bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200",secondary:"bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200"},f={success:"ring-green-600/20 dark:ring-green-400/30",error:"ring-red-600/20 dark:ring-red-400/30",warning:"ring-yellow-600/20 dark:ring-yellow-400/30",info:"ring-blue-600/20 dark:ring-blue-400/30",gray:"ring-gray-500/20 dark:ring-gray-400/30",blue:"ring-blue-600/20 dark:ring-blue-400/30",green:"ring-green-600/20 dark:ring-green-400/30",red:"ring-red-600/20 dark:ring-red-400/30",yellow:"ring-yellow-600/20 dark:ring-yellow-400/30",secondary:"ring-gray-500/20 dark:ring-gray-400/30"},p={sm:"px-2 py-1 text-xs",md:"px-2.5 py-0.5 text-xs"};_(()=>(c(t()),c(a()),c(g())),()=>{z(n,["inline-flex items-center rounded-full font-semibold",u[t()],p[a()],g()?`ring-1 ring-inset ${f[t()]}`:""].filter(Boolean).join(" "))}),v(),k();var s=I(),m=M(s,!0);j(s),h(()=>{w(s,1,x(T(n))),U(m,i())}),E(e,s),B()}export{Z as B,X as a,q as b,W as c,J as d,C as e,Q as f,A as g,l as h,H as i,P as j,G as k,O as l,K as m,Y as p,V as s};

View file

@ -1 +0,0 @@
import"./DsnmJJEf.js";import{i as E}from"./TJn6xDN9.js";import{p as H,E as L,f as h,t as f,b as c,c as z,d as e,r as a,s as x,e as d,z as M,D as q}from"./DniTuB_0.js";import{p as i,i as C}from"./DbNhg6mG.js";import{B as F}from"./BZ2rxtTc.js";var G=h('<div class="mt-4 sm:mt-0 flex items-center space-x-4"><!></div>'),I=h('<div class="sm:flex sm:items-center sm:justify-between"><div><h1 class="text-2xl font-bold text-gray-900 dark:text-white"> </h1> <p class="mt-2 text-sm text-gray-700 dark:text-gray-300"> </p></div> <!></div>');function S(u,t){H(t,!1);const _=L();let b=i(t,"title",8),k=i(t,"description",8),v=i(t,"actionLabel",8,null),g=i(t,"showAction",8,!0);function w(){_("action")}E();var r=I(),s=e(r),o=e(s),y=e(o,!0);a(o);var m=x(o,2),A=e(m,!0);a(m),a(s);var P=x(s,2);{var j=n=>{var l=G(),B=e(l);F(B,{variant:"primary",icon:'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />',$$events:{click:w},children:(D,J)=>{M();var p=q();f(()=>d(p,v())),c(D,p)},$$slots:{default:!0}}),a(l),c(n,l)};C(P,n=>{g()&&v()&&n(j)})}a(r),f(()=>{d(y,b()),d(A,k())}),c(u,r),z()}export{S as P};

View file

@ -1 +1 @@
import"./DsnmJJEf.js";import{i as ae}from"./TJn6xDN9.js";import{p as se,E as re,l as M,q as ie,k as r,g as t,m as k,h as le,f as p,d as v,s as A,r as f,b as l,c as oe,A as T,a as B,z as q,D as V,t as E,e as F,u as ne}from"./DniTuB_0.js";import{p as N,i as m}from"./DbNhg6mG.js";import{g as h,B as G}from"./BZ2rxtTc.js";import{t as y}from"./CBJzOE8U.js";import{e as de}from"./BZiHL9L3.js";var ce=p('<div class="flex items-center"><div class="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600 mr-2"></div> <span class="text-sm text-gray-500 dark:text-gray-400">Checking...</span></div>'),ve=p('<div class="ml-4 text-xs text-gray-500 dark:text-gray-400"> </div>'),fe=p('<div class="flex items-center"><svg class="w-4 h-4 text-green-500 mr-2" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path></svg> <span class="text-sm text-green-700 dark:text-green-300">Webhook installed</span></div> <!>',1),he=p('<div class="flex items-center"><svg class="w-4 h-4 text-gray-400 mr-2" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm0-2a6 6 0 100-12 6 6 0 000 12zm0-10a1 1 0 011 1v3a1 1 0 01-2 0V7a1 1 0 011-1z" clip-rule="evenodd"></path></svg> <span class="text-sm text-gray-500 dark:text-gray-400">No webhook installed</span></div>'),ue=p('<div class="bg-white dark:bg-gray-800 shadow rounded-lg"><div class="px-4 py-5 sm:p-6"><div class="flex items-center justify-between"><div><h3 class="text-lg font-medium text-gray-900 dark:text-white">Webhook Status</h3> <div class="mt-1 flex items-center"><!></div></div> <div class="flex space-x-2"><!></div></div></div></div>');function _e(H,g){se(g,!1);const x=k();let u=N(g,"entityType",8),s=N(g,"entityId",8),R=N(g,"entityName",8),i=k(null),o=k(!1),b=k(!0);const O=re();async function _(){if(s())try{r(b,!0),u()==="repository"?r(i,await h.getRepositoryWebhookInfo(s())):r(i,await h.getOrganizationWebhookInfo(s()))}catch(e){e&&typeof e=="object"&&"response"in e&&e.response?.status===404?r(i,null):(console.warn("Failed to check webhook status:",e),r(i,null))}finally{r(b,!1)}}async function J(){if(s())try{r(o,!0),u()==="repository"?await h.installRepositoryWebhook(s()):await h.installOrganizationWebhook(s()),y.success("Webhook Installed",`Webhook for ${u()} ${R()} has been installed successfully.`),await _(),O("webhookStatusChanged",{installed:!0})}catch(e){y.error("Webhook Installation Failed",e instanceof Error?e.message:"Failed to install webhook.")}finally{r(o,!1)}}async function K(){if(s())try{r(o,!0),u()==="repository"?await h.uninstallRepositoryWebhook(s()):await h.uninstallOrganizationWebhook(s()),y.success("Webhook Uninstalled",`Webhook for ${u()} ${R()} has been uninstalled successfully.`),await _(),O("webhookStatusChanged",{installed:!1})}catch(e){y.error("Webhook Uninstall Failed",de(e))}finally{r(o,!1)}}M(()=>ie(s()),()=>{s()&&_()}),M(()=>t(i),()=>{r(x,t(i)&&t(i).active)}),le(),ae();var w=ue(),P=v(w),j=v(P),W=v(j),D=A(v(W),2),Q=v(D);{var X=e=>{var d=ce();l(e,d)},Y=e=>{var d=T(),z=B(d);{var I=a=>{var n=fe(),C=A(B(n),2);{var c=U=>{var $=ve(),te=v($);f($),E(()=>F(te,`URL: ${t(i),ne(()=>t(i).url||"N/A")??""}`)),l(U,$)};m(C,U=>{t(i)&&U(c)})}l(a,n)},S=a=>{var n=he();l(a,n)};m(z,a=>{t(x)?a(I):a(S,!1)},!0)}l(e,d)};m(Q,e=>{t(b)?e(X):e(Y,!1)})}f(D),f(W);var L=A(W,2),Z=v(L);{var ee=e=>{var d=T(),z=B(d);{var I=a=>{G(a,{variant:"danger",size:"sm",get disabled(){return t(o)},$$events:{click:K},children:(n,C)=>{q();var c=V();E(()=>F(c,t(o)?"Uninstalling...":"Uninstall")),l(n,c)},$$slots:{default:!0}})},S=a=>{G(a,{variant:"primary",size:"sm",get disabled(){return t(o)},$$events:{click:J},children:(n,C)=>{q();var c=V();E(()=>F(c,t(o)?"Installing...":"Install Webhook")),l(n,c)},$$slots:{default:!0}})};m(z,a=>{t(x)?a(I):a(S,!1)})}l(e,d)};m(Z,e=>{t(b)||e(ee)})}f(L),f(j),f(P),f(w),l(H,w),oe()}export{_e as W};
import"./DsnmJJEf.js";import{i as ae}from"./TJn6xDN9.js";import{p as se,E as re,l as M,q as ie,k as r,g as t,m as k,h as le,f as p,d as v,s as A,r as f,b as l,c as oe,A as T,a as B,z as q,D as V,t as E,e as F,u as ne}from"./DniTuB_0.js";import{p as N,i as m}from"./DbNhg6mG.js";import{g as h,B as G}from"./DSfKzFV1.js";import{t as y}from"./CBJzOE8U.js";import{e as de}from"./BZiHL9L3.js";var ce=p('<div class="flex items-center"><div class="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600 mr-2"></div> <span class="text-sm text-gray-500 dark:text-gray-400">Checking...</span></div>'),ve=p('<div class="ml-4 text-xs text-gray-500 dark:text-gray-400"> </div>'),fe=p('<div class="flex items-center"><svg class="w-4 h-4 text-green-500 mr-2" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path></svg> <span class="text-sm text-green-700 dark:text-green-300">Webhook installed</span></div> <!>',1),he=p('<div class="flex items-center"><svg class="w-4 h-4 text-gray-400 mr-2" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm0-2a6 6 0 100-12 6 6 0 000 12zm0-10a1 1 0 011 1v3a1 1 0 01-2 0V7a1 1 0 011-1z" clip-rule="evenodd"></path></svg> <span class="text-sm text-gray-500 dark:text-gray-400">No webhook installed</span></div>'),ue=p('<div class="bg-white dark:bg-gray-800 shadow rounded-lg"><div class="px-4 py-5 sm:p-6"><div class="flex items-center justify-between"><div><h3 class="text-lg font-medium text-gray-900 dark:text-white">Webhook Status</h3> <div class="mt-1 flex items-center"><!></div></div> <div class="flex space-x-2"><!></div></div></div></div>');function _e(H,g){se(g,!1);const x=k();let u=N(g,"entityType",8),s=N(g,"entityId",8),R=N(g,"entityName",8),i=k(null),o=k(!1),b=k(!0);const O=re();async function _(){if(s())try{r(b,!0),u()==="repository"?r(i,await h.getRepositoryWebhookInfo(s())):r(i,await h.getOrganizationWebhookInfo(s()))}catch(e){e&&typeof e=="object"&&"response"in e&&e.response?.status===404?r(i,null):(console.warn("Failed to check webhook status:",e),r(i,null))}finally{r(b,!1)}}async function J(){if(s())try{r(o,!0),u()==="repository"?await h.installRepositoryWebhook(s()):await h.installOrganizationWebhook(s()),y.success("Webhook Installed",`Webhook for ${u()} ${R()} has been installed successfully.`),await _(),O("webhookStatusChanged",{installed:!0})}catch(e){y.error("Webhook Installation Failed",e instanceof Error?e.message:"Failed to install webhook.")}finally{r(o,!1)}}async function K(){if(s())try{r(o,!0),u()==="repository"?await h.uninstallRepositoryWebhook(s()):await h.uninstallOrganizationWebhook(s()),y.success("Webhook Uninstalled",`Webhook for ${u()} ${R()} has been uninstalled successfully.`),await _(),O("webhookStatusChanged",{installed:!1})}catch(e){y.error("Webhook Uninstall Failed",de(e))}finally{r(o,!1)}}M(()=>ie(s()),()=>{s()&&_()}),M(()=>t(i),()=>{r(x,t(i)&&t(i).active)}),le(),ae();var w=ue(),P=v(w),j=v(P),W=v(j),D=A(v(W),2),Q=v(D);{var X=e=>{var d=ce();l(e,d)},Y=e=>{var d=T(),z=B(d);{var I=a=>{var n=fe(),C=A(B(n),2);{var c=U=>{var $=ve(),te=v($);f($),E(()=>F(te,`URL: ${t(i),ne(()=>t(i).url||"N/A")??""}`)),l(U,$)};m(C,U=>{t(i)&&U(c)})}l(a,n)},S=a=>{var n=he();l(a,n)};m(z,a=>{t(x)?a(I):a(S,!1)},!0)}l(e,d)};m(Q,e=>{t(b)?e(X):e(Y,!1)})}f(D),f(W);var L=A(W,2),Z=v(L);{var ee=e=>{var d=T(),z=B(d);{var I=a=>{G(a,{variant:"danger",size:"sm",get disabled(){return t(o)},$$events:{click:K},children:(n,C)=>{q();var c=V();E(()=>F(c,t(o)?"Uninstalling...":"Uninstall")),l(n,c)},$$slots:{default:!0}})},S=a=>{G(a,{variant:"primary",size:"sm",get disabled(){return t(o)},$$events:{click:J},children:(n,C)=>{q();var c=V();E(()=>F(c,t(o)?"Installing...":"Install Webhook")),l(n,c)},$$slots:{default:!0}})};m(z,a=>{t(x)?a(I):a(S,!1)})}l(e,d)};m(Z,e=>{t(b)||e(ee)})}f(L),f(j),f(P),f(w),l(H,w),oe()}export{_e as W};

View file

@ -1 +1 @@
import"./DsnmJJEf.js";import"./TJn6xDN9.js";import{f as l,s as h,d as n,r as f,t as N,e as w,b as r,A as x,a as y}from"./DniTuB_0.js";import{p,i as b}from"./DbNhg6mG.js";import{s as O}from"./BZ2rxtTc.js";var P=l('<div class="absolute top-full left-1/2 transform -translate-x-1/2 border-4 border-transparent border-t-gray-900"></div>'),Q=l('<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 border-4 border-transparent border-b-gray-900"></div>'),R=l('<div class="absolute right-full top-1/2 transform -translate-y-1/2 border-4 border-transparent border-l-gray-900"></div>'),S=l('<div class="absolute left-full top-1/2 transform -translate-y-1/2 border-4 border-transparent border-r-gray-900"></div>'),U=l('<div class="relative group"><svg class="w-3 h-3 text-gray-400 cursor-help" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> <div><div class="font-semibold mb-1"> </div> <div class="text-gray-300"> </div> <!></div></div>');function $(k,s){let z=p(s,"title",8),M=p(s,"content",8),t=p(s,"position",8,"top"),T=p(s,"width",8,"w-80");var m=U(),u=h(n(m),2),_=n(u),j=n(_,!0);f(_);var c=h(_,2),A=n(c,!0);f(c);var B=h(c,2);{var C=a=>{var i=P();r(a,i)},q=a=>{var i=x(),D=y(i);{var E=o=>{var v=Q();r(o,v)},F=o=>{var v=x(),G=y(v);{var H=e=>{var d=R();r(e,d)},I=e=>{var d=x(),J=y(d);{var K=g=>{var L=S();r(g,L)};b(J,g=>{t()==="right"&&g(K)},!0)}r(e,d)};b(G,e=>{t()==="left"?e(H):e(I,!1)},!0)}r(o,v)};b(D,o=>{t()==="bottom"?o(E):o(F,!1)},!0)}r(a,i)};b(B,a=>{t()==="top"?a(C):a(q,!1)})}f(u),f(m),N(()=>{O(u,1,`absolute ${t()==="top"?"bottom-full":t()==="bottom"?"top-full":t()==="left"?"right-full top-1/2 -translate-y-1/2":"left-full top-1/2 -translate-y-1/2"} left-1/2 transform -translate-x-1/2 ${t()==="top"?"mb-2":t()==="bottom"?"mt-2":"mx-2"} ${T()??""} p-3 bg-gray-900 text-white text-xs rounded-lg shadow-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50`),w(j,z()),w(A,M())}),r(k,m)}export{$ as T};
import"./DsnmJJEf.js";import"./TJn6xDN9.js";import{f as l,s as h,d as n,r as f,t as N,e as w,b as r,A as x,a as y}from"./DniTuB_0.js";import{p,i as b}from"./DbNhg6mG.js";import{s as O}from"./DSfKzFV1.js";var P=l('<div class="absolute top-full left-1/2 transform -translate-x-1/2 border-4 border-transparent border-t-gray-900"></div>'),Q=l('<div class="absolute bottom-full left-1/2 transform -translate-x-1/2 border-4 border-transparent border-b-gray-900"></div>'),R=l('<div class="absolute right-full top-1/2 transform -translate-y-1/2 border-4 border-transparent border-l-gray-900"></div>'),S=l('<div class="absolute left-full top-1/2 transform -translate-y-1/2 border-4 border-transparent border-r-gray-900"></div>'),U=l('<div class="relative group"><svg class="w-3 h-3 text-gray-400 cursor-help" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> <div><div class="font-semibold mb-1"> </div> <div class="text-gray-300"> </div> <!></div></div>');function $(k,s){let z=p(s,"title",8),M=p(s,"content",8),t=p(s,"position",8,"top"),T=p(s,"width",8,"w-80");var m=U(),u=h(n(m),2),_=n(u),j=n(_,!0);f(_);var c=h(_,2),A=n(c,!0);f(c);var B=h(c,2);{var C=a=>{var i=P();r(a,i)},q=a=>{var i=x(),D=y(i);{var E=o=>{var v=Q();r(o,v)},F=o=>{var v=x(),G=y(v);{var H=e=>{var d=R();r(e,d)},I=e=>{var d=x(),J=y(d);{var K=g=>{var L=S();r(g,L)};b(J,g=>{t()==="right"&&g(K)},!0)}r(e,d)};b(G,e=>{t()==="left"?e(H):e(I,!1)},!0)}r(o,v)};b(D,o=>{t()==="bottom"?o(E):o(F,!1)},!0)}r(a,i)};b(B,a=>{t()==="top"?a(C):a(q,!1)})}f(u),f(m),N(()=>{O(u,1,`absolute ${t()==="top"?"bottom-full":t()==="bottom"?"top-full":t()==="left"?"right-full top-1/2 -translate-y-1/2":"left-full top-1/2 -translate-y-1/2"} left-1/2 transform -translate-x-1/2 ${t()==="top"?"mb-2":t()==="bottom"?"mt-2":"mx-2"} ${T()??""} p-3 bg-gray-900 text-white text-xs rounded-lg shadow-lg opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 z-50`),w(j,z()),w(A,M())}),r(k,m)}export{$ as T};

View file

@ -0,0 +1 @@
import"./DsnmJJEf.js";import{i as D}from"./TJn6xDN9.js";import{p as E,E as B,l as t,q as s,g as e,m as o,h as P,f as T,t as q,i as S,b as F,c as G,u as I,d as _,k as a,r as z}from"./DniTuB_0.js";import{i as J,h as K,s as N,j as O}from"./DSfKzFV1.js";import{l as j,p as l}from"./DbNhg6mG.js";var Q=T('<button><svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><!></svg></button>');function Z(C,i){const M=j(i,["children","$$slots","$$events","$$legacy"]),L=j(M,["action","disabled","title","ariaLabel","size"]);E(i,!1);const u=o(),h=o(),k=o(),f=o(),g=o(),v=o(),n=o(),m=o(),b=o(),A=B();let r=l(i,"action",8,"edit"),x=l(i,"disabled",8,!1),w=l(i,"title",8,""),y=l(i,"ariaLabel",8,""),c=l(i,"size",8,"md");function H(){x()||A("click")}t(()=>{},()=>{a(u,"transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 dark:focus:ring-offset-gray-900 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50")}),t(()=>s(c()),()=>{a(h,{sm:"p-1",md:"p-2"}[c()])}),t(()=>s(r()),()=>{a(k,{edit:"text-indigo-600 dark:text-indigo-400 hover:text-indigo-900 dark:hover:text-indigo-300 focus:ring-indigo-500",delete:"text-red-600 dark:text-red-400 hover:text-red-900 dark:hover:text-red-300 focus:ring-red-500",view:"text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-300 focus:ring-gray-500",add:"text-green-600 dark:text-green-400 hover:text-green-900 dark:hover:text-green-300 focus:ring-green-500",copy:"text-blue-600 dark:text-blue-400 hover:text-blue-900 dark:hover:text-blue-300 focus:ring-blue-500",download:"text-blue-600 dark:text-blue-400 hover:text-blue-900 dark:hover:text-blue-300 focus:ring-blue-500"}[r()])}),t(()=>s(c()),()=>{a(f,c()==="sm"?"h-4 w-4":"h-5 w-5")}),t(()=>(e(u),e(h),e(k)),()=>{a(g,[e(u),e(h),e(k)].join(" "))}),t(()=>{},()=>{a(v,{edit:'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />',delete:'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />',view:'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />',add:'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />',copy:'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />',download:'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />'})}),t(()=>{},()=>{a(n,{edit:"Edit",delete:"Delete",view:"View",add:"Add",copy:"Clone",download:"Download"})}),t(()=>(s(w()),e(n),s(r())),()=>{a(m,w()||e(n)[r()])}),t(()=>(s(y()),e(n),s(r())),()=>{a(b,y()||`${e(n)[r()]} item`)}),P(),D();var d=Q();J(d,()=>({type:"button",class:e(g),disabled:x(),title:e(m),"aria-label":e(b),...L}));var p=_(d),V=_(p);K(V,()=>(e(v),s(r()),I(()=>e(v)[r()])),!0),z(p),z(d),q(()=>N(p,0,O(e(f)))),S("click",d,H),F(C,d),G()}export{Z as A};

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
import"./DsnmJJEf.js";import{i as R}from"./TJn6xDN9.js";import{p as j,l as w,h as A,f as g,t as k,b as v,c as B,s as D,d as u,k as _,m as y,r as m,q as f,u as h,g as d,e as U}from"./DniTuB_0.js";import{p as o,i as F}from"./DbNhg6mG.js";import{c as b,s as G,d as n}from"./BZ2rxtTc.js";var H=g('<div class="text-sm text-gray-500 dark:text-gray-400 truncate"> </div>'),J=g('<div class="w-full min-w-0 text-sm font-medium"><a> </a><!></div>');function V(x,a){j(a,!1);const i=y(),p=y();let e=o(a,"item",8),s=o(a,"entityType",8,"repository"),E=o(a,"showOwner",8,!1),I=o(a,"showId",8,!1),z=o(a,"fontMono",8,!1);function C(){if(!e())return"Unknown";switch(s()){case"repository":return E()?`${e().owner||"Unknown"}/${e().name||"Unknown"}`:e().name||"Unknown";case"organization":case"enterprise":return e().name||"Unknown";case"pool":return I()?e().id||"Unknown":e().name||"Unknown";case"scaleset":return e().name||"Unknown";case"instance":return e().name||"Unknown";case"template":return e().name||"Unknown";default:return e().name||e().id||"Unknown"}}function M(){if(!e())return"#";let t;switch(s()){case"instance":t=e().name;break;default:t=e().id||e().name;break}if(!t)return"#";switch(s()){case"repository":return n(`/repositories/${t}`);case"organization":return n(`/organizations/${t}`);case"enterprise":return n(`/enterprises/${t}`);case"pool":return n(`/pools/${t}`);case"scaleset":return n(`/scalesets/${t}`);case"instance":return n(`/instances/${encodeURIComponent(t)}`);case"template":return n(`/templates/${t}`);default:return"#"}}w(()=>{},()=>{_(i,C())}),w(()=>{},()=>{_(p,M())}),A(),R();var c=J(),r=u(c),N=u(r,!0);m(r);var O=D(r);{var T=t=>{var l=H(),q=u(l,!0);m(l),k(()=>U(q,(f(e()),h(()=>e().provider_id)))),v(t,l)};F(O,t=>{f(s()),f(e()),h(()=>s()==="instance"&&e()?.provider_id)&&t(T)})}m(c),k(()=>{b(r,"href",d(p)),G(r,1,`block w-full truncate text-blue-600 dark:text-blue-400 hover:text-blue-500 dark:hover:text-blue-300 ${z()?"font-mono":""}`),b(r,"title",d(i)),U(N,d(i))}),v(x,c),B()}export{V as E};

View file

@ -1,4 +1,4 @@
import"./DsnmJJEf.js";import{i as g}from"./TJn6xDN9.js";import{p as x,l as k,k as d,m as w,q as y,h as J,f as b,d as z,x as L,s as j,g as c,r as q,t as B,b as f,c as C}from"./DniTuB_0.js";import{p as o,i as E}from"./DbNhg6mG.js";import{c as n,s as M}from"./BZ2rxtTc.js";import{b as N}from"./CCQwxxp9.js";var O=b('<div class="absolute top-2 right-2"><svg class="w-4 h-4 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"></path></svg></div>'),S=b('<div class="relative"><textarea style="tab-size: 2;" spellcheck="false"></textarea> <!></div>');function I(m,r){x(r,!1);let t=o(r,"value",12,""),p=o(r,"placeholder",8,"{}"),u=o(r,"rows",8,4),i=o(r,"disabled",8,!1),a=w(!0);k(()=>y(t()),()=>{if(t().trim())try{JSON.parse(t()),d(a,!0)}catch{d(a,!1)}else d(a,!0)}),J(),g();var l=S(),e=z(l);L(e);var v=j(e,2);{var h=s=>{var _=O();f(s,_)};E(v,s=>{c(a)||s(h)})}q(l),B(()=>{n(e,"placeholder",p()),n(e,"rows",u()),e.disabled=i(),M(e,1,`w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 font-mono text-sm resize-none
import"./DsnmJJEf.js";import{i as g}from"./TJn6xDN9.js";import{p as x,l as k,k as d,m as w,q as y,h as J,f as b,d as z,x as L,s as j,g as c,r as q,t as B,b as f,c as C}from"./DniTuB_0.js";import{p as o,i as E}from"./DbNhg6mG.js";import{c as n,s as M}from"./DSfKzFV1.js";import{b as N}from"./CCQwxxp9.js";var O=b('<div class="absolute top-2 right-2"><svg class="w-4 h-4 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"></path></svg></div>'),S=b('<div class="relative"><textarea style="tab-size: 2;" spellcheck="false"></textarea> <!></div>');function I(m,r){x(r,!1);let t=o(r,"value",12,""),p=o(r,"placeholder",8,"{}"),u=o(r,"rows",8,4),i=o(r,"disabled",8,!1),a=w(!0);k(()=>y(t()),()=>{if(t().trim())try{JSON.parse(t()),d(a,!0)}catch{d(a,!1)}else d(a,!0)}),J(),g();var l=S(),e=z(l);L(e);var v=j(e,2);{var h=s=>{var _=O();f(s,_)};E(v,s=>{c(a)||s(h)})}q(l),B(()=>{n(e,"placeholder",p()),n(e,"rows",u()),e.disabled=i(),M(e,1,`w-full px-3 py-2 border rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 font-mono text-sm resize-none
${c(a)?"border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white":"border-red-300 dark:border-red-600 bg-red-50 dark:bg-red-900/20 text-red-900 dark:text-red-100"}
${i()?"opacity-50 cursor-not-allowed":""}
`)}),N(e,t),f(m,l),C()}export{I as J};

View file

@ -1 +1 @@
import"./DsnmJJEf.js";import"./TJn6xDN9.js";import{f as k,d as a,s as m,r as i,t as g,e as f,b as v,z as p,D as z}from"./DniTuB_0.js";import{p as t,i as u}from"./DbNhg6mG.js";import{s as Y,h as Z,B as H}from"./BZ2rxtTc.js";var $=k('<div class="flex-shrink-0"><!></div>'),ee=k('<div class="mt-4 sm:mt-0 flex space-x-3"><!> <!></div>'),te=k('<div class="bg-white dark:bg-gray-800 shadow rounded-lg"><div class="px-4 py-5 sm:p-6"><div class="sm:flex sm:items-center sm:justify-between"><div class="flex items-center space-x-3"><!> <div><h1> </h1> <p class="text-sm text-gray-500 dark:text-gray-400"> </p></div></div> <!></div></div></div>');function se(j,e){let E=t(e,"title",8),M=t(e,"subtitle",8),D=t(e,"forgeIcon",8,""),h=t(e,"onEdit",8,null),x=t(e,"onDelete",8,null),B=t(e,"editLabel",8,"Edit"),C=t(e,"deleteLabel",8,"Delete"),P=t(e,"editVariant",8,"secondary"),A=t(e,"deleteVariant",8,"danger"),q=t(e,"editDisabled",8,!1),F=t(e,"deleteDisabled",8,!1),G=t(e,"editIcon",8,"<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z'/>"),J=t(e,"deleteIcon",8,"<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16'/>"),K=t(e,"titleClass",8,"");var _=te(),y=a(_),w=a(y),b=a(w),I=a(b);{var N=l=>{var d=$(),c=a(d);Z(c,D),i(d),v(l,d)};u(I,l=>{D()&&l(N)})}var L=m(I,2),o=a(L),O=a(o,!0);i(o);var V=m(o,2),Q=a(V,!0);i(V),i(L),i(b);var R=m(b,2);{var S=l=>{var d=ee(),c=a(d);{var T=r=>{H(r,{get variant(){return P()},size:"md",get disabled(){return q()},get icon(){return G()},$$events:{click(...s){h()?.apply(this,s)}},children:(s,X)=>{p();var n=z();g(()=>f(n,B())),v(s,n)},$$slots:{default:!0}})};u(c,r=>{h()&&r(T)})}var U=m(c,2);{var W=r=>{H(r,{get variant(){return A()},size:"md",get disabled(){return F()},get icon(){return J()},$$events:{click(...s){x()?.apply(this,s)}},children:(s,X)=>{p();var n=z();g(()=>f(n,C())),v(s,n)},$$slots:{default:!0}})};u(U,r=>{x()&&r(W)})}i(d),v(l,d)};u(R,l=>{(h()||x())&&l(S)})}i(w),i(y),i(_),g(()=>{Y(o,1,`text-2xl font-bold text-gray-900 dark:text-white ${K()??""}`),f(O,E()),f(Q,M())}),v(j,_)}export{se as D};
import"./DsnmJJEf.js";import"./TJn6xDN9.js";import{f as k,d as a,s as m,r as i,t as g,e as f,b as v,z as p,D as z}from"./DniTuB_0.js";import{p as t,i as u}from"./DbNhg6mG.js";import{s as Y,h as Z,B as H}from"./DSfKzFV1.js";var $=k('<div class="flex-shrink-0"><!></div>'),ee=k('<div class="mt-4 sm:mt-0 flex space-x-3"><!> <!></div>'),te=k('<div class="bg-white dark:bg-gray-800 shadow rounded-lg"><div class="px-4 py-5 sm:p-6"><div class="sm:flex sm:items-center sm:justify-between"><div class="flex items-center space-x-3"><!> <div><h1> </h1> <p class="text-sm text-gray-500 dark:text-gray-400"> </p></div></div> <!></div></div></div>');function se(j,e){let E=t(e,"title",8),M=t(e,"subtitle",8),D=t(e,"forgeIcon",8,""),h=t(e,"onEdit",8,null),x=t(e,"onDelete",8,null),B=t(e,"editLabel",8,"Edit"),C=t(e,"deleteLabel",8,"Delete"),P=t(e,"editVariant",8,"secondary"),A=t(e,"deleteVariant",8,"danger"),q=t(e,"editDisabled",8,!1),F=t(e,"deleteDisabled",8,!1),G=t(e,"editIcon",8,"<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z'/>"),J=t(e,"deleteIcon",8,"<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16'/>"),K=t(e,"titleClass",8,"");var _=te(),y=a(_),w=a(y),b=a(w),I=a(b);{var N=l=>{var d=$(),c=a(d);Z(c,D),i(d),v(l,d)};u(I,l=>{D()&&l(N)})}var L=m(I,2),o=a(L),O=a(o,!0);i(o);var V=m(o,2),Q=a(V,!0);i(V),i(L),i(b);var R=m(b,2);{var S=l=>{var d=ee(),c=a(d);{var T=r=>{H(r,{get variant(){return P()},size:"md",get disabled(){return q()},get icon(){return G()},$$events:{click(...s){h()?.apply(this,s)}},children:(s,X)=>{p();var n=z();g(()=>f(n,B())),v(s,n)},$$slots:{default:!0}})};u(c,r=>{h()&&r(T)})}var U=m(c,2);{var W=r=>{H(r,{get variant(){return A()},size:"md",get disabled(){return F()},get icon(){return J()},$$events:{click(...s){x()?.apply(this,s)}},children:(s,X)=>{p();var n=z();g(()=>f(n,C())),v(s,n)},$$slots:{default:!0}})};u(U,r=>{x()&&r(W)})}i(d),v(l,d)};u(R,l=>{(h()||x())&&l(S)})}i(w),i(y),i(_),g(()=>{Y(o,1,`text-2xl font-bold text-gray-900 dark:text-white ${K()??""}`),f(O,E()),f(Q,M())}),v(j,_)}export{se as D};

View file

@ -1 +1 @@
import{F as w}from"./DniTuB_0.js";import{g as r}from"./BZ2rxtTc.js";const m=!0,z=m,I=()=>window.location.port==="5173",b={isAuthenticated:!1,user:null,loading:!0,needsInitialization:!1},n=w(b);function f(t,a,e=7){const i=new Date;i.setTime(i.getTime()+e*24*60*60*1e3),document.cookie=`${t}=${a};expires=${i.toUTCString()};path=/;SameSite=Lax`}function d(t){const a=t+"=",e=document.cookie.split(";");for(let i=0;i<e.length;i++){let o=e[i];for(;o.charAt(0)===" ";)o=o.substring(1,o.length);if(o.indexOf(a)===0)return o.substring(a.length,o.length)}return null}function g(t){document.cookie=`${t}=;expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/`}const c={async login(t,a){try{n.update(i=>({...i,loading:!0}));const e=await r.login({username:t,password:a});z&&(f("garm_token",e.token),f("garm_user",t)),r.setToken(e.token),n.set({isAuthenticated:!0,user:t,loading:!1,needsInitialization:!1})}catch(e){throw n.update(i=>({...i,loading:!1})),e}},logout(){g("garm_token"),g("garm_user"),n.set({isAuthenticated:!1,user:null,loading:!1,needsInitialization:!1})},async init(){try{n.update(e=>({...e,loading:!0})),await c.checkInitializationStatus();const t=d("garm_token"),a=d("garm_user");if(t&&a&&(r.setToken(t),await c.checkAuth())){n.set({isAuthenticated:!0,user:a,loading:!1,needsInitialization:!1});return}n.update(e=>({...e,loading:!1,needsInitialization:!1}))}catch{n.update(a=>({...a,loading:!1}))}},async checkInitializationStatus(){try{const t={Accept:"application/json"},a=d("garm_token"),e=I();e&&a&&(t.Authorization=`Bearer ${a}`);const i=await fetch("/api/v1/login",{method:"GET",headers:t,credentials:e?"omit":"include"});if(!i.ok){if(i.status===409&&(await i.json()).error==="init_required")throw n.update(s=>({...s,needsInitialization:!0,loading:!1})),new Error("Initialization required");return}return}catch(t){if(t instanceof Error&&t.message==="Initialization required")throw t;return}},async checkAuth(){try{return await c.checkInitializationStatus(),await r.getControllerInfo(),!0}catch(t){return t instanceof Error&&t.message==="Initialization required"?!1:t?.response?.status===409&&t?.response?.data?.error==="init_required"?(n.update(a=>({...a,needsInitialization:!0,loading:!1})),!1):(c.logout(),!1)}},async initialize(t,a,e,i,o){try{n.update(u=>({...u,loading:!0}));const s=await r.firstRun({username:t,email:a,password:e,full_name:i||t});await c.login(t,e);const l=window.location.origin,h=o?.metadataUrl||`${l}/api/v1/metadata`,p=o?.callbackUrl||`${l}/api/v1/callbacks`,k=o?.webhookUrl||`${l}/webhooks`;await r.updateController({metadata_url:h,callback_url:p,webhook_url:k}),n.update(u=>({...u,needsInitialization:!1}))}catch(s){throw n.update(l=>({...l,loading:!1})),s}}};export{n as a,c as b};
import{F as w}from"./DniTuB_0.js";import{g as r}from"./DSfKzFV1.js";const m=!0,z=m,I=()=>window.location.port==="5173",b={isAuthenticated:!1,user:null,loading:!0,needsInitialization:!1},n=w(b);function f(t,a,e=7){const i=new Date;i.setTime(i.getTime()+e*24*60*60*1e3),document.cookie=`${t}=${a};expires=${i.toUTCString()};path=/;SameSite=Lax`}function d(t){const a=t+"=",e=document.cookie.split(";");for(let i=0;i<e.length;i++){let o=e[i];for(;o.charAt(0)===" ";)o=o.substring(1,o.length);if(o.indexOf(a)===0)return o.substring(a.length,o.length)}return null}function g(t){document.cookie=`${t}=;expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/`}const c={async login(t,a){try{n.update(i=>({...i,loading:!0}));const e=await r.login({username:t,password:a});z&&(f("garm_token",e.token),f("garm_user",t)),r.setToken(e.token),n.set({isAuthenticated:!0,user:t,loading:!1,needsInitialization:!1})}catch(e){throw n.update(i=>({...i,loading:!1})),e}},logout(){g("garm_token"),g("garm_user"),n.set({isAuthenticated:!1,user:null,loading:!1,needsInitialization:!1})},async init(){try{n.update(e=>({...e,loading:!0})),await c.checkInitializationStatus();const t=d("garm_token"),a=d("garm_user");if(t&&a&&(r.setToken(t),await c.checkAuth())){n.set({isAuthenticated:!0,user:a,loading:!1,needsInitialization:!1});return}n.update(e=>({...e,loading:!1,needsInitialization:!1}))}catch{n.update(a=>({...a,loading:!1}))}},async checkInitializationStatus(){try{const t={Accept:"application/json"},a=d("garm_token"),e=I();e&&a&&(t.Authorization=`Bearer ${a}`);const i=await fetch("/api/v1/login",{method:"GET",headers:t,credentials:e?"omit":"include"});if(!i.ok){if(i.status===409&&(await i.json()).error==="init_required")throw n.update(s=>({...s,needsInitialization:!0,loading:!1})),new Error("Initialization required");return}return}catch(t){if(t instanceof Error&&t.message==="Initialization required")throw t;return}},async checkAuth(){try{return await c.checkInitializationStatus(),await r.getControllerInfo(),!0}catch(t){return t instanceof Error&&t.message==="Initialization required"?!1:t?.response?.status===409&&t?.response?.data?.error==="init_required"?(n.update(a=>({...a,needsInitialization:!0,loading:!1})),!1):(c.logout(),!1)}},async initialize(t,a,e,i,o){try{n.update(u=>({...u,loading:!0}));const s=await r.firstRun({username:t,email:a,password:e,full_name:i||t});await c.login(t,e);const l=window.location.origin,h=o?.metadataUrl||`${l}/api/v1/metadata`,p=o?.callbackUrl||`${l}/api/v1/callbacks`,k=o?.webhookUrl||`${l}/webhooks`;await r.updateController({metadata_url:h,callback_url:p,webhook_url:k}),n.update(u=>({...u,needsInitialization:!1}))}catch(s){throw n.update(l=>({...l,loading:!1})),s}}};export{n as a,c as b};

View file

@ -1 +1 @@
import{s as e}from"./HMJxCnAR.js";const r=()=>{const s=e;return{page:{subscribe:s.page.subscribe},navigating:{subscribe:s.navigating.subscribe},updated:s.updated}},b={subscribe(s){return r().page.subscribe(s)}};export{b as p};
import{s as e}from"./kW9-6GPQ.js";const r=()=>{const s=e;return{page:{subscribe:s.page.subscribe},navigating:{subscribe:s.navigating.subscribe},updated:s.updated}},b={subscribe(s){return r().page.subscribe(s)}};export{b as p};

View file

@ -0,0 +1 @@
import"./DsnmJJEf.js";import{i as k}from"./TJn6xDN9.js";import{p as c,l as u,q as g,h as f,f as m,t as p,b as w,c as _,k as v,m as h,d as z,r as B,g as j,e as q}from"./DniTuB_0.js";import{s as V,j as A}from"./DSfKzFV1.js";import{p as a}from"./DbNhg6mG.js";var C=m("<span> </span>");function I(d,e){c(e,!1);const n=h();let t=a(e,"variant",8,"gray"),l=a(e,"size",8,"sm"),i=a(e,"text",8),s=a(e,"ring",8,!1);const o={success:"bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200",error:"bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200",warning:"bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200",info:"bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200",gray:"bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200",blue:"bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200",green:"bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200",red:"bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200",yellow:"bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200",secondary:"bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200"},b={success:"ring-green-600/20 dark:ring-green-400/30",error:"ring-red-600/20 dark:ring-red-400/30",warning:"ring-yellow-600/20 dark:ring-yellow-400/30",info:"ring-blue-600/20 dark:ring-blue-400/30",gray:"ring-gray-500/20 dark:ring-gray-400/30",blue:"ring-blue-600/20 dark:ring-blue-400/30",green:"ring-green-600/20 dark:ring-green-400/30",red:"ring-red-600/20 dark:ring-red-400/30",yellow:"ring-yellow-600/20 dark:ring-yellow-400/30",secondary:"ring-gray-500/20 dark:ring-gray-400/30"},y={sm:"px-2 py-1 text-xs",md:"px-2.5 py-0.5 text-xs"};u(()=>(g(t()),g(l()),g(s())),()=>{v(n,["inline-flex items-center rounded-full font-semibold",o[t()],y[l()],s()?`ring-1 ring-inset ${b[t()]}`:""].filter(Boolean).join(" "))}),f(),k();var r=C(),x=z(r,!0);B(r),p(()=>{V(r,1,A(j(n))),q(x,i())}),w(d,r),_()}export{I as B};

View file

@ -1 +1 @@
import"./DsnmJJEf.js";import{i as _}from"./TJn6xDN9.js";import{p as k,f as E,t as C,u as i,q as t,e as f,b as P,c as j,s as q,d as l,r as o}from"./DniTuB_0.js";import{c as z}from"./BZ2rxtTc.js";import{p as n}from"./DbNhg6mG.js";import{j as x,e as c,i as u}from"./CTcPpzia.js";var N=E('<div class="flex flex-col"><a class="text-sm font-medium text-blue-600 dark:text-blue-400 hover:text-blue-500 dark:hover:text-blue-300"> </a> <span class="text-xs text-gray-500 dark:text-gray-400 capitalize"> </span></div>');function F(d,r){k(r,!1);let e=n(r,"item",8),m=n(r,"eagerCache",8,null);_();var s=N(),a=l(s),v=l(a,!0);o(a);var p=q(a,2),g=l(p,!0);o(p),o(s),C((h,b,y)=>{z(a,"href",h),f(v,b),f(g,y)},[()=>(t(x),t(e()),i(()=>x(e()))),()=>(t(c),t(e()),t(m()),i(()=>c(e(),m()))),()=>(t(u),t(e()),i(()=>u(e())))]),P(d,s),j()}export{F as P};
import"./DsnmJJEf.js";import{i as _}from"./TJn6xDN9.js";import{p as k,f as E,t as C,u as i,q as t,e as f,b as P,c as j,s as q,d as l,r as o}from"./DniTuB_0.js";import{c as z}from"./DSfKzFV1.js";import{p as n}from"./DbNhg6mG.js";import{j as x,e as c,i as u}from"./BC4TpgBU.js";var N=E('<div class="flex flex-col"><a class="text-sm font-medium text-blue-600 dark:text-blue-400 hover:text-blue-500 dark:hover:text-blue-300"> </a> <span class="text-xs text-gray-500 dark:text-gray-400 capitalize"> </span></div>');function F(d,r){k(r,!1);let e=n(r,"item",8),m=n(r,"eagerCache",8,null);_();var s=N(),a=l(s),v=l(a,!0);o(a);var p=q(a,2),g=l(p,!0);o(p),o(s),C((h,b,y)=>{z(a,"href",h),f(v,b),f(g,y)},[()=>(t(x),t(e()),i(()=>x(e()))),()=>(t(c),t(e()),t(m()),i(()=>c(e(),m()))),()=>(t(u),t(e()),i(()=>u(e())))]),P(d,s),j()}export{F as P};

File diff suppressed because one or more lines are too long

View file

@ -1 +0,0 @@
import"./DsnmJJEf.js";import{i as V}from"./TJn6xDN9.js";import{p as B,E as D,l as t,q as s,g as e,m as a,h as P,f as T,t as q,i as S,b as F,c as G,u as I,d as w,k as o,r as z}from"./DniTuB_0.js";import{e as J,h as K,s as N,f as O}from"./BZ2rxtTc.js";import{l as C,p as l}from"./DbNhg6mG.js";var Q=T('<button><svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><!></svg></button>');function Z(j,i){const M=C(i,["children","$$slots","$$events","$$legacy"]),L=C(M,["action","disabled","title","ariaLabel","size"]);B(i,!1);const u=a(),h=a(),p=a(),v=a(),g=a(),f=a(),n=a(),m=a(),b=a(),A=D();let r=l(i,"action",8,"edit"),x=l(i,"disabled",8,!1),y=l(i,"title",8,""),_=l(i,"ariaLabel",8,""),c=l(i,"size",8,"md");function H(){x()||A("click")}t(()=>{},()=>{o(u,"transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 dark:focus:ring-offset-gray-900 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50")}),t(()=>s(c()),()=>{o(h,{sm:"p-1",md:"p-2"}[c()])}),t(()=>s(r()),()=>{o(p,{edit:"text-indigo-600 dark:text-indigo-400 hover:text-indigo-900 dark:hover:text-indigo-300 focus:ring-indigo-500",delete:"text-red-600 dark:text-red-400 hover:text-red-900 dark:hover:text-red-300 focus:ring-red-500",view:"text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-300 focus:ring-gray-500",add:"text-green-600 dark:text-green-400 hover:text-green-900 dark:hover:text-green-300 focus:ring-green-500",copy:"text-blue-600 dark:text-blue-400 hover:text-blue-900 dark:hover:text-blue-300 focus:ring-blue-500"}[r()])}),t(()=>s(c()),()=>{o(v,c()==="sm"?"h-4 w-4":"h-5 w-5")}),t(()=>(e(u),e(h),e(p)),()=>{o(g,[e(u),e(h),e(p)].join(" "))}),t(()=>{},()=>{o(f,{edit:'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />',delete:'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />',view:'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />',add:'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />',copy:'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />'})}),t(()=>{},()=>{o(n,{edit:"Edit",delete:"Delete",view:"View",add:"Add",copy:"Clone"})}),t(()=>(s(y()),e(n),s(r())),()=>{o(m,y()||e(n)[r()])}),t(()=>(s(_()),e(n),s(r())),()=>{o(b,_()||`${e(n)[r()]} item`)}),P(),V();var d=Q();J(d,()=>({type:"button",class:e(g),disabled:x(),title:e(m),"aria-label":e(b),...L}));var k=w(d),E=w(k);K(E,()=>(e(f),s(r()),I(()=>e(f)[r()])),!0),z(k),z(d),q(()=>N(k,0,O(e(v)))),S("click",d,H),F(j,d),G()}export{Z as A};

View file

@ -0,0 +1 @@
import"./DsnmJJEf.js";import{i as F}from"./TJn6xDN9.js";import{p as G,E as I,f as x,t as _,b as r,c as J,d as s,r as i,s as k,u as K,e as f,A as N,a as O,z as Q,D as R}from"./DniTuB_0.js";import{p as o,i as b}from"./DbNhg6mG.js";import{e as S,f as T,B as U}from"./DSfKzFV1.js";var V=x('<div class="mt-4 sm:mt-0 flex items-center space-x-4"><!></div>'),W=x('<div class="mt-4 sm:mt-0 flex items-center space-x-4"><!></div>'),X=x('<div class="sm:flex sm:items-center sm:justify-between"><div><h1 class="text-2xl font-bold text-gray-900 dark:text-white"> </h1> <p class="mt-2 text-sm text-gray-700 dark:text-gray-300"> </p></div> <!></div>');function st(g,t){const w=S(t);G(t,!1);const y=I();let A=o(t,"title",8),P=o(t,"description",8),h=o(t,"actionLabel",8,null),j=o(t,"showAction",8,!0);function z(){y("action")}F();var n=X(),l=s(n),c=s(l),B=s(c,!0);i(c);var p=k(c,2),D=s(p,!0);i(p),i(l);var E=k(l,2);{var H=e=>{var a=V(),v=s(a);T(v,t,"actions",{}),i(a),r(e,a)},L=e=>{var a=N(),v=O(a);{var M=d=>{var m=W(),q=s(m);U(q,{variant:"primary",icon:'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />',$$events:{click:z},children:(C,Y)=>{Q();var u=R();_(()=>f(u,h())),r(C,u)},$$slots:{default:!0}}),i(m),r(d,m)};b(v,d=>{j()&&h()&&d(M)},!0)}r(e,a)};b(E,e=>{K(()=>w.actions)?e(H):e(L,!1)})}i(n),_(()=>{f(B,A()),f(D,P())}),r(g,n),J()}export{st as P};

View file

@ -0,0 +1 @@
function i(t){const n=["B","KB","MB","GB","TB"];let r=t,e=0;for(;r>=1024&&e<n.length-1;)r/=1024,e++;return`${r.toFixed(e===0?0:1)} ${n[e]}`}function o(t){if(!t)return"N/A";try{return new Date(t).toLocaleString()}catch{return t}}export{o as a,i as f};

View file

@ -1 +1 @@
import{s as t,p as r}from"./HMJxCnAR.js";const e={get data(){return r.data},get error(){return r.error},get form(){return r.form},get params(){return r.params},get route(){return r.route},get state(){return r.state},get status(){return r.status},get url(){return r.url}};t.updated.check;const s=e;export{s as p};
import{s as t,p as r}from"./kW9-6GPQ.js";const e={get data(){return r.data},get error(){return r.error},get form(){return r.form},get params(){return r.params},get route(){return r.route},get state(){return r.state},get status(){return r.status},get url(){return r.url}};t.updated.check;const s=e;export{s as p};

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
import"./DsnmJJEf.js";import{i as u}from"./TJn6xDN9.js";import{p as v,E as m,f as h,d as r,r as d,i as t,b as k,c as g}from"./DniTuB_0.js";import{f as b}from"./DSfKzFV1.js";var w=h('<div class="fixed inset-0 bg-black/30 dark:bg-black/50 overflow-y-auto h-full w-full z-50 flex items-center justify-center p-4" role="dialog" aria-modal="true" tabindex="-1"><div class="relative mx-auto bg-white dark:bg-gray-800 rounded-lg shadow-lg" role="document"><!></div></div>');function M(s,i){v(i,!1);const l=m();function n(){l("close")}function c(o){o.stopPropagation()}function f(o){o.key==="Escape"&&l("close")}u();var a=w(),e=r(a),p=r(e);b(p,i,"default",{}),d(e),d(a),t("click",e,c),t("click",a,n),t("keydown",a,f),k(s,a),g()}export{M};

View file

@ -1 +1 @@
import"./DsnmJJEf.js";import{i as q}from"./TJn6xDN9.js";import{p as A,E as F,f as y,s as l,d as t,r as a,z as $,D as b,b as o,t as p,e as n,c as G}from"./DniTuB_0.js";import{p as v,i as H}from"./DbNhg6mG.js";import{M as I}from"./CHjY0Kaf.js";import{B as w}from"./BZ2rxtTc.js";var J=y('<p class="mt-1 font-medium text-gray-900 dark:text-white"> </p>'),K=y('<div class="max-w-xl w-full p-6"><div class="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100 dark:bg-red-900 mb-4"><svg class="h-6 w-6 text-red-600 dark:text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"></path></svg></div> <div class="text-center"><h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white mb-2"> </h3> <div class="text-sm text-gray-500 dark:text-gray-400"><p> </p> <!></div></div> <div class="mt-6 flex justify-end space-x-3"><!> <!></div></div>');function W(D,s){A(s,!1);let M=v(s,"title",8),j=v(s,"message",8),g=v(s,"itemName",8,""),d=v(s,"loading",8,!1);const c=F();function B(){c("confirm")}q(),I(D,{$$events:{close:()=>c("close")},children:(C,O)=>{var m=K(),f=l(t(m),2),u=t(f),P=t(u,!0);a(u);var h=l(u,2),x=t(h),z=t(x,!0);a(x);var E=l(x,2);{var L=e=>{var i=J(),r=t(i,!0);a(i),p(()=>n(r,g())),o(e,i)};H(E,e=>{g()&&e(L)})}a(h),a(f);var _=l(f,2),k=t(_);w(k,{variant:"secondary",get disabled(){return d()},$$events:{click:()=>c("close")},children:(e,i)=>{$();var r=b("Cancel");o(e,r)},$$slots:{default:!0}});var N=l(k,2);w(N,{variant:"danger",get disabled(){return d()},get loading(){return d()},$$events:{click:B},children:(e,i)=>{$();var r=b();p(()=>n(r,d()?"Deleting...":"Delete")),o(e,r)},$$slots:{default:!0}}),a(_),a(m),p(()=>{n(P,M()),n(z,j())}),o(C,m)},$$slots:{default:!0}}),G()}export{W as D};
import"./DsnmJJEf.js";import{i as q}from"./TJn6xDN9.js";import{p as A,E as F,f as y,s as l,d as t,r as a,z as $,D as b,b as o,t as p,e as n,c as G}from"./DniTuB_0.js";import{p as v,i as H}from"./DbNhg6mG.js";import{M as I}from"./l9U2NoST.js";import{B as w}from"./DSfKzFV1.js";var J=y('<p class="mt-1 font-medium text-gray-900 dark:text-white"> </p>'),K=y('<div class="max-w-xl w-full p-6"><div class="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100 dark:bg-red-900 mb-4"><svg class="h-6 w-6 text-red-600 dark:text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"></path></svg></div> <div class="text-center"><h3 class="text-lg leading-6 font-medium text-gray-900 dark:text-white mb-2"> </h3> <div class="text-sm text-gray-500 dark:text-gray-400"><p> </p> <!></div></div> <div class="mt-6 flex justify-end space-x-3"><!> <!></div></div>');function W(D,s){A(s,!1);let M=v(s,"title",8),j=v(s,"message",8),g=v(s,"itemName",8,""),d=v(s,"loading",8,!1);const c=F();function B(){c("confirm")}q(),I(D,{$$events:{close:()=>c("close")},children:(C,O)=>{var m=K(),f=l(t(m),2),u=t(f),P=t(u,!0);a(u);var h=l(u,2),x=t(h),z=t(x,!0);a(x);var E=l(x,2);{var L=e=>{var i=J(),r=t(i,!0);a(i),p(()=>n(r,g())),o(e,i)};H(E,e=>{g()&&e(L)})}a(h),a(f);var _=l(f,2),k=t(_);w(k,{variant:"secondary",get disabled(){return d()},$$events:{click:()=>c("close")},children:(e,i)=>{$();var r=b("Cancel");o(e,r)},$$slots:{default:!0}});var N=l(k,2);w(N,{variant:"danger",get disabled(){return d()},get loading(){return d()},$$events:{click:B},children:(e,i)=>{$();var r=b();p(()=>n(r,d()?"Deleting...":"Delete")),o(e,r)},$$slots:{default:!0}}),a(_),a(m),p(()=>{n(P,M()),n(z,j())}),o(C,m)},$$slots:{default:!0}}),G()}export{W as D};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
import{l as o,a as r}from"../chunks/kW9-6GPQ.js";export{o as load_css,r as start};

View file

@ -1 +0,0 @@
import{l as o,a as r}from"../chunks/HMJxCnAR.js";export{o as load_css,r as start};

View file

@ -1 +1 @@
import"../chunks/DsnmJJEf.js";import{i as h}from"../chunks/TJn6xDN9.js";import{p as c,f as l,a as v,t as u,b as _,c as d,d as s,r as e,s as g,e as p}from"../chunks/DniTuB_0.js";import{p as o}from"../chunks/Hb0uuDRx.js";var x=l("<h1> </h1> <p> </p>",1);function q(i,m){c(m,!1),h();var t=x(),r=v(t),f=s(r,!0);e(r);var a=g(r,2),n=s(a,!0);e(a),u(()=>{p(f,o.status),p(n,o.error?.message)}),_(i,t),d()}export{q as component};
import"../chunks/DsnmJJEf.js";import{i as h}from"../chunks/TJn6xDN9.js";import{p as c,f as l,a as v,t as u,b as _,c as d,d as s,r as e,s as g,e as p}from"../chunks/DniTuB_0.js";import{p as o}from"../chunks/covROD4j.js";var x=l("<h1> </h1> <p> </p>",1);function q(i,m){c(m,!1),h();var t=x(),r=v(t),f=s(r,!0);e(r);var a=g(r,2),n=s(a,!0);e(a),u(()=>{p(f,o.status),p(n,o.error?.message)}),_(i,t),d()}export{q as component};

View file

@ -1 +1 @@
import"../chunks/DsnmJJEf.js";import{i as Z}from"../chunks/TJn6xDN9.js";import{p as ee,o as ae,l as re,h as te,f as K,j as se,t as _,g as a,i as k,b as w,c as de,$ as oe,s as d,D as ie,m as f,d as r,u as q,q as B,k as i,r as t,z as D,e as I}from"../chunks/DniTuB_0.js";import{i as le,s as ne,a as ce}from"../chunks/DbNhg6mG.js";import{B as me,d as l,c as T,r as U}from"../chunks/BZ2rxtTc.js";import{b as C}from"../chunks/CCQwxxp9.js";import{p as ue}from"../chunks/D4Caz1gY.js";import{g as H}from"../chunks/HMJxCnAR.js";import{a as pe,b as ve}from"../chunks/1biM6o9g.js";import{e as fe}from"../chunks/BZiHL9L3.js";var ge=K('<div class="rounded-md bg-red-50 dark:bg-red-900 p-4"><div class="flex"><div class="flex-shrink-0"><svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"></path></svg></div> <div class="ml-3"><p class="text-sm font-medium text-red-800 dark:text-red-200"> </p></div></div></div>'),he=K('<div class="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900 py-12 px-4 sm:px-6 lg:px-8"><div class="max-w-md w-full space-y-8"><div><div class="mx-auto h-48 w-auto flex justify-center"><img alt="GARM" class="h-48 w-auto dark:hidden"/> <img alt="GARM" class="h-48 w-auto hidden dark:block"/></div> <h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900 dark:text-white">Sign in to GARM</h2> <p class="mt-2 text-center text-sm text-gray-600 dark:text-gray-400">GitHub Actions Runner Manager</p></div> <form class="mt-8 space-y-6"><div class="rounded-md shadow-sm -space-y-px"><div><label for="username" class="sr-only">Username</label> <input id="username" name="username" type="text" required class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 placeholder-gray-500 dark:placeholder-gray-400 text-gray-900 dark:text-white bg-white dark:bg-gray-700 rounded-t-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm" placeholder="Username"/></div> <div><label for="password" class="sr-only">Password</label> <input id="password" name="password" type="password" required class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 placeholder-gray-500 dark:placeholder-gray-400 text-gray-900 dark:text-white bg-white dark:bg-gray-700 rounded-b-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm" placeholder="Password"/></div></div> <!> <div><!></div></form></div></div>');function Ae(W,F){ee(F,!1);const[J,N]=ne(),$=()=>ce(pe,"$authStore",J);let m=f(""),u=f(""),o=f(!1),n=f("");ae(()=>{O()});function O(){const e=localStorage.getItem("theme");let s=!1;e==="dark"?s=!0:e==="light"?s=!1:s=window.matchMedia("(prefers-color-scheme: dark)").matches,s?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark")}async function M(){if(!a(m)||!a(u)){i(n,"Please enter both username and password");return}i(o,!0),i(n,"");try{await ve.login(a(m),a(u)),H(l("/"))}catch(e){i(n,fe(e))}finally{i(o,!1)}}function L(e){e.key==="Enter"&&M()}re(()=>($(),l),()=>{$().isAuthenticated&&H(l("/"))}),te(),Z();var g=he();se(e=>{oe.title="Login - GARM"});var z=r(g),h=r(z),A=r(h),S=r(A),Q=d(S,2);t(A),D(4),t(h);var b=d(h,2),x=r(b),y=r(x),p=d(r(y),2);U(p),t(y);var P=d(y,2),v=d(r(P),2);U(v),t(P),t(x);var G=d(x,2);{var V=e=>{var s=ge(),c=r(s),E=d(r(c),2),j=r(E),Y=r(j,!0);t(j),t(E),t(c),t(s),_(()=>I(Y,a(n))),w(e,s)};le(G,e=>{a(n)&&e(V)})}var R=d(G,2),X=r(R);me(X,{type:"submit",variant:"primary",size:"md",fullWidth:!0,get disabled(){return a(o)},get loading(){return a(o)},children:(e,s)=>{D();var c=ie();_(()=>I(c,a(o)?"Signing in...":"Sign in")),w(e,c)},$$slots:{default:!0}}),t(R),t(b),t(z),t(g),_((e,s)=>{T(S,"src",e),T(Q,"src",s),p.disabled=a(o),v.disabled=a(o)},[()=>(B(l),q(()=>l("/assets/garm-light.svg"))),()=>(B(l),q(()=>l("/assets/garm-dark.svg")))]),C(p,()=>a(m),e=>i(m,e)),k("keypress",p,L),C(v,()=>a(u),e=>i(u,e)),k("keypress",v,L),k("submit",b,ue(M)),w(W,g),de(),N()}export{Ae as component};
import"../chunks/DsnmJJEf.js";import{i as Z}from"../chunks/TJn6xDN9.js";import{p as ee,o as ae,l as re,h as te,f as K,j as se,t as _,g as a,i as k,b as w,c as de,$ as oe,s as d,D as ie,m as f,d as r,u as q,q as B,k as i,r as t,z as D,e as I}from"../chunks/DniTuB_0.js";import{i as le,s as ne,a as ce}from"../chunks/DbNhg6mG.js";import{B as me,d as l,c as T,r as U}from"../chunks/DSfKzFV1.js";import{b as C}from"../chunks/CCQwxxp9.js";import{p as ue}from"../chunks/D4Caz1gY.js";import{g as H}from"../chunks/kW9-6GPQ.js";import{a as pe,b as ve}from"../chunks/Dt70yZzL.js";import{e as fe}from"../chunks/BZiHL9L3.js";var ge=K('<div class="rounded-md bg-red-50 dark:bg-red-900 p-4"><div class="flex"><div class="flex-shrink-0"><svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"></path></svg></div> <div class="ml-3"><p class="text-sm font-medium text-red-800 dark:text-red-200"> </p></div></div></div>'),he=K('<div class="min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900 py-12 px-4 sm:px-6 lg:px-8"><div class="max-w-md w-full space-y-8"><div><div class="mx-auto h-48 w-auto flex justify-center"><img alt="GARM" class="h-48 w-auto dark:hidden"/> <img alt="GARM" class="h-48 w-auto hidden dark:block"/></div> <h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900 dark:text-white">Sign in to GARM</h2> <p class="mt-2 text-center text-sm text-gray-600 dark:text-gray-400">GitHub Actions Runner Manager</p></div> <form class="mt-8 space-y-6"><div class="rounded-md shadow-sm -space-y-px"><div><label for="username" class="sr-only">Username</label> <input id="username" name="username" type="text" required class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 placeholder-gray-500 dark:placeholder-gray-400 text-gray-900 dark:text-white bg-white dark:bg-gray-700 rounded-t-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm" placeholder="Username"/></div> <div><label for="password" class="sr-only">Password</label> <input id="password" name="password" type="password" required class="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 dark:border-gray-600 placeholder-gray-500 dark:placeholder-gray-400 text-gray-900 dark:text-white bg-white dark:bg-gray-700 rounded-b-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 focus:z-10 sm:text-sm" placeholder="Password"/></div></div> <!> <div><!></div></form></div></div>');function Ae(W,F){ee(F,!1);const[J,N]=ne(),$=()=>ce(pe,"$authStore",J);let m=f(""),u=f(""),o=f(!1),n=f("");ae(()=>{O()});function O(){const e=localStorage.getItem("theme");let s=!1;e==="dark"?s=!0:e==="light"?s=!1:s=window.matchMedia("(prefers-color-scheme: dark)").matches,s?document.documentElement.classList.add("dark"):document.documentElement.classList.remove("dark")}async function M(){if(!a(m)||!a(u)){i(n,"Please enter both username and password");return}i(o,!0),i(n,"");try{await ve.login(a(m),a(u)),H(l("/"))}catch(e){i(n,fe(e))}finally{i(o,!1)}}function L(e){e.key==="Enter"&&M()}re(()=>($(),l),()=>{$().isAuthenticated&&H(l("/"))}),te(),Z();var g=he();se(e=>{oe.title="Login - GARM"});var z=r(g),h=r(z),A=r(h),S=r(A),Q=d(S,2);t(A),D(4),t(h);var b=d(h,2),x=r(b),y=r(x),p=d(r(y),2);U(p),t(y);var P=d(y,2),v=d(r(P),2);U(v),t(P),t(x);var G=d(x,2);{var V=e=>{var s=ge(),c=r(s),E=d(r(c),2),j=r(E),Y=r(j,!0);t(j),t(E),t(c),t(s),_(()=>I(Y,a(n))),w(e,s)};le(G,e=>{a(n)&&e(V)})}var R=d(G,2),X=r(R);me(X,{type:"submit",variant:"primary",size:"md",fullWidth:!0,get disabled(){return a(o)},get loading(){return a(o)},children:(e,s)=>{D();var c=ie();_(()=>I(c,a(o)?"Signing in...":"Sign in")),w(e,c)},$$slots:{default:!0}}),t(R),t(b),t(z),t(g),_((e,s)=>{T(S,"src",e),T(Q,"src",s),p.disabled=a(o),v.disabled=a(o)},[()=>(B(l),q(()=>l("/assets/garm-light.svg"))),()=>(B(l),q(()=>l("/assets/garm-dark.svg")))]),C(p,()=>a(m),e=>i(m,e)),k("keypress",p,L),C(v,()=>a(u),e=>i(u,e)),k("keypress",v,L),k("submit",b,ue(M)),w(W,g),de(),N()}export{Ae as component};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show more