Merge branch 'main' of github.com:czhu12/canine

This commit is contained in:
Celina Lopez
2024-10-23 10:29:55 -07:00
58 changed files with 456 additions and 155 deletions

View File

@@ -7,9 +7,6 @@
- [ ] allow public network access flag is not currently doing anything
* I want a way to “stop” the processes, can maybe do this with a replicas=0 setting
* All the times need to show relative times, not absolute. Its too hard to understand absolute times.
* Whenever something is pushed, and deployed, we need to kill all one off containers because they are no longer running the correct source code
* “Pending” should have some kind of active spinner animation just for the feels
* Rebulid metrics tabs so it works for both clusters & pods
https://overcast.blog/zero-downtime-deployments-with-kubernetes-a-full-guide-71019397b924?gi=95ab85c45634
* Team mates features

View File

@@ -13,13 +13,12 @@ class Clusters::InstallAcmeIssuer
cluster.info("Acme issuer is already installed")
rescue Cli::CommandFailedError => e
cluster.info("Acme issuer not detected, installing...")
ingress_yaml = K8::Shared::AcmeIssuer.new(cluster.user.email).to_yaml
ingress_yaml = K8::Shared::AcmeIssuer.new(cluster.account.owner.email).to_yaml
kubectl.apply_yaml(ingress_yaml)
cluster.info("Acme issuer installed")
end
rescue StandardError => e
cluster.failed!
cluster.info("Acme issuer failed to install")
context.fail!("Script failed with exit code #{exit_status.exitstatus}")
end
end

View File

