feat: implement environment management features

- Added environment creation and editing pages with forms for name and description.
- Integrated environment listing with options to edit and delete environments.
- Updated agent detail page to manage environments followed by agents.
- Enhanced API to support environment operations including listing, creating, updating, and deleting environments.
- Refactored related components and state management for improved clarity and functionality.
This commit is contained in:
patel-lyzr
2026-05-13 22:15:08 +05:30
parent 1766bdf497
commit 6b1a13ecdc
19 changed files with 1377 additions and 341 deletions
+72
View File
@@ -72,6 +72,12 @@ func (m *Mongo) Credentials() CredentialStore {
return &mongoCredentials{coll: m.db.Collection("credentials")}
}
// Environments returns the global EnvironmentStore backed by this Mongo
// connection.
func (m *Mongo) Environments() EnvironmentStore {
return &mongoEnvironments{coll: m.db.Collection("environments")}
}
func (m *Mongo) ensureIndexes(ctx context.Context) error {
if _, err := m.db.Collection("pipelines").Indexes().CreateMany(ctx, []mongo.IndexModel{
{Keys: bson.D{{Key: "updated_at", Value: -1}}},
@@ -95,6 +101,12 @@ func (m *Mongo) ensureIndexes(ctx context.Context) error {
}); err != nil {
return fmt.Errorf("credentials indexes: %w", err)
}
if _, err := m.db.Collection("environments").Indexes().CreateMany(ctx, []mongo.IndexModel{
{Keys: bson.D{{Key: "name", Value: 1}}, Options: options.Index().SetUnique(true)},
{Keys: bson.D{{Key: "updated_at", Value: -1}}},
}); err != nil {
return fmt.Errorf("environments indexes: %w", err)
}
return nil
}
@@ -522,3 +534,63 @@ func (s *mongoCredentials) List(ctx context.Context) ([]*Credential, error) {
}
return out, nil
}
// --- environments (global) -----------------------------------------------
type mongoEnvironments struct{ coll *mongo.Collection }
func (s *mongoEnvironments) Create(ctx context.Context, e *Environment) error {
if _, err := s.coll.InsertOne(ctx, e); err != nil {
if mongo.IsDuplicateKeyError(err) {
return ErrAlreadyExists
}
return err
}
return nil
}
func (s *mongoEnvironments) GetByName(ctx context.Context, name string) (*Environment, error) {
var e Environment
if err := s.coll.FindOne(ctx, bson.M{"name": name}).Decode(&e); err != nil {
if errors.Is(err, mongo.ErrNoDocuments) {
return nil, ErrNotFound
}
return nil, err
}
return &e, nil
}
func (s *mongoEnvironments) Update(ctx context.Context, e *Environment) error {
res, err := s.coll.ReplaceOne(ctx, bson.M{"name": e.Name}, e)
if err != nil {
return err
}
if res.MatchedCount == 0 {
return ErrNotFound
}
return nil
}
func (s *mongoEnvironments) Delete(ctx context.Context, name string) error {
res, err := s.coll.DeleteOne(ctx, bson.M{"name": name})
if err != nil {
return err
}
if res.DeletedCount == 0 {
return ErrNotFound
}
return nil
}
func (s *mongoEnvironments) List(ctx context.Context) ([]*Environment, error) {
cur, err := s.coll.Find(ctx, bson.M{}, options.Find().SetSort(bson.D{{Key: "name", Value: 1}}))
if err != nil {
return nil, err
}
defer cur.Close(ctx)
var out []*Environment
if err := cur.All(ctx, &out); err != nil {
return nil, err
}
return out, nil
}
+54 -1
View File
@@ -132,9 +132,14 @@ type Agent struct {
WebhookInstalledAt *time.Time `json:"webhookInstalledAt,omitempty" bson:"webhook_installed_at,omitempty"`
AuthStatus AuthStatus `json:"authStatus,omitempty" bson:"auth_status,omitempty"`
AuthCheckedAt *time.Time `json:"authCheckedAt,omitempty" bson:"auth_checked_at,omitempty"`
AttachedPipelines []string `json:"attachedPipelines,omitempty" bson:"attached_pipelines,omitempty"`
// Environments this agent follows by name. Triggering the agent runs
// the pipelines of these environments (filtered by branch). Replaces
// the older flat AttachedPipelines list — pipelines now live on the
// environment, and agents subscribe to environments.
Environments []string `json:"environments,omitempty" bson:"environments,omitempty"`
// Named credentials — referenced by name from Deploy / future nodes.
// These are agent-specific overrides of the global credential pool.
Credentials []Credential `json:"credentials,omitempty" bson:"credentials,omitempty"`
CreatedAt time.Time `json:"createdAt" bson:"created_at"`
@@ -177,3 +182,51 @@ type CredentialStore interface {
Delete(ctx context.Context, name string) error
List(ctx context.Context) ([]*Credential, error)
}
// Environment is a global, named deploy stage (dev / staging / prod /
// custom — the name is free-form). It is purely a sequencing container:
// it owns an ordered list of pipeline IDs (the promotion sequence) plus
// a description. Per-deploy config (credential, runtime target, approval
// method) lives on the nodes themselves, not here.
//
// Agents subscribe to environments by name (agent.Environments);
// triggering an agent runs the pipelines of the environments it follows
// (each pipeline still gated by its Trigger node's branch filter).
//
// A pipeline may appear in more than one environment.
type Environment struct {
ID string `json:"id" bson:"_id"`
Name string `json:"name" bson:"name"` // unique
Description string `json:"description,omitempty" bson:"description,omitempty"`
// PipelineIDs is ordered — the order is the promotion sequence and is
// reorderable via the API. Dispatch still applies each pipeline's own
// branch filter; the order is the documented progression.
PipelineIDs []string `json:"pipelineIds,omitempty" bson:"pipeline_ids,omitempty"`
CreatedAt time.Time `json:"createdAt" bson:"created_at"`
UpdatedAt time.Time `json:"updatedAt" bson:"updated_at"`
}
// HasPipeline reports whether the pipeline ID is brought into this env.
func (e *Environment) HasPipeline(pipelineID string) bool {
if e == nil {
return false
}
for _, p := range e.PipelineIDs {
if p == pipelineID {
return true
}
}
return false
}
// EnvironmentStore persists global environments. Lookup is by name (the
// user-facing identifier — pipelines/agents reference envs by name).
type EnvironmentStore interface {
Create(ctx context.Context, e *Environment) error
GetByName(ctx context.Context, name string) (*Environment, error)
Update(ctx context.Context, e *Environment) error
Delete(ctx context.Context, name string) error
List(ctx context.Context) ([]*Environment, error)
}