@@ -7,7 +7,7 @@ class Projects::DeployLatestCommit
executed do |context|
# Fetch the latest commit from the default branch
project = context.project
client = Octokit::Client.new(access_token: project.user.github_access_token)
client = Octokit::Client.new(access_token: project.account.github_access_token)
commit = client.commits(project.repository_url).first
build = Build.create!(

View File

@@ -6,7 +6,7 @@ class Projects::RegisterGithubWebhook
promises :project
executed do |context|
client = Octokit::Client.new(access_token: context.project.user.github_access_token)
client = Octokit::Client.new(access_token: context.project.account.github_access_token)
client.create_hook(
context.project.repository_url,
"web",

View File

@@ -5,7 +5,7 @@ class Projects::ValidateGithubRepository
promises :project
executed do |context|
client = Octokit::Client.new(access_token: context.project.user.github_access_token)
client = Octokit::Client.new(access_token: context.project.account.github_access_token)
unless client.repository?(context.project.repository_url)
context.project.errors.add(:repository_url, 'does not exist')
context.fail_and_return!('Repository does not exist')

View File

@@ -0,0 +1,7 @@
class AccountsController < ApplicationController
def switch
@account = current_user.accounts.find(params[:id])
session[:account_id] = @account.id
redirect_to root_path
end
end

View File

@@ -4,7 +4,7 @@ class AddOnsController < ApplicationController
# GET /add_ons
def index
@pagy, @add_ons = pagy(AddOn.all)
@pagy, @add_ons = pagy(current_account.add_ons)
# Uncomment to authorize with Pundit
# authorize @add_ons
@@ -71,7 +71,7 @@ class AddOnsController < ApplicationController
# Use callbacks to share common setup or constraints between actions.
def set_add_on
@add_on = current_user.add_ons.find(params[:id])
@add_on = current_account.add_ons.find(params[:id])
# Uncomment to authorize with Pundit
# authorize @add_on

View File

@@ -1,4 +1,5 @@
class ApplicationController < ActionController::Base
include ActionView::Helpers::DateHelper
impersonates :user
include Pundit::Authorization
include Pagy::Backend
@@ -13,6 +14,20 @@ class ApplicationController < ActionController::Base
protected
def current_account
return nil unless user_signed_in?
@current_account ||= current_user.accounts.find_by(id: session[:account_id]) || current_user.accounts.first
end
helper_method :current_account
def time_ago(t)
if t.present?
"#{time_ago_in_words(t)} ago"
else
"Never"
end
end
helper_method :time_ago
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [ :name ])
devise_parameter_sanitizer.permit(:account_update, keys: [ :name, :avatar ])

View File

@@ -4,7 +4,7 @@ class ClustersController < ApplicationController
# GET /clusters
def index
sortable_column = params[:sort] || "created_at"
@pagy, @clusters = pagy(current_user.clusters.order(sortable_column => "asc"))
@pagy, @clusters = pagy(current_account.clusters.order(sortable_column => "asc"))
# Uncomment to authorize with Pundit
# authorize @clusters
@@ -44,7 +44,7 @@ class ClustersController < ApplicationController
# POST /clusters or /clusters.json
def create
@cluster = current_user.clusters.new(cluster_params)
@cluster = current_account.clusters.new(cluster_params)
# Uncomment to authorize with Pundit
# authorize @cluster
@@ -88,7 +88,7 @@ class ClustersController < ApplicationController
# Use callbacks to share common setup or constraints between actions.
def set_cluster
@cluster = current_user.clusters.find(params[:id])
@cluster = current_account.clusters.find(params[:id])
# Uncomment to authorize with Pundit
# authorize @cluster

View File

@@ -4,6 +4,6 @@ class Projects::BaseController < ApplicationController
private
def set_project
@project = current_user.projects.find(params[:project_id])
@project = current_account.projects.find(params[:project_id])
end
end

View File

@@ -31,6 +31,6 @@ class Projects::ProcessesController < Projects::BaseController
end
def set_cluster
@cluster = current_user.clusters.find(params[:cluster_id])
@cluster = current_account.clusters.find(params[:cluster_id])
end
end

View File

@@ -5,7 +5,7 @@ class ProjectsController < ApplicationController
# GET /projects
def index
sortable_column = params[:sort] || "created_at"
@pagy, @projects = pagy(current_user.projects.order(sortable_column => "asc"))
@pagy, @projects = pagy(current_account.projects.order(sortable_column => "asc"))
# Uncomment to authorize with Pundit
# authorize @projects
@@ -79,7 +79,7 @@ class ProjectsController < ApplicationController
# Use callbacks to share common setup or constraints between actions.
def set_project
@project = current_user.projects.find(params[:id])
@project = current_account.projects.find(params[:id])
# Uncomment to authorize with Pundit
# authorize @project

View File

@@ -35,6 +35,7 @@ module Users
redirect_to edit_user_registration_path
else
sign_in_and_redirect user, event: :authentication
session[:account_id] = user.accounts.first.id
set_flash_message :notice, :success, kind: kind
end
end
@@ -77,11 +78,19 @@ module Users
end
def create_user
User.create(
email: auth.info.email,
# name: auth.info.name,
password: Devise.friendly_token[0, 20]
)
ActiveRecord::Base.transaction do
user = User.create!(
email: auth.info.email,
# name: auth.info.name,
password: Devise.friendly_token[0, 20]
)
account = Account.create!(
owner: user,
name: "#{auth.info.name || auth.info.email.split("@").first}'s Account"
)
AccountUser.create!(account: account, user: user)
user
end
end
end
end

View File

@@ -14,6 +14,6 @@ export default class extends Controller {
event.currentTarget.classList.add('ring', 'ring-primary')
// Show Input
this.element.querySelectorAll('.card-form').forEach(form => form.classList.add('hidden'))
this.element.querySelector(`#card-${event.currentTarget.dataset.cardName}`)?.classList.remove("hidden");
this.element.querySelectorAll(`.card-${event.currentTarget.dataset.cardName}`).forEach(form => form.classList.remove("hidden"));
}
}

View File

@@ -23,7 +23,7 @@ class Projects::BuildJob < ApplicationJob
private
def project_git(project)
"https://#{project.user.github_username}:#{project.user.github_access_token}@github.com/#{project.repository_url}.git"
"https://#{project.account.github_username}:#{project.account.github_access_token}@github.com/#{project.repository_url}.git"
end
def git_clone(project, build, repository_path)
@@ -72,9 +72,9 @@ class Projects::BuildJob < ApplicationJob
def login_to_docker(project, build)
docker_login_command = %w[docker login ghcr.io --username] +
[ project.user.github_username, "--password", project.user.github_access_token ]
[ project.account.github_username, "--password", project.account.github_access_token ]
build.info "Logging into ghcr.io as #{project.user.github_username}"
build.info "Logging into ghcr.io as #{project.account.github_username}"
_stdout, stderr, status = Open3.capture3(*docker_login_command)
if status.success?

View File

@@ -63,11 +63,17 @@ class Projects::DeploymentJob < ApplicationJob
elsif service.web_service?
apply_deployment(service, kubectl)
apply_service(service, kubectl)
if service.domains.any?
if service.domains.any? && service.allow_public_networking?
apply_ingress(service, kubectl)
end
restart_deployment(service, kubectl)
end
# Kill all one off containers
kill_one_off_containers(service, kubectl)
end
def kill_one_off_containers(service, kubectl)
kubectl.call("-n #{service.project.name} delete pods -l oneoff=true")
end
def apply_namespace(project, kubectl)
@@ -90,7 +96,7 @@ class Projects::DeploymentJob < ApplicationJob
def upload_registry_secrets(kubectl, deployment)
project = deployment.project
docker_config_json = create_docker_config_json(project.user.github_username, project.user.github_access_token)
docker_config_json = create_docker_config_json(project.account.github_username, project.account.github_access_token)
secret_yaml = K8::Secrets::RegistrySecret.new(project, docker_config_json).to_yaml
kubectl.apply_yaml(secret_yaml)
end

View File

@@ -0,0 +1,23 @@
class AccountResource < Madmin::Resource
# Associations
attribute :owner
attribute :users
attribute :clusters
attribute :projects
attribute :services
attribute :add_ons
# Uncomment this to customize the display name of records in the admin area.
# def self.display_name(record)
# record.name
# end
# Uncomment this to customize the default sort column and direction.
# def self.default_sort_column
# "created_at"
# end
#
# def self.default_sort_direction
# "desc"
# end
end

View File

@@ -11,6 +11,7 @@ class AddOnResource < Madmin::Resource
# Associations
attribute :log_output
attribute :cluster
attribute :account
# Uncomment this to customize the display name of records in the admin area.
# def self.display_name(record)

View File

@@ -9,7 +9,7 @@ class ClusterResource < Madmin::Resource
# Associations
attribute :log_output
attribute :user
attribute :account
attribute :projects
attribute :add_ons
attribute :domains

View File

@@ -15,7 +15,7 @@ class ProjectResource < Madmin::Resource
# Associations
attribute :cluster
attribute :user
attribute :account
attribute :services
attribute :environment_variables
attribute :builds

View File

@@ -19,7 +19,6 @@ class UserResource < Madmin::Resource
attribute :clusters
attribute :projects
attribute :services
attribute :docker_hub_credential
attribute :add_ons
# Uncomment this to customize the display name of records in the admin area.

40
app/models/account.rb Normal file
View File

@@ -0,0 +1,40 @@
# == Schema Information
#
# Table name: accounts
#
# id :bigint not null, primary key
# name :string not null
# created_at :datetime not null
# updated_at :datetime not null
# owner_id :bigint
#
# Indexes
#
# index_accounts_on_owner_id (owner_id)
#
# Foreign Keys
#
# fk_rails_... (owner_id => users.id)
#
class Account < ApplicationRecord
belongs_to :owner, class_name: "User"
has_many :account_users, dependent: :destroy
has_many :users, through: :account_users
has_many :clusters, dependent: :destroy
has_many :projects, through: :clusters
has_many :add_ons, through: :clusters
has_many :services, through: :projects
def github_username
JSON.parse(github_account.auth)["info"]["nickname"]
end
def github_access_token
github_account.access_token
end
def github_account
@_github_account ||= owner.providers.find_by(provider: "github")
end
end

View File

@@ -0,0 +1,24 @@
# == Schema Information
#
# Table name: account_users
#
# id :bigint not null, primary key
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint not null
# user_id :bigint not null
#
# Indexes
#
# index_account_users_on_account_id (account_id)
# index_account_users_on_user_id (user_id)
#
# Foreign Keys
#
# fk_rails_... (account_id => accounts.id)
# fk_rails_... (user_id => users.id)
#
class AccountUser < ApplicationRecord
belongs_to :user
belongs_to :account
end

View File

@@ -27,6 +27,7 @@ class AddOn < ApplicationRecord
}.freeze
include Loggable
belongs_to :cluster
has_one :account, through: :cluster
enum :status, {installing: 0, installed: 1, uninstalling: 2, uninstalled: 3, failed: 4}
validates :chart_type, presence: true
validate :chart_type_exists

View File

@@ -8,20 +8,21 @@
# status :integer default("initializing"), not null
# created_at :datetime not null
# updated_at :datetime not null
# user_id :bigint not null
# account_id :bigint not null
#
# Indexes
#
# index_clusters_on_user_id (user_id)
# index_clusters_on_account_id (account_id)
#
# Foreign Keys
#
# fk_rails_... (user_id => users.id)
# fk_rails_... (account_id => accounts.id)
#
class Cluster < ApplicationRecord
include Loggable
broadcasts_refreshes
belongs_to :user
belongs_to :account
has_many :projects, dependent: :destroy
has_many :add_ons, dependent: :destroy
has_many :domains, through: :projects

View File

@@ -28,7 +28,7 @@
class Project < ApplicationRecord
broadcasts_refreshes
belongs_to :cluster
has_one :user, through: :cluster
has_one :account, through: :cluster
has_many :services, dependent: :destroy
has_many :environment_variables, dependent: :destroy
has_many :builds, dependent: :destroy
@@ -48,6 +48,10 @@ class Project < ApplicationRecord
deployments.order(created_at: :desc).where(status: :completed).first
end
def last_build
builds.order(created_at: :desc).first
end
def last_deployment
deployments.order(created_at: :desc).first
end

View File

@@ -11,7 +11,7 @@
# name :string not null
# replicas :integer default(1)
# service_type :integer not null
# status :integer
# status :integer default("healthy")
# created_at :datetime not null
# updated_at :datetime not null
# project_id :bigint not null

View File

@@ -28,26 +28,16 @@ class User < ApplicationRecord
has_one_attached :avatar
has_person_name
has_many :account_users, dependent: :destroy
has_many :accounts, through: :account_users
has_many :providers, dependent: :destroy
has_many :clusters, through: :accounts
has_many :projects, through: :accounts
has_many :add_ons, through: :accounts
has_many :services, through: :accounts
# has_many :notifications, as: :recipient, dependent: :destroy, class_name: "Noticed::Notification"
# has_many :notification_mentions, as: :record, dependent: :destroy, class_name: "Noticed::Event"
has_many :providers
has_many :clusters, dependent: :destroy
has_many :projects, through: :clusters
has_many :services, through: :projects
has_one :docker_hub_credential, dependent: :destroy
has_many :add_ons, through: :clusters
def github_username
JSON.parse(github_account.auth)["info"]["nickname"]
end
def github_access_token
github_account.access_token
end
def github_account
@_github_account ||= providers.find_by(provider: "github")
end
end

View File

@@ -19,7 +19,7 @@
<div class="form-group">
<%= form.label :cluster_id %>
<%= form.collection_select :cluster_id, current_user.clusters, :id, :name, {}, { class: "select select-bordered" } %>
<%= form.collection_select :cluster_id, current_account.clusters, :id, :name, {}, { class: "select select-bordered" } %>
</div>
<div data-controller="new-add-ons">

View File

@@ -4,6 +4,20 @@
<img src="/images/logo-full.png" class="h-[40px] hidden lg:block" />
<span class="sr-only"><%= Rails.configuration.application_name %></span>
<% end %>
<% if current_user.accounts.count > 1 %>
<div class="flex items-center justify-center">
<div class="mx-auto dropdown">
<button type="button" class="btn w-full"><%= current_account.name %></button>
<ul tabindex="0" class="menu dropdown-content w-52 rounded-box bg-base-100 p-2 shadow" role="menu">
<%= current_user.accounts.each do |account| %>
<li>
<%= link_to account.name, switch_account_path(account) %>
</li>
<% end %>
</ul>
</div>
</div>
<% end %>
<div data-simplebar="init" class="h-[calc(100vh-64px)] lg:h-[calc(100vh-230px)] simplebar-scrollable-y">
<div class="simplebar-wrapper">
@@ -23,7 +37,7 @@
<% end %>
</summary>
<ul>
<% current_user.projects.each do |project| %>
<% current_account.projects.each do |project| %>
<li>
<%= link_to root_projects_path(project), class: "hover:bg-base-content/15 #{'active' if current_page?(root_projects_path(project))}" do %>
<div class="flex items-center gap-2">
@@ -54,7 +68,7 @@
<% end %>
</summary>
<ul>
<% current_user.clusters.each do |cluster| %>
<% current_account.clusters.each do |cluster| %>
<li>
<%= link_to cluster_path(cluster), class: "hover:bg-base-content/15 #{'active' if current_page?(cluster_path(cluster))}" do %>
<div class="flex items-center gap-2">
@@ -85,7 +99,7 @@
<% end %>
</summary>
<ul>
<% current_user.add_ons.each do |add_on| %>
<% current_account.add_ons.each do |add_on| %>
<li>
<%= link_to add_on_path(add_on), class: "hover:bg-base-content/15 #{'active' if current_page?(add_on_path(add_on))}" do %>
<div class="flex items-center gap-2">

View File

@@ -34,7 +34,9 @@
<td>
<% if project.last_deployment_at %>
<div>
<div class="text-sm"><%= "#{distance_of_time_in_words(project.last_deployment_at, Time.current)} ago" %></div>
<div role="tooltip" data-tip="<%= project.last_deployment_at.strftime("%B %d, %Y %I:%M %p") %>" class="tooltip">
<div class="text-sm"><%= time_ago(project.last_deployment_at) %></div>
</div>
<div class="text-xs italic"><%= project.last_deployment.build.commit_message %></div>
</div>
<% else %>

View File

@@ -14,7 +14,7 @@
<label class="label">
<span class="label-text">Cluster</span>
</label>
<%= form.collection_select :cluster_id, current_user.clusters.running, :id, :name, {}, { class: "select select-bordered" } %>
<%= form.collection_select :cluster_id, current_account.clusters.running, :id, :name, {}, { class: "select select-bordered" } %>
<label class="label">
<span class="label-text-alt">* Required</span>
</label>

View File

@@ -1,4 +1,8 @@
<% if project.last_deployment&.in_progress? %>
<% if project.last_build&.in_progress? %>
<div aria-label="Badge" class="badge border-0 bg-warning/10 font-medium capitalize text-warning">
Building <iconify-icon class="ml-1 animate-spin" icon="lucide:loader-circle"></iconify-icon>
</div>
<% elsif project.last_deployment&.in_progress? %>
<div aria-label="Badge" class="badge border-0 bg-warning/10 font-medium capitalize text-warning">
Deploying <iconify-icon class="ml-1 animate-spin" icon="lucide:loader-circle"></iconify-icon>
</div>

View File

@@ -61,7 +61,9 @@
</div>
</td>
<td>
<div class="text-sm capitalize"><%= build.created_at.strftime("%B %d, %Y %H:%M") %></div>
<div role="tooltip" data-tip="<%= build.created_at.strftime("%B %d, %Y %H:%M %p") %>" class="tooltip">
<div class="text-sm capitalize"><%= distance_of_time_in_words(build.created_at, Time.current) %> ago</div>
</div>
</td>
<td>
<div class="text-sm capitalize">

View File

@@ -9,7 +9,7 @@
<div class="mt-5">
<div aria-label="Card" class="card card-bordered bg-base-100">
<div class="card-body">
<% if current_user.clusters.empty? %>
<% if current_account.clusters.empty? %>
<div class="text-center">
<p class="mb-4 h3">Create your first Cluster to get started</p>
<%= link_to t("scaffold.new.title", model: "Cluster"), new_cluster_path, class: "btn btn-primary" %>

View File

@@ -30,14 +30,24 @@
<label class="label mt-1">
<span class="label-text cursor-pointer">Allow public networking</span>
<%= form.check_box :allow_public_networking, class: "checkbox" %>
<span class="label-text-alt">Checking this allows your service to be accessible from the public internet</span>
</label>
</div>
<% end %>
<% if service.web_service? || service.background_service? %>
<div>
<h2 class="text-lg my-2 mt-4">Resources</h2>
<div class="form-control form-group">
<%= form.label :replicas %>
<%= form.number_field :replicas, class: "input input-bordered", placeholder: "1" %>
</div>
</div>
<% end %>
<div class="form-footer">
<%= form.submit class: "btn btn-primary" %>
</div>
<% end %>
<% if service.web_service? %>
<% if service.web_service? && service.allow_public_networking? %>
<div class="my-8">
<h2 class="text-lg my-2">Networking</h2>

View File

@@ -11,6 +11,10 @@
<%= service.status %>
</div>
<div class="text-xs text-gray-500">
Last checked <%= time_ago_in_words(service.last_health_checked_at) %> ago
<% if service.last_health_checked_at %>
Last checked <%= time_ago(service.last_health_checked_at) %>
<% else %>
Never checked
<% end %>
</div>
</div>

View File

@@ -74,7 +74,7 @@
</label>
</div>
<div id="card-web_service" class="card-form hidden">
<div class="card-form hidden card-web_service">
<h3 class="text-lg font-bold">Networking</h3>
<div class="form-group">
<%= form.label :container_port %>
@@ -84,15 +84,24 @@
<%= form.label :healthcheck_url %>
<%= form.text_field :healthcheck_url, class: "input input-bordered", placeholder: "/health" %>
</div>
<div class="form-control">
<label class="label">
<div class="form-control rounded-lg bg-base-200 p-2 px-4 max-w-xs">
<label class="label mt-1">
<span class="label-text cursor-pointer">Allow public networking</span>
<%= form.check_box :allow_public_networking, class: "toggle toggle-primary" %>
<span class="label-text-alt">Checking this allows your service to be accessible from the public internet</span>
</label>
</div>
</div>
<div id="card-cron_job" class="card-form hidden">
<div class="card-form hidden card-web_service card-background_service">
<h3 class="text-lg font-bold">Resources</h3>
<div class="form-group">
<%= form.label :replicas %>
<%= form.number_field :replicas, class: "input input-bordered", placeholder: "1" %>
</div>
</div>
<div class="card-form hidden card-cron_job">
<div class="form-group">
<h3 class="text-lg font-bold">Cron job</h3>
<%= form.fields_for :cron_schedule do |cron_schedule_form| %>

View File

@@ -1,62 +1,51 @@
<div class="ml-auto">
<% if current_user.present? %>
<div class="dropdown ml-auto dropdown-end dropdown-bottom">
<label tabindex="0" class="btn btn-ghost rounded-btn px-1.5 hover:bg-base-content/20">
<div aria-label="Avatar photo" class="avatar">
<div class="mask mask-squircle" style="width: 30px; height: 30px">
<%= image_tag avatar_path(current_user, size: 40) %>
</div>
<div class="dropdown ml-auto dropdown-end dropdown-bottom">
<label tabindex="0" class="btn btn-ghost rounded-btn px-1.5 hover:bg-base-content/20">
<div aria-label="Avatar photo" class="avatar">
<div class="mask mask-squircle" style="width: 30px; height: 30px">
<%= image_tag avatar_path(current_user, size: 40) %>
</div>
</label>
<ul class="menu dropdown-content mt-4 w-52 rounded-box bg-base-100 p-2 shadow">
</div>
</label>
<ul class="menu dropdown-content mt-4 w-52 rounded-box bg-base-100 p-2 shadow">
<li>
<div>
<%# <%= link_to t(".connected_accounts"), user_connected_accounts_path, class: 'text-gray-800 bg-white hover:bg-primary-50 dark:text-gray-50 dark:bg-gray-800 dark:hover:bg-primary-800 transition ease-in-out duration-200 whitespace-nowrap no-underline block px-6 py-3' if Devise.omniauth_configs.any? %>
<%# <%= link_to t(".accounts"), accounts_path, class: 'text-gray-800 bg-white hover:bg-primary-50 dark:text-gray-50 dark:bg-gray-800 dark:hover:bg-primary-800 transition ease-in-out duration-200 whitespace-nowrap no-underline block px-6 py-3' %>
<%#= link_to t(".api_tokens"), api_tokens_path, class: 'text-gray-800 bg-white hover:bg-primary-50 dark:text-gray-50 dark:bg-gray-800 dark:hover:bg-primary-800 transition ease-in-out duration-200 whitespace-nowrap no-underline block px-6 py-3' if Jumpstart.config.payments_enabled? %>
<%= link_to edit_user_registration_path do %>
<iconify-icon icon="lucide:user" height="16"></iconify-icon>
My Profile
<% end %>
</div>
</li>
<% if current_user.admin? && respond_to?(:madmin_root_path) %>
<li>
<div>
<%# <%= link_to t(".connected_accounts"), user_connected_accounts_path, class: 'text-gray-800 bg-white hover:bg-primary-50 dark:text-gray-50 dark:bg-gray-800 dark:hover:bg-primary-800 transition ease-in-out duration-200 whitespace-nowrap no-underline block px-6 py-3' if Devise.omniauth_configs.any? %>
<%# <%= link_to t(".accounts"), accounts_path, class: 'text-gray-800 bg-white hover:bg-primary-50 dark:text-gray-50 dark:bg-gray-800 dark:hover:bg-primary-800 transition ease-in-out duration-200 whitespace-nowrap no-underline block px-6 py-3' %>
<%#= link_to t(".api_tokens"), api_tokens_path, class: 'text-gray-800 bg-white hover:bg-primary-50 dark:text-gray-50 dark:bg-gray-800 dark:hover:bg-primary-800 transition ease-in-out duration-200 whitespace-nowrap no-underline block px-6 py-3' if Jumpstart.config.payments_enabled? %>
<%= link_to edit_user_registration_path do %>
<%= link_to admin_root_path do %>
<iconify-icon icon="lucide:user" height="16"></iconify-icon>
My Profile
Dashboard
<% end %>
</div>
</li>
<% if current_user.admin? && respond_to?(:madmin_root_path) %>
<li>
<div>
<%= link_to admin_root_path do %>
<iconify-icon icon="lucide:user" height="16"></iconify-icon>
Dashboard
<% end %>
</div>
</li>
<li>
<div>
<%= link_to admin_sidekiq_web_path do %>
<iconify-icon icon="lucide:user" height="16"></iconify-icon>
Sidekiq
<% end %>
</div>
</li>
<% end %>
<hr class="-mx-2 my-1 border-base-content/10">
</li>
<li>
<div class="text-error">
<%= link_to destroy_user_session_path, method: :delete do %>
<iconify-icon icon="lucide:log-out" height="16"></iconify-icon>
Logout
<div>
<%= link_to admin_sidekiq_web_path do %>
<iconify-icon icon="lucide:user" height="16"></iconify-icon>
Sidekiq
<% end %>
</div>
</li>
</ul>
</div>
<% else %>
<div class="flex items-center gap-2">
<%= link_to new_user_session_path, class: "btn btn-primary" do %>
Login
<% end %>
<%= link_to new_user_registration_path, class: "btn btn-primary" do %>
Sign Up
<% end %>
</div>
<% end %>
<hr class="-mx-2 my-1 border-base-content/10">
<li>
<div class="text-error">
<%= link_to destroy_user_session_path, method: :delete do %>
<iconify-icon icon="lucide:log-out" height="16"></iconify-icon>
Logout
<% end %>
</div>
</li>
</ul>
</div>
</div>

View File

@@ -2,6 +2,11 @@ require "sidekiq/web"
Rails.application.routes.draw do
draw :madmin
resources :accounts, only: [] do
member do
get :switch
end
end
namespace :inbound_webhooks do
resources :github, controller: :github, only: [ :create ]
end

View File

@@ -0,0 +1,10 @@
class CreateAccounts < ActiveRecord::Migration[7.2]
def change
create_table :accounts do |t|
t.references :owner, foreign_key: { to_table: :users }, index: true
t.string :name, null: false
t.timestamps
end
end
end

View File

@@ -0,0 +1,10 @@
class CreateAccountUsers < ActiveRecord::Migration[7.2]
def change
create_table :account_users do |t|
t.references :user, null: false, foreign_key: true
t.references :account, null: false, foreign_key: true
t.timestamps
end
end
end

View File

@@ -3,7 +3,7 @@ class CreateClusters < ActiveRecord::Migration[7.2]
create_table :clusters do |t|
t.string :name, null: false
t.jsonb :kubeconfig, null: false, default: {}
t.references :user, null: false, foreign_key: true
t.references :account, null: false, foreign_key: true
t.integer :status, null: false, default: 0
t.timestamps

View File

@@ -3,8 +3,14 @@ class CreateServices < ActiveRecord::Migration[7.2]
create_table :services do |t|
t.references :project, null: false, foreign_key: true
t.integer :service_type, null: false
t.string :command, null: false
t.string :command
t.string :name, null: false
t.integer :replicas, default: 1
t.string :healthcheck_url
t.boolean :allow_public_networking, default: false
t.integer :status, default: 0
t.datetime :last_health_checked_at
t.integer :container_port, default: 3000
t.timestamps
end

View File

@@ -1,5 +0,0 @@
class AddContainerPortToServices < ActiveRecord::Migration[7.2]
def change
add_column :services, :container_port, :integer, default: 3000
end
end

View File

@@ -1,5 +0,0 @@
class RemoveNullConstraintOnServices < ActiveRecord::Migration[7.2]
def change
change_column_null :services, :command, true
end
end

View File

@@ -1,8 +0,0 @@
class AddHealthcheckUrlToServices < ActiveRecord::Migration[7.2]
def change
add_column :services, :healthcheck_url, :string
add_column :services, :allow_public_networking, :boolean, default: false
add_column :services, :status, :integer
add_column :services, :last_health_checked_at, :datetime
end
end

View File

@@ -1,5 +0,0 @@
class AddReplicasToServices < ActiveRecord::Migration[7.2]
def change
add_column :services, :replicas, :integer, default: 1
end
end

38
db/schema.rb generated
View File

@@ -10,10 +10,27 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.2].define(version: 2024_10_17_220734) do
ActiveRecord::Schema[7.2].define(version: 2024_10_09_172011) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "account_users", force: :cascade do |t|
t.bigint "user_id", null: false
t.bigint "account_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["account_id"], name: "index_account_users_on_account_id"
t.index ["user_id"], name: "index_account_users_on_user_id"
end
create_table "accounts", force: :cascade do |t|
t.bigint "owner_id"
t.string "name", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["owner_id"], name: "index_accounts_on_owner_id"
end
create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
@@ -78,11 +95,11 @@ ActiveRecord::Schema[7.2].define(version: 2024_10_17_220734) do
create_table "clusters", force: :cascade do |t|
t.string "name", null: false
t.jsonb "kubeconfig", default: {}, null: false
t.bigint "user_id", null: false
t.bigint "account_id", null: false
t.integer "status", default: 0, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_clusters_on_user_id"
t.index ["account_id"], name: "index_clusters_on_account_id"
end
create_table "cron_schedules", force: :cascade do |t|
@@ -222,14 +239,14 @@ ActiveRecord::Schema[7.2].define(version: 2024_10_17_220734) do
t.integer "service_type", null: false
t.string "command"
t.string "name", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "container_port", default: 3000
t.integer "replicas", default: 1
t.string "healthcheck_url"
t.boolean "allow_public_networking", default: false
t.integer "status"
t.integer "status", default: 0
t.datetime "last_health_checked_at"
t.integer "replicas", default: 1
t.integer "container_port", default: 3000
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["project_id"], name: "index_services_on_project_id"
end
@@ -249,11 +266,14 @@ ActiveRecord::Schema[7.2].define(version: 2024_10_17_220734) do
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
add_foreign_key "account_users", "accounts"
add_foreign_key "account_users", "users"
add_foreign_key "accounts", "users", column: "owner_id"
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
add_foreign_key "add_ons", "clusters"
add_foreign_key "builds", "projects"
add_foreign_key "clusters", "users"
add_foreign_key "clusters", "accounts"
add_foreign_key "cron_schedules", "services"
add_foreign_key "deployments", "builds"
add_foreign_key "environment_variables", "projects"

View File

@@ -6,7 +6,12 @@ metadata:
labels:
app: <%= service.name %>
spec:
replicas: 1
replicas: <%= service.replicas %>
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
selector:
matchLabels:
app: <%= service.name %>
@@ -24,5 +29,19 @@ spec:
name: <%= project.name %>
ports:
- containerPort: <%= service.container_port %>
<% if service.healthcheck_url.present? %>
livenessProbe:
httpGet:
path: <%= service.healthcheck_url %>
port: <%= service.container_port %>
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: <%= service.healthcheck_url %>
port: <%= service.container_port %>
initialDelaySeconds: 30
periodSeconds: 10
<% end %>
imagePullSecrets:
- name: dockerconfigjson-github-com

28
test/fixtures/account_users.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
# == Schema Information
#
# Table name: account_users
#
# id :bigint not null, primary key
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint not null
# user_id :bigint not null
#
# Indexes
#
# index_account_users_on_account_id (account_id)
# index_account_users_on_user_id (user_id)
#
# Foreign Keys
#
# fk_rails_... (account_id => accounts.id)
# fk_rails_... (user_id => users.id)
#
one:
user: one
account: one
two:
user: two
account: two

24
test/fixtures/accounts.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
# == Schema Information
#
# Table name: accounts
#
# id :bigint not null, primary key
# name :string not null
# created_at :datetime not null
# updated_at :datetime not null
# owner_id :bigint
#
# Indexes
#
# index_accounts_on_owner_id (owner_id)
#
# Foreign Keys
#
# fk_rails_... (owner_id => users.id)
#
one:
owner_id:
two:
owner_id:

View File

@@ -0,0 +1,25 @@
# == Schema Information
#
# Table name: accounts
#
# id :bigint not null, primary key
# name :string not null
# created_at :datetime not null
# updated_at :datetime not null
# owner_id :bigint
#
# Indexes
#
# index_accounts_on_owner_id (owner_id)
#
# Foreign Keys
#
# fk_rails_... (owner_id => users.id)
#
require "test_helper"
class AccountTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end

View File

@@ -0,0 +1,27 @@
# == Schema Information
#
# Table name: account_users
#
# id :bigint not null, primary key
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint not null
# user_id :bigint not null
#
# Indexes
#
# index_account_users_on_account_id (account_id)
# index_account_users_on_user_id (user_id)
#
# Foreign Keys
#
# fk_rails_... (account_id => accounts.id)
# fk_rails_... (user_id => users.id)
#
require "test_helper"
class AccountUserTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end