init
@@ -0,0 +1,17 @@
|
||||
FROM ruby:3.3-slim
|
||||
|
||||
# Install dependencies needed for gems and mysql2
|
||||
RUN apt-get update -qq && apt-get install -y \
|
||||
build-essential \
|
||||
default-libmysqlclient-dev \
|
||||
git \
|
||||
pkg-config
|
||||
|
||||
WORKDIR /app
|
||||
COPY Gemfile Gemfile.lock ./
|
||||
RUN bundle install
|
||||
|
||||
COPY . .
|
||||
|
||||
# Start the Puma server
|
||||
CMD ["bundle", "exec", "puma", "-p", "4567"]
|
||||
@@ -0,0 +1,40 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem 'sinatra', '~> 3.2.0'
|
||||
gem 'sinatra-contrib', '~> 3.2.0'
|
||||
gem 'activerecord', '~> 7.1'
|
||||
gem 'sinatra-activerecord', '~> 2.0'
|
||||
gem 'mysql2', '~> 0.5.3'
|
||||
gem 'rake', '~> 13.0'
|
||||
gem 'puma'
|
||||
gem 'acts_as_list'
|
||||
|
||||
# gem "rake"
|
||||
# gem 'sinatra', '~> 3.2'
|
||||
# gem "activerecord"
|
||||
# gem "activesupport"
|
||||
# gem "sinatra-activerecord"
|
||||
# gem "sinatra-contrib"
|
||||
|
||||
# gem "builder"
|
||||
# gem "log4r"
|
||||
|
||||
# gem "mysql2"
|
||||
# #gem "i18n"
|
||||
|
||||
# gem "sinatra-tailwind"
|
||||
|
||||
# gem "nokogiri", :require => false
|
||||
|
||||
group :development, :test do
|
||||
gem "awesome_print"
|
||||
|
||||
# gem "rspec"
|
||||
# gem "rspec-core"
|
||||
# gem "fuubar"
|
||||
# gem "ZenTest"
|
||||
# gem "autotest-fsevent", "~> 0.2.9"
|
||||
|
||||
# gem "sqlite3"
|
||||
# gem "ruby-debug19"
|
||||
end
|
||||
@@ -0,0 +1,87 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activemodel (7.2.3)
|
||||
activesupport (= 7.2.3)
|
||||
activerecord (7.2.3)
|
||||
activemodel (= 7.2.3)
|
||||
activesupport (= 7.2.3)
|
||||
timeout (>= 0.4.0)
|
||||
activesupport (7.2.3)
|
||||
base64
|
||||
benchmark (>= 0.3)
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||
connection_pool (>= 2.2.5)
|
||||
drb
|
||||
i18n (>= 1.6, < 2)
|
||||
logger (>= 1.4.2)
|
||||
minitest (>= 5.1)
|
||||
securerandom (>= 0.3)
|
||||
tzinfo (~> 2.0, >= 2.0.5)
|
||||
acts_as_list (1.2.6)
|
||||
activerecord (>= 6.1)
|
||||
activesupport (>= 6.1)
|
||||
awesome_print (1.9.2)
|
||||
base64 (0.3.0)
|
||||
benchmark (0.5.0)
|
||||
bigdecimal (4.1.2)
|
||||
concurrent-ruby (1.3.6)
|
||||
connection_pool (3.0.2)
|
||||
drb (2.2.3)
|
||||
i18n (1.14.8)
|
||||
concurrent-ruby (~> 1.0)
|
||||
logger (1.7.0)
|
||||
minitest (6.0.6)
|
||||
drb (~> 2.0)
|
||||
prism (~> 1.5)
|
||||
multi_json (1.21.1)
|
||||
mustermann (3.1.1)
|
||||
mysql2 (0.5.7)
|
||||
bigdecimal
|
||||
nio4r (2.7.5)
|
||||
prism (1.9.0)
|
||||
puma (8.0.2)
|
||||
nio4r (~> 2.0)
|
||||
rack (2.2.23)
|
||||
rack-protection (3.2.0)
|
||||
base64 (>= 0.1.0)
|
||||
rack (~> 2.2, >= 2.2.4)
|
||||
rake (13.4.2)
|
||||
securerandom (0.4.1)
|
||||
sinatra (3.2.0)
|
||||
mustermann (~> 3.0)
|
||||
rack (~> 2.2, >= 2.2.4)
|
||||
rack-protection (= 3.2.0)
|
||||
tilt (~> 2.0)
|
||||
sinatra-activerecord (2.0.28)
|
||||
activerecord (>= 4.1)
|
||||
sinatra (>= 1.0)
|
||||
sinatra-contrib (3.2.0)
|
||||
multi_json (>= 0.0.2)
|
||||
mustermann (~> 3.0)
|
||||
rack-protection (= 3.2.0)
|
||||
sinatra (= 3.2.0)
|
||||
tilt (~> 2.0)
|
||||
tilt (2.7.0)
|
||||
timeout (0.6.1)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
|
||||
PLATFORMS
|
||||
aarch64-linux
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
activerecord (~> 7.1)
|
||||
acts_as_list
|
||||
awesome_print
|
||||
mysql2 (~> 0.5.3)
|
||||
puma
|
||||
rake (~> 13.0)
|
||||
sinatra (~> 3.2.0)
|
||||
sinatra-activerecord (~> 2.0)
|
||||
sinatra-contrib (~> 3.2.0)
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.22
|
||||
@@ -0,0 +1,2 @@
|
||||
require 'sinatra/activerecord/rake'
|
||||
require_relative 'config/environment'
|
||||
@@ -0,0 +1,152 @@
|
||||
class ApplicationController < Sinatra::Base
|
||||
register Sinatra::ActiveRecordExtension
|
||||
register Sinatra::Contrib
|
||||
|
||||
configure do
|
||||
set :views, File.expand_path('../../views', __FILE__)
|
||||
set :public_folder, File.expand_path('../../../public', __FILE__)
|
||||
set :root, File.expand_path('../../..', __FILE__)
|
||||
set :trust_proxy, true
|
||||
set :bind, '0.0.0.0'
|
||||
set :port, 4567
|
||||
end
|
||||
|
||||
get '/' do
|
||||
client_ip = request.ip.to_s
|
||||
puts client_ip
|
||||
|
||||
if client_ip.start_with?('100.')
|
||||
@url_type = 'tailscale'
|
||||
elsif params[:local_ip].present? && params[:local_ip] == 'true'
|
||||
@url_type = 'local_ip'
|
||||
else
|
||||
@url_type = 'local_domain'
|
||||
end
|
||||
@media = ServiceType.media
|
||||
@media.services.map { |serv| serv.set_current_url(@url_type) }
|
||||
@support = ServiceType.support
|
||||
@support.services.map { |serv| serv.set_current_url(@url_type) }
|
||||
@admin = ServiceType.admin
|
||||
@admin.services.map { |serv| serv.set_current_url(@url_type) }
|
||||
erb :'index'
|
||||
end
|
||||
|
||||
get '/admin' do
|
||||
erb :'admin/index'
|
||||
end
|
||||
|
||||
get '/admin/local_network' do
|
||||
@local_network = LocalNetwork.first || LocalNetwork.new
|
||||
@is_readonly = true
|
||||
erb :'admin/local_network/index'
|
||||
end
|
||||
|
||||
get '/admin/local_network/edit' do
|
||||
@local_network = LocalNetwork.first || LocalNetwork.create
|
||||
@is_readonly = false
|
||||
erb :'admin/local_network/index'
|
||||
end
|
||||
|
||||
post '/admin/local_network' do
|
||||
@local_network = LocalNetwork.find(params[:local_network][:id])
|
||||
if @local_network.update(params[:local_network])
|
||||
redirect 'admin/local_network'
|
||||
else
|
||||
erb :'admin/local_network/index'
|
||||
end
|
||||
end
|
||||
|
||||
get '/admin/machines' do
|
||||
@machines = Machine.all
|
||||
@is_readonly = true
|
||||
erb :'admin/machines/index'
|
||||
end
|
||||
|
||||
get '/admin/machines/edit' do
|
||||
@machines = Machine.all
|
||||
@is_readonly = false
|
||||
erb :'admin/machines/index'
|
||||
end
|
||||
|
||||
post '/admin/machines' do
|
||||
params[:machines].each do |machine_data|
|
||||
machine_data["local_network_id"] = 1
|
||||
if machine_data[:id].empty? && machine_data[:name].present?
|
||||
Machine.create(machine_data)
|
||||
elsif machine_data[:id].present?
|
||||
@machine = Machine.find(machine_data[:id])
|
||||
@machine.update(machine_data)
|
||||
end
|
||||
end
|
||||
redirect 'admin/machines'
|
||||
end
|
||||
|
||||
get '/admin/service_types' do
|
||||
@service_types = ServiceType.all
|
||||
@is_readonly = true
|
||||
erb :'admin/service_types/index'
|
||||
end
|
||||
|
||||
get '/admin/service_types/edit' do
|
||||
@service_types = ServiceType.all
|
||||
@is_readonly = false
|
||||
erb :'admin/service_types/index'
|
||||
end
|
||||
|
||||
get '/admin/service_types/media' do
|
||||
@service_type = ServiceType.media
|
||||
erb :'admin/service_types/position'
|
||||
end
|
||||
|
||||
get '/admin/service_types/admin' do
|
||||
@service_type = ServiceType.admin
|
||||
erb :'admin/service_types/position'
|
||||
end
|
||||
|
||||
get '/admin/service_types/support' do
|
||||
@service_type = ServiceType.support
|
||||
erb :'admin/service_types/position'
|
||||
end
|
||||
|
||||
post '/admin/service_types' do
|
||||
puts params
|
||||
params[:service_types].each do |service_type_data|
|
||||
if service_type_data[:id].empty? && service_type_data[:name].present?
|
||||
ServiceType.create(service_type_data)
|
||||
elsif service_type_data[:id].present?
|
||||
@service_type = ServiceType.find(service_type_data[:id])
|
||||
@service_type.update(service_type_data)
|
||||
end
|
||||
end
|
||||
redirect 'admin/service_types'
|
||||
end
|
||||
|
||||
get '/admin/services' do
|
||||
@services = Service.all
|
||||
@service_types = ServiceType.all
|
||||
@machines = Machine.all
|
||||
@is_readonly = true
|
||||
erb :'admin/services/index'
|
||||
end
|
||||
|
||||
get '/admin/services/edit' do
|
||||
@services = Service.all
|
||||
@service_types = ServiceType.all
|
||||
@machines = Machine.all
|
||||
@is_readonly = false
|
||||
erb :'admin/services/index'
|
||||
end
|
||||
|
||||
post '/admin/services' do
|
||||
params[:services].each do |service_data|
|
||||
if service_data[:id].empty? && service_data[:name].present?
|
||||
Service.create(service_data)
|
||||
elsif service_data[:id].present?
|
||||
@service = Service.find(service_data[:id])
|
||||
@service.update(service_data)
|
||||
end
|
||||
end
|
||||
redirect 'admin/services'
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
# encoding: utf-8
|
||||
|
||||
class LocalNetwork < ActiveRecord::Base
|
||||
# attr_accessible :title, :body
|
||||
|
||||
# validates :title, :presence => true
|
||||
# validates :body, :presence => true
|
||||
|
||||
has_many :machines
|
||||
# # belongs_to :someotherthing
|
||||
|
||||
# scope :published, -> { order("created_at DESC") }
|
||||
|
||||
end
|
||||
@@ -0,0 +1,26 @@
|
||||
# encoding: utf-8
|
||||
|
||||
class Machine < ActiveRecord::Base
|
||||
# attr_accessible :title, :body
|
||||
|
||||
# validates :title, :presence => true
|
||||
# validates :body, :presence => true
|
||||
|
||||
has_many :services
|
||||
belongs_to :local_network
|
||||
|
||||
# scope :published, -> { order("created_at DESC") }
|
||||
|
||||
# def tailscale_ip
|
||||
# tailscale_ip
|
||||
# end
|
||||
|
||||
def local_domain
|
||||
"#{domain}.#{local_network.tld}"
|
||||
end
|
||||
|
||||
def local_ip
|
||||
"#{local_network.subnet}.#{local_ip_octet}"
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
# encoding: utf-8
|
||||
|
||||
class Post < ActiveRecord::Base
|
||||
# attr_accessible :title, :body
|
||||
|
||||
# validates :title, :presence => true
|
||||
# validates :body, :presence => true
|
||||
|
||||
# # has_many :somethings
|
||||
# # belongs_to :someotherthing
|
||||
|
||||
# scope :published, -> { order("created_at DESC") }
|
||||
|
||||
end
|
||||
@@ -0,0 +1,15 @@
|
||||
# encoding: utf-8
|
||||
|
||||
class ServiceType < ActiveRecord::Base
|
||||
# attr_accessible :title, :body
|
||||
|
||||
# validates :title, :presence => true
|
||||
# validates :body, :presence => true
|
||||
|
||||
has_many :services, -> { order(position: :asc) }
|
||||
|
||||
scope :media, -> { includes(:services).find_by(name: "Media") }
|
||||
scope :support, -> { includes(:services).find_by(name: "Support") }
|
||||
scope :admin, -> { includes(:services).find_by(name: "Admin") }
|
||||
|
||||
end
|
||||
@@ -0,0 +1,57 @@
|
||||
# encoding: utf-8
|
||||
|
||||
class Service < ActiveRecord::Base
|
||||
attr_accessor :current_url
|
||||
|
||||
# validates :title, :presence => true
|
||||
# validates :body, :presence => true
|
||||
|
||||
belongs_to :machine
|
||||
belongs_to :service_type
|
||||
acts_as_list scope: :service_type
|
||||
|
||||
# scope :published, -> { order("created_at DESC") }
|
||||
|
||||
def tailscale_ip_url
|
||||
if machine.tailscale_ip.present?
|
||||
url = "http://#{machine.tailscale_ip}:#{port}"
|
||||
if url_path.present?
|
||||
url.concat(url_path)
|
||||
end
|
||||
url
|
||||
end
|
||||
end
|
||||
|
||||
def local_domain_url
|
||||
url = "http://"
|
||||
if subdomain.present?
|
||||
url.concat("#{subdomain}.")
|
||||
end
|
||||
url.concat(machine.local_domain)
|
||||
if url_path.present?
|
||||
url.concat(url_path)
|
||||
end
|
||||
url
|
||||
end
|
||||
|
||||
def local_ip_url
|
||||
url = "http://#{machine.local_ip}:#{port}"
|
||||
if url_path.present?
|
||||
url.concat(url_path)
|
||||
end
|
||||
url
|
||||
end
|
||||
|
||||
def set_current_url(url_type)
|
||||
if url_type == 'tailscale'
|
||||
self.current_url = self.tailscale_ip_url
|
||||
elsif url_type == 'local_ip'
|
||||
self.current_url = self.local_ip_url
|
||||
else
|
||||
self.current_url = self.local_domain_url
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
@@ -0,0 +1,23 @@
|
||||
<div class="text-4xl mb-10">
|
||||
Network Directory Admin
|
||||
</div>
|
||||
|
||||
<div class="w-1/2 flex flex-col space-y-10 ml-20 mt-10">
|
||||
<a href="/admin/local_network" class="inline-block w-50 px-3 py-1 text-center font-semibold text-[#140029] bg-[#8000FF] border-2 border-[#8000FF] rounded-lg hover:text-[#2FD400] hover:bg-[#140029] hover:border-[#2FD400] transition-colors duration-200">
|
||||
Local Network
|
||||
</a>
|
||||
|
||||
<a href="/admin/machines" class="inline-block w-50 px-3 py-1 text-center font-semibold text-[#140029] bg-[#8000FF] border-2 border-[#8000FF] rounded-lg hover:text-[#2FD400] hover:bg-[#140029] hover:border-[#2FD400] transition-colors duration-200">
|
||||
Machines
|
||||
</a>
|
||||
|
||||
<a href="/admin/service_types" class="inline-block w-50 px-3 py-1 text-center font-semibold text-[#140029] bg-[#8000FF] border-2 border-[#8000FF] rounded-lg hover:text-[#2FD400] hover:bg-[#140029] hover:border-[#2FD400] transition-colors duration-200">
|
||||
Service Types
|
||||
</a>
|
||||
|
||||
<a href="/admin/services" class="inline-block w-50 px-3 py-1 text-center font-semibold text-[#140029] bg-[#8000FF] border-2 border-[#8000FF] rounded-lg hover:text-[#2FD400] hover:bg-[#140029] hover:border-[#2FD400] transition-colors duration-200">
|
||||
Services
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<form action="/admin/local_network" method="POST">
|
||||
|
||||
<div class="text-4xl mb-10">
|
||||
Local Network Settings
|
||||
</div>
|
||||
<% unless @is_readonly %>
|
||||
<div class="w-1/2 flex flex-none items-center justify-between ml-5 mb-2">
|
||||
<div class="flex-none text-xl text-[#2FD400]">Edit</div>
|
||||
<div class="grow h-[1px] mt-1 bg-[#2FD400]"></div>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="flex ml-10 space-x-2">
|
||||
|
||||
<input type="hidden" name="local_network[id]" value="<%= @local_network.id %>">
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input class="h-8 border-2 border-[#8000FF] rounded-lg text-center" type="text" id="local_network_name" name="local_network[name]" value="<%= @local_network.name %>" <%= 'readonly' if @is_readonly %> >
|
||||
<div class="text-[#2FD400]">Name</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input class="h-8 border-2 border-[#8000FF] rounded-lg text-center" type="text" id="local_network_subnet" name="local_network[subnet]" value="<%= @local_network.subnet %>" <%= 'readonly' if @is_readonly %> >
|
||||
<div class="text-[#2FD400]">Subnet</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input class="h-8 border-2 border-[#8000FF] rounded-lg text-center" type="text" id="local_network_tld" name="local_network[tld]" value="<%= @local_network.tld %>" <%= 'readonly' if @is_readonly %> >
|
||||
<div class="text-[#2FD400]">Top Level Domain</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col space-y-2 mt-10">
|
||||
<% if @is_readonly %>
|
||||
<div class="w-1/2 flex justify-end space-x-5">
|
||||
<a href="/admin/local_network/edit" class="inline-block w-50 px-3 py-1 mt-20 text-center font-semibold text-[#140029] bg-[#8000FF] border-2 border-[#8000FF] rounded-lg hover:text-[#2FD400] hover:bg-[#140029] hover:border-[#2FD400] transition-colors duration-200">
|
||||
Edit Local Network
|
||||
</a>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="w-1/2 flex justify-end space-x-5">
|
||||
<button type="submit" class="inline-block w-50 px-3 py-1 mt-20 text-center font-semibold text-[#140029] bg-[#8000FF] border-2 border-[#8000FF] rounded-lg hover:text-[#2FD400] hover:bg-[#140029] hover:border-[#2FD400] transition-colors duration-200">
|
||||
Save Local Network
|
||||
</button>
|
||||
<a href="/admin/local_network" class="inline-block w-50 px-3 py-1 mt-20 text-center font-semibold text-[#140029] bg-[#F80800] border-2 border-[#F80800] rounded-lg hover:text-[#F80800] hover:bg-[#140029] hover:border-[#F80800] transition-colors duration-200">
|
||||
Cancel
|
||||
</a>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
@@ -0,0 +1,135 @@
|
||||
<form action="/admin/machines" method="POST">
|
||||
|
||||
<div class="text-4xl mb-10">
|
||||
Network Machines
|
||||
</div>
|
||||
<% unless @is_readonly %>
|
||||
<div class="w-2/3 flex flex-none items-center justify-between ml-5 mb-2">
|
||||
<div class="flex-none text-xl text-[#2FD400]">Edit</div>
|
||||
<div class="grow h-[1px] mt-1 bg-[#2FD400]"></div>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="flex flex-col ml-10 space-y-2">
|
||||
<% @machines.each_with_index do |mach, index| %>
|
||||
<div class="flex space-x-2">
|
||||
<input type="hidden" name="machines[][id]" value="<%= mach.id %>">
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input type="text"
|
||||
class="h-8 border-2 border-[#8000FF] rounded-lg text-center"
|
||||
id="mach_<%= index %>_name"
|
||||
name="machines[][name]"
|
||||
value="<%= mach.name %>"
|
||||
<%= 'readonly' if @is_readonly %> >
|
||||
<% if index == @machines.length - 1 %>
|
||||
<div class="text-[#2FD400]">Name</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input type="text"
|
||||
class="h-8 border-2 border-[#8000FF] rounded-lg text-center"
|
||||
id="mach_<%= index %>_domain"
|
||||
name="machines[][domain]"
|
||||
value="<%= mach.domain %>"
|
||||
<%= 'readonly' if @is_readonly %> >
|
||||
<% if index == @machines.length - 1 %>
|
||||
<div class="text-[#2FD400]">Domain</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input type="text"
|
||||
class="h-8 border-2 border-[#8000FF] rounded-lg text-center"
|
||||
id="mach_<%= index %>_local_ip_octet"
|
||||
name="machines[][local_ip_octet]"
|
||||
value="<%= mach.local_ip_octet %>"
|
||||
<%= 'readonly' if @is_readonly %> >
|
||||
<% if index == @machines.length - 1 %>
|
||||
<div class="text-[#2FD400]">Local IP Octet</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input type="text"
|
||||
class="h-8 border-2 border-[#8000FF] rounded-lg text-center"
|
||||
id="mach_<%= index %>_tailscale_ip"
|
||||
name="machines[][tailscale_ip]"
|
||||
value="<%= mach.tailscale_ip %>"
|
||||
<%= 'readonly' if @is_readonly %> >
|
||||
<% if index == @machines.length - 1 %>
|
||||
<div class="text-[#2FD400]">Tailscale IP</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex flex-col space-y-2 mt-10">
|
||||
<% if @is_readonly %>
|
||||
<div class="w-2/3 flex justify-end space-x-5">
|
||||
<a href="/admin/machines/edit" class="inline-block w-50 px-3 py-1 mt-20 text-center font-semibold text-[#140029] bg-[#8000FF] border-2 border-[#8000FF] rounded-lg hover:text-[#2FD400] hover:bg-[#140029] hover:border-[#2FD400] transition-colors duration-200">
|
||||
Edit/Add Machines
|
||||
</a>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="w-2/3 flex flex-none items-center justify-between ml-5 mb-2">
|
||||
<div class="flex-none text-xl text-[#2FD400]">New</div>
|
||||
<div class="grow h-[1px] mt-1 bg-[#2FD400]"></div>
|
||||
</div>
|
||||
<div class="flex space-x-2 mb-10 ml-10">
|
||||
<input type="hidden" name="machines[][id]" value="">
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input type="text"
|
||||
class="h-8 border-2 border-[#8000FF] rounded-lg text-center"
|
||||
id="mach_new_name"
|
||||
name="machines[][name]"
|
||||
value=""
|
||||
<%= 'readonly' if @is_readonly %> >
|
||||
<label for="mach_new_name" class="text-[#2FD400]">Name</label>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input type="text"
|
||||
class="h-8 border-2 border-[#8000FF] rounded-lg text-center"
|
||||
id="mach_new_domain"
|
||||
name="machines[][domain]"
|
||||
value=""
|
||||
<%= 'readonly' if @is_readonly %> >
|
||||
<label for="mach_new_domain" class="text-[#2FD400]">Domain</label>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input type="text"
|
||||
class="h-8 border-2 border-[#8000FF] rounded-lg text-center"
|
||||
id="mach_new_local_ip_octet"
|
||||
name="machines[][local_ip_octet]"
|
||||
value=""
|
||||
<%= 'readonly' if @is_readonly %> >
|
||||
<label for="mach_new_local_ip_octet" class="text-[#2FD400]">Local IP Octet</label>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input type="text"
|
||||
class="h-8 border-2 border-[#8000FF] rounded-lg text-center"
|
||||
id="mach_new_tailscale_ip"
|
||||
name="machines[][tailscale_ip]"
|
||||
value=""
|
||||
<%= 'readonly' if @is_readonly %> >
|
||||
<label for="mach_new_tailscale_ip" class="text-[#2FD400]">Tailscale IP</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-2/3 flex justify-end space-x-5">
|
||||
<button type="submit" class="inline-block w-50 px-3 py-1 mt-20 text-center font-semibold text-[#140029] bg-[#8000FF] border-2 border-[#8000FF] rounded-lg hover:text-[#2FD400] hover:bg-[#140029] hover:border-[#2FD400] transition-colors duration-200">
|
||||
Save Machines
|
||||
</button>
|
||||
<a href="/admin/machines" class="inline-block w-50 px-3 py-1 mt-20 text-center font-semibold text-[#140029] bg-[#F80800] border-2 border-[#F80800] rounded-lg hover:text-[#F80800] hover:bg-[#140029] hover:border-[#F80800] transition-colors duration-200">
|
||||
Cancel
|
||||
</a>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
@@ -0,0 +1,91 @@
|
||||
<form action="/admin/service_types" method="POST">
|
||||
|
||||
<div class="text-4xl mb-10">
|
||||
Service Types
|
||||
</div>
|
||||
<% unless @is_readonly %>
|
||||
<div class="w-1/2 flex flex-none items-center justify-between ml-5 mb-2">
|
||||
<div class="flex-none text-xl text-[#2FD400]">Edit</div>
|
||||
<div class="grow h-[1px] mt-1 bg-[#2FD400]"></div>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="flex flex-col ml-10 space-y-2">
|
||||
<% @service_types.each_with_index do |servt, index| %>
|
||||
<div class="flex space-x-2">
|
||||
<input type="hidden" name="service_types[][id]" value="<%= servt.id %>">
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input type="text"
|
||||
class="h-8 border-2 border-[#8000FF] rounded-lg text-center"
|
||||
id="servt_<%= index %>_name"
|
||||
name="service_types[][name]"
|
||||
value="<%= servt.name %>"
|
||||
<%= 'readonly' if @is_readonly %> >
|
||||
<% if index == @service_types.length - 1 %>
|
||||
<div class="text-[#2FD400]">Name</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input type="text"
|
||||
class="h-8 border-2 border-[#8000FF] rounded-lg text-center"
|
||||
id="servt_<%= index %>_hex_color"
|
||||
name="service_types[][hex_color]"
|
||||
value="<%= servt.hex_color %>"
|
||||
<%= 'readonly' if @is_readonly %> >
|
||||
<% if index == @service_types.length - 1 %>
|
||||
<div class="text-[#2FD400]">Hex Color</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex flex-col space-y-2 mt-10">
|
||||
<% if @is_readonly %>
|
||||
<div class="w-1/2 flex justify-end space-x-5">
|
||||
<a href="/admin/service_types/edit" class="inline-block w-60 px-3 py-1 mt-20 text-center font-semibold text-[#140029] bg-[#8000FF] border-2 border-[#8000FF] rounded-lg hover:text-[#2FD400] hover:bg-[#140029] hover:border-[#2FD400] transition-colors duration-200">
|
||||
Edit/Add Service Types
|
||||
</a>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="w-1/2 flex flex-none items-center justify-between ml-5 mb-2">
|
||||
<div class="flex-none text-xl text-[#2FD400]">New</div>
|
||||
<div class="grow h-[1px] mt-1 bg-[#2FD400]"></div>
|
||||
</div>
|
||||
<div class="flex space-x-2 mb-10 ml-10">
|
||||
<input type="hidden" name="service_types[][id]" value="">
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input type="text"
|
||||
class="h-8 border-2 border-[#8000FF] rounded-lg text-center"
|
||||
id="servt_new_name"
|
||||
name="service_types[][name]"
|
||||
value=""
|
||||
<%= 'readonly' if @is_readonly %> >
|
||||
<label for="servt_new_name" class="text-[#2FD400]">Name</label>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input type="text"
|
||||
class="h-8 border-2 border-[#8000FF] rounded-lg text-center"
|
||||
id="servt_new_hex_color"
|
||||
name="service_types[][hex_color]"
|
||||
value=""
|
||||
<%= 'readonly' if @is_readonly %> >
|
||||
<label for="servt_new_hex_color" class="text-[#2FD400]">Hex Color</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-1/2 flex justify-end space-x-5">
|
||||
<button type="submit" class="inline-block w-50 px-3 py-1 mt-20 text-center font-semibold text-[#140029] bg-[#8000FF] border-2 border-[#8000FF] rounded-lg hover:text-[#2FD400] hover:bg-[#140029] hover:border-[#2FD400] transition-colors duration-200">
|
||||
Save Service Types
|
||||
</button>
|
||||
<a href="/admin/service_types" class="inline-block w-50 px-3 py-1 mt-20 text-center font-semibold text-[#140029] bg-[#F80800] border-2 border-[#F80800] rounded-lg hover:text-[#F80800] hover:bg-[#140029] hover:border-[#F80800] transition-colors duration-200">
|
||||
Cancel
|
||||
</a>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
@@ -0,0 +1,45 @@
|
||||
<form action="/admin/services" method="POST">
|
||||
|
||||
<div class="text-4xl mb-10">
|
||||
Service Positions
|
||||
</div>
|
||||
<div class="flex flex-col ml-10 space-y-2">
|
||||
<% @service_type.services.each_with_index do |service, index| %>
|
||||
<div class="flex space-x-2">
|
||||
<input type="hidden" name="services[][id]" value="<%= service.id %>">
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input type="text"
|
||||
class="h-8 border-2 border-[#8000FF] rounded-lg text-center"
|
||||
id="service_<%= index %>_name"
|
||||
name="services[][name]"
|
||||
value="<%= service.name %>"
|
||||
readonly >
|
||||
<% if index == @service_type.services.length - 1 %>
|
||||
<div class="text-[#2FD400]">Name</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input type="text"
|
||||
class="h-8 border-2 border-[#8000FF] rounded-lg text-center"
|
||||
id="service_<%= index %>_position"
|
||||
name="services[][position]"
|
||||
value="<%= service.position %>" >
|
||||
<% if index == @service_type.services.length - 1 %>
|
||||
<div class="text-[#2FD400]">Position</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="w-1/2 flex justify-end space-x-5">
|
||||
<button type="submit" class="inline-block w-50 px-3 py-1 mt-20 text-center font-semibold text-[#140029] bg-[#8000FF] border-2 border-[#8000FF] rounded-lg hover:text-[#2FD400] hover:bg-[#140029] hover:border-[#2FD400] transition-colors duration-200">
|
||||
Save Service Positions
|
||||
</button>
|
||||
<a href="/admin/service_types" class="inline-block w-50 px-3 py-1 mt-20 text-center font-semibold text-[#140029] bg-[#F80800] border-2 border-[#F80800] rounded-lg hover:text-[#F80800] hover:bg-[#140029] hover:border-[#F80800] transition-colors duration-200">
|
||||
Cancel
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
@@ -0,0 +1,177 @@
|
||||
<form action="/admin/services" method="POST">
|
||||
|
||||
<div class="text-4xl mb-10">
|
||||
Services
|
||||
</div>
|
||||
<% unless @is_readonly %>
|
||||
<div class="w-19/20 flex flex-none items-center justify-between ml-5 mb-2">
|
||||
<div class="flex-none text-xl text-[#2FD400]">Edit</div>
|
||||
<div class="grow h-[1px] mt-1 bg-[#2FD400]"></div>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="flex flex-col ml-10 space-y-2">
|
||||
<% @services.each_with_index do |serv, index| %>
|
||||
<div class="flex space-x-2">
|
||||
<input type="hidden" name="services[][id]" value="<%= serv.id %>">
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input type="text"
|
||||
class="h-8 border-2 border-[#8000FF] rounded-lg text-center"
|
||||
id="serv_<%= index %>_name"
|
||||
name="services[][name]"
|
||||
value="<%= serv.name %>"
|
||||
<%= 'readonly' if @is_readonly %> >
|
||||
<% if index == @services.length - 1 %>
|
||||
<div class="text-[#2FD400]">Name</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<select class="h-8 border-2 border-[#8000FF] rounded-lg text-center" name="services[][service_type_id]" id="service_type_select" <%= 'disabled' if @is_readonly %> >
|
||||
<option value="">Select a Service Type</option>
|
||||
<% @service_types.each do |service_type| %>
|
||||
<option value="<%= service_type.id %>" <%= "selected" if serv.service_type_id == service_type.id %>><%= service_type.name %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
<% if index == @services.length - 1 %>
|
||||
<div class="text-[#2FD400]">Service Type</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<select class="h-8 border-2 border-[#8000FF] rounded-lg text-center" name="services[][machine_id]" id="machine_select" <%= 'disabled' if @is_readonly %> >
|
||||
<option value="">Select a Service Type</option>
|
||||
<% @machines.each do |machine| %>
|
||||
<option value="<%= machine.id %>" <%= "selected" if serv.machine_id == machine.id %>><%= machine.name %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
<% if index == @services.length - 1 %>
|
||||
<div class="text-[#2FD400]">Machine</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input type="text"
|
||||
class="h-8 border-2 border-[#8000FF] rounded-lg text-center"
|
||||
id="serv_<%= index %>_subdomain"
|
||||
name="services[][subdomain]"
|
||||
value="<%= serv.subdomain%>"
|
||||
<%= 'readonly' if @is_readonly %> >
|
||||
<% if index == @services.length - 1 %>
|
||||
<div class="text-[#2FD400]">Subdomain</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input type="text"
|
||||
class="h-8 border-2 border-[#8000FF] rounded-lg text-center"
|
||||
id="serv_<%= index %>_port"
|
||||
name="services[][port]"
|
||||
value="<%= serv.port%>"
|
||||
<%= 'readonly' if @is_readonly %> >
|
||||
<% if index == @services.length - 1 %>
|
||||
<div class="text-[#2FD400]">Port</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input type="text"
|
||||
class="h-8 border-2 border-[#8000FF] rounded-lg text-center"
|
||||
id="serv_<%= index %>_url_path"
|
||||
name="services[][url_path]"
|
||||
value="<%= serv.url_path%>"
|
||||
<%= 'readonly' if @is_readonly %> >
|
||||
<% if index == @services.length - 1 %>
|
||||
<div class="text-[#2FD400]">URL Path (optional)</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex flex-col space-y-2 mt-10">
|
||||
<% if @is_readonly %>
|
||||
<div class="w-19/20 flex justify-end space-x-5">
|
||||
<a href="/admin/services/edit" class="inline-block w-50 px-3 py-1 mt-20 text-center font-semibold text-[#140029] bg-[#8000FF] border-2 border-[#8000FF] rounded-lg hover:text-[#2FD400] hover:bg-[#140029] hover:border-[#2FD400] transition-colors duration-200">
|
||||
Edit/Add Services
|
||||
</a>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="w-19/20 flex flex-none items-center justify-between ml-5 mb-2">
|
||||
<div class="flex-none text-xl text-[#2FD400]">New</div>
|
||||
<div class="grow h-[1px] mt-1 bg-[#2FD400]"></div>
|
||||
</div>
|
||||
<div class="flex space-x-2 mb-10 ml-10"">
|
||||
<input type="hidden" name="services[][id]" value="">
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input type="text"
|
||||
class="h-8 border-2 border-[#8000FF] rounded-lg text-center"
|
||||
id="serv_new_name"
|
||||
name="services[][name]"
|
||||
value="">
|
||||
<div class="text-[#2FD400]">Name</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<select class="h-8 border-2 border-[#8000FF] rounded-lg text-center" name="services[][service_type_id]" id="service_type_select" <%= 'disabled' if @is_readonly %> >
|
||||
<option value="">Select a Service Type</option>
|
||||
<% @service_types.each do |service_type| %>
|
||||
<option value="<%= service_type.id %>"><%= service_type.name %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
<div class="text-[#2FD400]">Service Type</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<select class="h-8 border-2 border-[#8000FF] rounded-lg text-center" name="services[][machine_id]" id="machine_select" <%= 'disabled' if @is_readonly %> >
|
||||
<option value="">Select a Service Type</option>
|
||||
<% @machines.each do |machine| %>
|
||||
<option value="<%= machine.id %>"><%= machine.name %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
<div class="text-[#2FD400]">Machine</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input type="text"
|
||||
class="h-8 border-2 border-[#8000FF] rounded-lg text-center"
|
||||
id="serv_new_subdomain"
|
||||
name="services[][subdomain]"
|
||||
value="">
|
||||
<div class="text-[#2FD400]">Subdomain</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input type="text"
|
||||
class="h-8 border-2 border-[#8000FF] rounded-lg text-center"
|
||||
id="serv_new_port"
|
||||
name="services[][port]"
|
||||
value="">
|
||||
<div class="text-[#2FD400]">Port</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center">
|
||||
<input type="text"
|
||||
class="h-8 border-2 border-[#8000FF] rounded-lg text-center"
|
||||
id="serv_new_url_path"
|
||||
name="services[][url_path]"
|
||||
value="">
|
||||
<div class="text-[#2FD400]">URL Path (optional)</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="w-19/20 flex justify-end space-x-5">
|
||||
<button type="submit" class="inline-block w-50 px-3 py-1 mt-20 text-center font-semibold text-[#140029] bg-[#8000FF] border-2 border-[#8000FF] rounded-lg hover:text-[#2FD400] hover:bg-[#140029] hover:border-[#2FD400] transition-colors duration-200">
|
||||
Save Services
|
||||
</button>
|
||||
<a href="/admin/services" class="inline-block w-50 px-3 py-1 mt-20 text-center font-semibold text-[#140029] bg-[#F80800] border-2 border-[#F80800] rounded-lg hover:text-[#F80800] hover:bg-[#140029] hover:border-[#F80800] transition-colors duration-200">
|
||||
Cancel
|
||||
</a>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
@@ -0,0 +1,66 @@
|
||||
<div class="flex flex-col gap-y-2 bg-[url('/images/purpleandblackfire.svg')] bg-size-[auto_113%] bg-center bg-no-repeat">
|
||||
<div class="flex flex-none items-center py-2 mb-13">
|
||||
<div class="flex-none text-5xl text-[#8000FF]">Local Network Directory</div>
|
||||
<div class="grow-5 h-[3px] mt-3 bg-[#8000FF]"></div>
|
||||
<div class="flex-none mx-1 mt-2 text-2xl text-[#2FD400]">
|
||||
<% if @url_type == 'tailscale' %>
|
||||
<%= @url_type.gsub('_', ' ') %>
|
||||
<% elsif @url_type == 'local_ip' %>
|
||||
<a href="/" class="flex h-full text-center hover:text-[#009fff]">
|
||||
<%= @url_type.gsub('_', ' ') %>
|
||||
</a>
|
||||
<% else %>
|
||||
<a href="?local_ip=true" class="flex h-full text-center hover:text-[#009fff]">
|
||||
<%= @url_type.gsub('_', ' ') %>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="grow-1 h-[3px] mt-3 bg-[#8000FF]"></div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex gap-x-7">
|
||||
<div class="flex flex-col w-3/5 items-start">
|
||||
<div class="flex bg-[#140029] text-center text-3xl text-[#<%= @media.hex_color %>] ml-15 -mb-4 px-2 z-1">
|
||||
<%= @media.name %>
|
||||
</div>
|
||||
<div class="flex flex-wrap text-2xl gap-1 py-4 justify-evenly items-center border-4 border-[#<%= @media.hex_color %>] rounded-lg">
|
||||
<% @media.services.each do |service| %>
|
||||
<a href=<%= service.current_url %> class="flex justify-center items-center h-15 w-55 px-2 py-10 my-4 text-center font-bold text-[#<%= @media.hex_color %>] bg-transparent border-2 border-[#<%= @media.hex_color %>] rounded-lg hover:text-[#140029] hover:bg-[#<%= @media.hex_color %>] transition-colors duration-200">
|
||||
<%= service.name %>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-2/5 items-start">
|
||||
<div class="bg-[#140029] text-center text-3xl text-[#<%= @support.hex_color %>] ml-15 -mb-4 px-2 z-1">
|
||||
<%= @support.name %>
|
||||
</div>
|
||||
<div class="flex flex-wrap text-2xl gap-1 py-4 justify-evenly items-center border-4 border-[#<%= @support.hex_color %>] rounded-lg">
|
||||
<% @support.services.each do |service| %>
|
||||
<a href=<%= service.current_url %> class="flex justify-center items-center h-15 w-55 px-2 py-10 my-4 text-center font-bold text-[#<%= @support.hex_color %>] bg-transparent border-2 border-[#<%= @support.hex_color %>] rounded-lg hover:text-[#140029] hover:bg-[#<%= @support.hex_color %>] transition-colors duration-200">
|
||||
<%= service.name %>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full items-start">
|
||||
<div class="bg-[#140029] text-center text-3xl text-[#<%= @admin.hex_color %>] ml-15 -mb-4 px-2 z-1">
|
||||
<%= @admin.name %>
|
||||
</div>
|
||||
<div class="flex flex-wrap w-full text-2xl gap-1 py-4 justify-evenly items-center border-4 border-[#<%= @admin.hex_color %>] rounded-lg">
|
||||
<% @admin.services.each do |service| %>
|
||||
<div class="flex w-[21%] justify-center items-center">
|
||||
<a href=<%= service.current_url %> class="flex justify-center items-center h-15 w-55 px-2 py-10 my-4 text-center font-bold text-[#<%= @admin.hex_color %>] bg-transparent border-2 border-[#<%= @admin.hex_color %>] rounded-lg hover:text-[#140029] hover:bg-[#<%= @admin.hex_color %>] transition-colors duration-200">
|
||||
<%= service.name %>
|
||||
</a>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Network Directory</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
</head>
|
||||
<body class="bg-[#140029] text-[#8000FF] font-semibold antialiased">
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<%= yield %>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,3 @@
|
||||
<div class="small-12 columns">
|
||||
<h2>404 Not Found</h2>
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
require_relative 'config/environment'
|
||||
|
||||
run ApplicationController
|
||||
@@ -0,0 +1,15 @@
|
||||
development:
|
||||
adapter: mysql2
|
||||
encoding: utf8mb4
|
||||
database: dashboard_development
|
||||
username: root
|
||||
password: mypassword
|
||||
host: db
|
||||
port: 3306
|
||||
|
||||
production:
|
||||
adapter: mysql2
|
||||
encoding: utf8
|
||||
username: root
|
||||
password:
|
||||
database: sinatra_template_production
|
||||
@@ -0,0 +1,13 @@
|
||||
require 'bundler/setup'
|
||||
Bundler.require(:default, ENV['RACK_ENV'] || :development)
|
||||
|
||||
# Establish ActiveRecord Connection
|
||||
ActiveRecord::Base.configurations = YAML.load_file('config/database.yml')
|
||||
ActiveRecord::Base.establish_connection(ENV['RACK_ENV']&.to_sym || :development)
|
||||
|
||||
# Load Models
|
||||
Dir.glob(File.join(File.dirname(__FILE__), '../app/models', '*.rb')).each { |file| require file }
|
||||
require_relative '../app/controllers/application_controller'
|
||||
|
||||
# Enable ActiveRecord logging to stdout
|
||||
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
||||
@@ -0,0 +1,11 @@
|
||||
class CreateLocalNetworks < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :local_networks do |t|
|
||||
t.string :name
|
||||
t.string :subnet
|
||||
t.string :tld
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,14 @@
|
||||
class CreateMachines < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :machines do |t|
|
||||
t.string :name
|
||||
t.string :domain
|
||||
t.string :local_ip_octet
|
||||
t.string :tailscale_ip
|
||||
|
||||
t.belongs_to :local_network
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
class CreateServiceTypes < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :service_types do |t|
|
||||
t.string :name
|
||||
t.string :hex_color
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,16 @@
|
||||
class CreateServices < ActiveRecord::Migration[7.2]
|
||||
def change
|
||||
create_table :services do |t|
|
||||
t.string :name
|
||||
t.string :subdomain
|
||||
t.string :port
|
||||
t.string :url_path
|
||||
t.integer :position
|
||||
|
||||
t.belongs_to :machine
|
||||
t.belongs_to :service_type
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,53 @@
|
||||
# This file is auto-generated from the current state of the database. Instead
|
||||
# of editing this file, please use the migrations feature of Active Record to
|
||||
# incrementally modify your database, and then regenerate this schema definition.
|
||||
#
|
||||
# This file is the source Rails uses to define your schema when running `bin/rails
|
||||
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
||||
# be faster and is potentially less error prone than running all of your
|
||||
# migrations from scratch. Old migrations may fail to apply correctly if those
|
||||
# migrations use external dependencies or application code.
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.2].define(version: 2026_05_27_212837) do
|
||||
create_table "local_networks", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.string "subnet"
|
||||
t.string "tld"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
create_table "machines", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.string "domain"
|
||||
t.string "local_ip_octet"
|
||||
t.string "tailscale_ip"
|
||||
t.bigint "local_network_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["local_network_id"], name: "index_machines_on_local_network_id"
|
||||
end
|
||||
|
||||
create_table "service_types", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.string "hex_color"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
end
|
||||
|
||||
create_table "services", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.string "subdomain"
|
||||
t.string "port"
|
||||
t.string "url_path"
|
||||
t.integer "position"
|
||||
t.bigint "machine_id"
|
||||
t.bigint "service_type_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.index ["machine_id"], name: "index_services_on_machine_id"
|
||||
t.index ["service_type_id"], name: "index_services_on_service_type_id"
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,27 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
# command: bundle exec puma -p 4567 -b 0.0.0.0
|
||||
volumes:
|
||||
- .:/app
|
||||
ports:
|
||||
- "4567:4567"
|
||||
environment:
|
||||
- RACK_ENV=development
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
db:
|
||||
image: mysql:8.0
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: mypassword
|
||||
MYSQL_DATABASE: dashboard_development
|
||||
ports:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
- db_data:/var/lib/mysql
|
||||
|
||||
volumes:
|
||||
db_data:
|
||||
@@ -0,0 +1,96 @@
|
||||
helpers do
|
||||
def link_to title, url, &block
|
||||
"<a href='#{url}'>#{title || yield}<a>"
|
||||
end
|
||||
|
||||
def protected!
|
||||
unless authorized?
|
||||
response['WWW-Authenticate'] = %(Basic realm="Restricted Area")
|
||||
throw(:halt, [401, "Not authorized\n"])
|
||||
end
|
||||
end
|
||||
|
||||
def authorized?
|
||||
@auth ||= Rack::Auth::Basic::Request.new(request.env)
|
||||
@auth.provided? && @auth.basic? && @auth.credentials && @auth.credentials == [settings.http_auth_accounts.first[0], settings.http_auth_accounts.first[1]]
|
||||
end
|
||||
|
||||
def in_groups_of(number, fill_with = nil)
|
||||
if fill_with == false
|
||||
collection = self
|
||||
else
|
||||
# size % number gives how many extra we have;
|
||||
# subtracting from number gives how many to add;
|
||||
# modulo number ensures we don't add group of just fill.
|
||||
padding = (number - size % number) % number
|
||||
collection = dup.concat([fill_with] * padding)
|
||||
end
|
||||
|
||||
if block_given?
|
||||
collection.each_slice(number) { |slice| yield(slice) }
|
||||
else
|
||||
returning [] do |groups|
|
||||
collection.each_slice(number) { |group| groups << group }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def options_for_select(container, selected = nil)
|
||||
return container if String === container
|
||||
|
||||
container = container.to_a if Hash === container
|
||||
selected, disabled = extract_selected_and_disabled(selected).map do | r |
|
||||
Array.wrap(r).map { |item| item.to_s }
|
||||
end
|
||||
|
||||
container.map do |element|
|
||||
html_attributes = option_html_attributes(element)
|
||||
text, value = option_text_and_value(element).map { |item| item.to_s }
|
||||
selected_attribute = ' selected="selected"' if option_value_selected?(value, selected)
|
||||
disabled_attribute = ' disabled="disabled"' if disabled && option_value_selected?(value, disabled)
|
||||
%(<option value="#{html_escape(value)}"#{selected_attribute}#{disabled_attribute}#{html_attributes}>#{html_escape(text)}</option>)
|
||||
end.join("\n")
|
||||
|
||||
end
|
||||
|
||||
def extract_selected_and_disabled(selected)
|
||||
if selected.is_a?(Proc)
|
||||
[ selected, nil ]
|
||||
else
|
||||
selected = Array.wrap(selected)
|
||||
options = selected.extract_options!.symbolize_keys
|
||||
[ options.include?(:selected) ? options[:selected] : selected, options[:disabled] ]
|
||||
end
|
||||
end
|
||||
|
||||
def option_html_attributes(element)
|
||||
return "" unless Array === element
|
||||
html_attributes = []
|
||||
element.select { |e| Hash === e }.reduce({}, :merge).each do |k, v|
|
||||
html_attributes << " #{k}=\"#{ERB::Util.html_escape(v.to_s)}\""
|
||||
end
|
||||
html_attributes.join
|
||||
end
|
||||
|
||||
def option_text_and_value(option)
|
||||
# Options are [text, value] pairs or strings used for both.
|
||||
case
|
||||
when Array === option
|
||||
option = option.reject { |e| Hash === e }
|
||||
[option.first, option.last]
|
||||
when !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last)
|
||||
[option.first, option.last]
|
||||
else
|
||||
[option, option]
|
||||
end
|
||||
end
|
||||
|
||||
def option_value_selected?(value, selected)
|
||||
if selected.respond_to?(:include?) && !selected.is_a?(String)
|
||||
selected.include? value
|
||||
else
|
||||
value == selected
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID/TCCAuWgAwIBAgIUdwbdfgvNHyMVmXLZYZpvunx9/AgwDQYJKoZIhvcNAQEL
|
||||
BQAwgY0xCzAJBgNVBAYTAlVTMRcwFQYDVQQIDA5Ob3J0aCBDYXJvbGluYTENMAsG
|
||||
A1UEBwwEQ2FyeTEWMBQGA1UECgwNTGltZUdyZWVuRmlyZTEZMBcGA1UEAwwQY29k
|
||||
ZWh1Yi5xbmFwLmxhbjEjMCEGCSqGSIb3DQEJARYUanRqb3JkYW4xM0BnbWFpbC5j
|
||||
b20wHhcNMjYwNjEwMjA1OTU5WhcNMjcwNjEwMjA1OTU5WjCBjTELMAkGA1UEBhMC
|
||||
VVMxFzAVBgNVBAgMDk5vcnRoIENhcm9saW5hMQ0wCwYDVQQHDARDYXJ5MRYwFAYD
|
||||
VQQKDA1MaW1lR3JlZW5GaXJlMRkwFwYDVQQDDBBjb2RlaHViLnFuYXAubGFuMSMw
|
||||
IQYJKoZIhvcNAQkBFhRqdGpvcmRhbjEzQGdtYWlsLmNvbTCCASIwDQYJKoZIhvcN
|
||||
AQEBBQADggEPADCCAQoCggEBAMsGw5d7BOYXGIUr5ZkConyxdzwQE6S4pITvdO+3
|
||||
oN++xe0V+s/cZmX7vVcp7Me4N9sJ9OSLoyfPIoP8dqbzpDRViMrqKzKwZJ3Let0W
|
||||
wRbyRM/2+HeNaHgay3eQT876ciDyLz4IKHnw9gnW2mkCJh/ntboJQ63+uFKmOJZt
|
||||
O1O8WDJtI+bqAyA+bX5LG8eGJYCjf3exdwsDQ/BBOApSCVxSWpGpaI7+N4jzrsOx
|
||||
47cRaGHjTGXmX00oDewUIV2g3CWPf6r5dN5DFFQ8jOCeZ+GsZKPDXS0Q6jJY6DBp
|
||||
fFKgv8jFufgv9K2oksXMIiuVgRcDzjwwzKRwGIBF/s6F2VECAwEAAaNTMFEwHQYD
|
||||
VR0OBBYEFKlM5ts/wBM/aeT2K2hPbA1KfI6AMB8GA1UdIwQYMBaAFKlM5ts/wBM/
|
||||
aeT2K2hPbA1KfI6AMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
|
||||
ALJiW6yuZ6fnP8vOKp0meND8079GQzU7tgGfsC1v1StQzQ4LMjAuoALKeLHmD4Ml
|
||||
tLXtGFt0u3g6o5GfxU1Wxxn2rSGyeGurQU9vjgiJMKJHIRwSbEJVSGjMqyCTInjs
|
||||
tt/FiKbO8OFJCBvEToc3GNrlW6ncLfAYNOSHprp2n7WJ4E8BxkKCR1ePAZbsds1g
|
||||
ZMpJLnK8XFz9oUx9bx24XduKYDECEPSahq5FuRonSAKDemS8zmEV1PSrTmcen2SC
|
||||
5EokOxRj7MkQpseOtROwr0F4Yq4mirZapAOO7ZWsxMjj2AWf1uF48nbi3/NqTMnN
|
||||
Nx5FxOEatF1zyUeYdKDMz6s=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDLBsOXewTmFxiF
|
||||
K+WZAqJ8sXc8EBOkuKSE73Tvt6DfvsXtFfrP3GZl+71XKezHuDfbCfTki6MnzyKD
|
||||
/Ham86Q0VYjK6isysGSdy3rdFsEW8kTP9vh3jWh4Gst3kE/O+nIg8i8+CCh58PYJ
|
||||
1tppAiYf57W6CUOt/rhSpjiWbTtTvFgybSPm6gMgPm1+SxvHhiWAo393sXcLA0Pw
|
||||
QTgKUglcUlqRqWiO/jeI867DseO3EWhh40xl5l9NKA3sFCFdoNwlj3+q+XTeQxRU
|
||||
PIzgnmfhrGSjw10tEOoyWOgwaXxSoL/Ixbn4L/StqJLFzCIrlYEXA848MMykcBiA
|
||||
Rf7OhdlRAgMBAAECggEAFf4H9Koephp5HV7bjnswtPtn0DWpHRIjvyMtunRcnLQO
|
||||
fl+/xSGppXEzKcSYuaswwL1HvfSK0kp/oYa45x2UC1e7G0jpsEJXidjDeLzI4oaQ
|
||||
ker9r/ydRQpZATz8ii4KrBsz5x8s3EWv7zGrA8436UOZJbtwbYH+vzQyg8f2Edwu
|
||||
+fhHDTiayXIgp38TwYNhzoakERadBLtyCUbypCrUOc+HYJlgbAzxrjNfLzJsM2Ht
|
||||
9zvsLAylpkDiQuuKkPwVrpKWQkdcJnTzYyqRm5ydzlquF0ezEQEHfb5t/KFE7SVm
|
||||
1FHbaY5qoXwcqhkQTohRe0frRQtdxsTmSDyw+MA3SQKBgQD0fwOqX+cGSdqEfENM
|
||||
ZhBgw/244aOzqF3gYeByghcmsgzRUmwO/wuIfcql0xoHnFfJnFsrf0yzylXtMmre
|
||||
xqwTmKcPPzJsuO9Y8JogwKcsh6VhMCWBkgw7P/XiT0BkhsCpLFTV+YZQ7veGDILs
|
||||
lWlHFNAEmOiukiczMReUl1taqQKBgQDUlD3oc02mtX81h2k29SDjTmXq8fdY/Aug
|
||||
A1d8ASuBPMS06qHDzMvemRkgkz76aDAd7M97kCfT8qBF9L/W01pAn2W/VyIFLpGK
|
||||
kVReMJvVkRDPev+ljVa7GTWHSl9EircXtR0EtHy/CG6AzzPkToCACPnuSTFFOVrS
|
||||
EzDOL+SaaQKBgQDuGCasqtniwNcAv7YV1yrJ4PLbMTjmwuYwlYAqYs9Cyo865OYA
|
||||
MJS9papLk+k8Uh8XYaFTGZPLXhYReFCkg5qdNsIxUdy8Ddhfp2ag0Ju7/JirrWRI
|
||||
6r3okR/U9FKD0soZtOckvOr1M9FuBA8Xb2TnaLguUe392qw76OnKtR6siQKBgQCn
|
||||
HOKOGhaxN30JV6oeyhVQnBEC4bTQ/1MkN3xOv5yzvFHm54zDn/ukwjY+pYKc18r7
|
||||
u25gdLLaq6HTXNRyzTPmGWijQpw79p/zjswEP7JB8giFEuxl+PZ1nxu1f4HlICdP
|
||||
O9HUIQ7wHnDAUiM5F31tKaFQ8bkJ8kyzWOLFNGFCAQKBgQDpwiFdZZYfplmxuObp
|
||||
vQVEmP1rIvkHzbS3hDEXRESi3/iok6XDTa5xBrZKTYHqJ+yaRsVhq6b3A3oC+Q5w
|
||||
TMCJ7phrA5VAVHaEcf3QIPUc8UdkpUzDGpholN6+KulTJ7et6iszEv4BL8hTZFLv
|
||||
hTQW4LBHzHk5awQjKMSBM1hlwg==
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -0,0 +1,19 @@
|
||||
development: &default
|
||||
# general
|
||||
default_timezone: "Asia/Taipei"
|
||||
site_root_url: http://localhost:4567
|
||||
per_page: 10
|
||||
# meta tags
|
||||
meta_title: Example Blog
|
||||
meta_description: Sinatra Template is like a tiny Rails
|
||||
meta_keywords: "sinatra ruby rails blog"
|
||||
meta_og_site_name: Example Blog
|
||||
meta_og_url: http://example.com/sub_dir
|
||||
meta_og_image: http://example.com/sub_dir/images/og_image.png
|
||||
|
||||
test:
|
||||
<<: *default
|
||||
|
||||
production:
|
||||
<<: *default
|
||||
site_root_url: http://example.com/sub_dir
|
||||
@@ -0,0 +1,7 @@
|
||||
development: &default
|
||||
http_auth_accounts:
|
||||
admin: right_password
|
||||
test:
|
||||
<<: *default
|
||||
production:
|
||||
<<: *default
|
||||
@@ -0,0 +1,14 @@
|
||||
development:
|
||||
adapter: mysql2
|
||||
encoding: utf8mb4
|
||||
database: dashboard_development
|
||||
username: root
|
||||
password: mypassword
|
||||
host: db
|
||||
|
||||
production:
|
||||
adapter: mysql2
|
||||
encoding: utf8
|
||||
username: root
|
||||
password:
|
||||
database: sinatra_template_production
|
||||
@@ -0,0 +1,26 @@
|
||||
log4r_config:
|
||||
loggers:
|
||||
- name: app_logger
|
||||
trace: "false"
|
||||
outputters:
|
||||
- stdout
|
||||
- logfile
|
||||
outputters:
|
||||
- type: StdoutOutputter
|
||||
name: stdout
|
||||
level: DEBUG
|
||||
formatter:
|
||||
date_pattern: "%Y-%m-%d %H:%M:%S"
|
||||
pattern: "%d [%l] %m"
|
||||
type: PatternFormatter
|
||||
|
||||
- type: FileOutputter
|
||||
name: logfile
|
||||
level: INFO
|
||||
date_pattern: "%Y%m%d"
|
||||
trunc: "false"
|
||||
filename: "#{HOME}/app_logger.log"
|
||||
formatter:
|
||||
date_pattern: "%Y-%m-%d %H:%M:%S"
|
||||
pattern: "%d [%l] %m"
|
||||
type: PatternFormatter
|
||||
@@ -0,0 +1,15 @@
|
||||
if ENV['MY_RUBY_HOME'] && ENV['MY_RUBY_HOME'].include?('rvm')
|
||||
begin
|
||||
rvm_path = File.dirname(File.dirname(ENV['MY_RUBY_HOME']))
|
||||
rvm_lib_path = File.join(rvm_path, 'lib')
|
||||
$LOAD_PATH.unshift rvm_lib_path
|
||||
require 'rvm'
|
||||
RVM.use_from_path! File.dirname(File.dirname(__FILE__))
|
||||
rescue LoadError
|
||||
raise "RVM ruby lib is currently unavailable."
|
||||
end
|
||||
end
|
||||
|
||||
# This assumes Bundler 1.0+
|
||||
ENV['BUNDLE_GEMFILE'] = File.expand_path('../Gemfile', File.dirname(__FILE__))
|
||||
require 'bundler/setup'
|
||||
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 241 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 241 KiB |
@@ -0,0 +1,337 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="3001"
|
||||
height="3001"
|
||||
viewBox="0 0 3001 3001"
|
||||
version="1.1"
|
||||
id="svg52"
|
||||
sodipodi:docname="greenswitch.svg"
|
||||
inkscape:version="1.4.2 (ebf0e940, 2025-05-08)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview52"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="false"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showborder="false"
|
||||
borderlayer="false"
|
||||
inkscape:antialias-rendering="true"
|
||||
inkscape:zoom="0.14315544"
|
||||
inkscape:cx="1896.5399"
|
||||
inkscape:cy="1397.0828"
|
||||
inkscape:window-width="1440"
|
||||
inkscape:window-height="779"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="25"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg52" />
|
||||
<defs
|
||||
id="defs49">
|
||||
<clipPath
|
||||
id="clip-0">
|
||||
<path
|
||||
clip-rule="nonzero"
|
||||
d="M 0.5 0.5 L 3000.5 0.5 L 3000.5 3000.5 L 0.5 3000.5 Z M 0.5 0.5 "
|
||||
id="path1" />
|
||||
</clipPath>
|
||||
<radialGradient
|
||||
id="radial-pattern-0"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
cx="0"
|
||||
cy="0"
|
||||
fx="0"
|
||||
fy="0"
|
||||
r="1500"
|
||||
gradientTransform="matrix(1, 0, 0, 1, 1500.5, 1500.5)">
|
||||
<stop
|
||||
offset="0"
|
||||
stop-color="rgb(99.905396%, 99.90387%, 99.90387%)"
|
||||
stop-opacity="1"
|
||||
id="stop1" />
|
||||
<stop
|
||||
offset="0.015625"
|
||||
stop-color="rgb(99.64447%, 99.638367%, 99.639893%)"
|
||||
stop-opacity="1"
|
||||
id="stop2" />
|
||||
<stop
|
||||
offset="0.0429688"
|
||||
stop-color="rgb(99.311829%, 99.299622%, 99.302673%)"
|
||||
stop-opacity="1"
|
||||
id="stop3" />
|
||||
<stop
|
||||
offset="0.0703125"
|
||||
stop-color="rgb(98.979187%, 98.960876%, 98.965454%)"
|
||||
stop-opacity="1"
|
||||
id="stop4" />
|
||||
<stop
|
||||
offset="0.0976562"
|
||||
stop-color="rgb(98.64502%, 98.620605%, 98.626709%)"
|
||||
stop-opacity="1"
|
||||
id="stop5" />
|
||||
<stop
|
||||
offset="0.125"
|
||||
stop-color="rgb(98.312378%, 98.28186%, 98.28949%)"
|
||||
stop-opacity="1"
|
||||
id="stop6" />
|
||||
<stop
|
||||
offset="0.152344"
|
||||
stop-color="rgb(98.002625%, 97.966003%, 97.975159%)"
|
||||
stop-opacity="1"
|
||||
id="stop7" />
|
||||
<stop
|
||||
offset="0.175781"
|
||||
stop-color="rgb(97.717285%, 97.676086%, 97.686768%)"
|
||||
stop-opacity="1"
|
||||
id="stop8" />
|
||||
<stop
|
||||
offset="0.199219"
|
||||
stop-color="rgb(97.43042%, 97.384644%, 97.395325%)"
|
||||
stop-opacity="1"
|
||||
id="stop9" />
|
||||
<stop
|
||||
offset="0.222656"
|
||||
stop-color="rgb(97.146606%, 97.094727%, 97.106934%)"
|
||||
stop-opacity="1"
|
||||
id="stop10" />
|
||||
<stop
|
||||
offset="0.246094"
|
||||
stop-color="rgb(96.862793%, 96.80481%, 96.820068%)"
|
||||
stop-opacity="1"
|
||||
id="stop11" />
|
||||
<stop
|
||||
offset="0.269531"
|
||||
stop-color="rgb(96.600342%, 96.537781%, 96.554565%)"
|
||||
stop-opacity="1"
|
||||
id="stop12" />
|
||||
<stop
|
||||
offset="0.289062"
|
||||
stop-color="rgb(96.362305%, 96.295166%, 96.311951%)"
|
||||
stop-opacity="1"
|
||||
id="stop13" />
|
||||
<stop
|
||||
offset="0.308594"
|
||||
stop-color="rgb(96.122742%, 96.052551%, 96.069336%)"
|
||||
stop-opacity="1"
|
||||
id="stop14" />
|
||||
<stop
|
||||
offset="0.328125"
|
||||
stop-color="rgb(95.88623%, 95.811462%, 95.831299%)"
|
||||
stop-opacity="1"
|
||||
id="stop15" />
|
||||
<stop
|
||||
offset="0.347656"
|
||||
stop-color="rgb(95.648193%, 95.568848%, 95.588684%)"
|
||||
stop-opacity="1"
|
||||
id="stop16" />
|
||||
<stop
|
||||
offset="0.367188"
|
||||
stop-color="rgb(95.40863%, 95.326233%, 95.346069%)"
|
||||
stop-opacity="1"
|
||||
id="stop17" />
|
||||
<stop
|
||||
offset="0.386719"
|
||||
stop-color="rgb(95.196533%, 95.109558%, 95.13092%)"
|
||||
stop-opacity="1"
|
||||
id="stop18" />
|
||||
<stop
|
||||
offset="0.402344"
|
||||
stop-color="rgb(95.005798%, 94.915771%, 94.93866%)"
|
||||
stop-opacity="1"
|
||||
id="stop19" />
|
||||
<stop
|
||||
offset="0.417969"
|
||||
stop-color="rgb(94.813538%, 94.718933%, 94.743347%)"
|
||||
stop-opacity="1"
|
||||
id="stop20" />
|
||||
<stop
|
||||
offset="0.433594"
|
||||
stop-color="rgb(94.624329%, 94.526672%, 94.551086%)"
|
||||
stop-opacity="1"
|
||||
id="stop21" />
|
||||
<stop
|
||||
offset="0.449219"
|
||||
stop-color="rgb(94.43512%, 94.334412%, 94.358826%)"
|
||||
stop-opacity="1"
|
||||
id="stop22" />
|
||||
<stop
|
||||
offset="0.464844"
|
||||
stop-color="rgb(94.245911%, 94.140625%, 94.166565%)"
|
||||
stop-opacity="1"
|
||||
id="stop23" />
|
||||
<stop
|
||||
offset="0.480469"
|
||||
stop-color="rgb(94.05365%, 93.945312%, 93.971252%)"
|
||||
stop-opacity="1"
|
||||
id="stop24" />
|
||||
<stop
|
||||
offset="0.496094"
|
||||
stop-color="rgb(93.864441%, 93.753052%, 93.780518%)"
|
||||
stop-opacity="1"
|
||||
id="stop25" />
|
||||
<stop
|
||||
offset="0.511719"
|
||||
stop-color="rgb(93.603516%, 93.486023%, 93.515015%)"
|
||||
stop-opacity="1"
|
||||
id="stop26" />
|
||||
<stop
|
||||
offset="0.539062"
|
||||
stop-color="rgb(93.269348%, 93.145752%, 93.17627%)"
|
||||
stop-opacity="1"
|
||||
id="stop27" />
|
||||
<stop
|
||||
offset="0.566406"
|
||||
stop-color="rgb(92.961121%, 92.832947%, 92.86499%)"
|
||||
stop-opacity="1"
|
||||
id="stop28" />
|
||||
<stop
|
||||
offset="0.589844"
|
||||
stop-color="rgb(92.674255%, 92.539978%, 92.573547%)"
|
||||
stop-opacity="1"
|
||||
id="stop29" />
|
||||
<stop
|
||||
offset="0.613281"
|
||||
stop-color="rgb(92.388916%, 92.250061%, 92.285156%)"
|
||||
stop-opacity="1"
|
||||
id="stop30" />
|
||||
<stop
|
||||
offset="0.636719"
|
||||
stop-color="rgb(92.127991%, 91.984558%, 92.019653%)"
|
||||
stop-opacity="1"
|
||||
id="stop31" />
|
||||
<stop
|
||||
offset="0.65625"
|
||||
stop-color="rgb(91.888428%, 91.741943%, 91.778564%)"
|
||||
stop-opacity="1"
|
||||
id="stop32" />
|
||||
<stop
|
||||
offset="0.675781"
|
||||
stop-color="rgb(91.651917%, 91.500854%, 91.537476%)"
|
||||
stop-opacity="1"
|
||||
id="stop33" />
|
||||
<stop
|
||||
offset="0.695312"
|
||||
stop-color="rgb(91.436768%, 91.281128%, 91.319275%)"
|
||||
stop-opacity="1"
|
||||
id="stop34" />
|
||||
<stop
|
||||
offset="0.710938"
|
||||
stop-color="rgb(91.246033%, 91.087341%, 91.127014%)"
|
||||
stop-opacity="1"
|
||||
id="stop35" />
|
||||
<stop
|
||||
offset="0.726562"
|
||||
stop-color="rgb(91.056824%, 90.895081%, 90.934753%)"
|
||||
stop-opacity="1"
|
||||
id="stop36" />
|
||||
<stop
|
||||
offset="0.742188"
|
||||
stop-color="rgb(90.867615%, 90.701294%, 90.742493%)"
|
||||
stop-opacity="1"
|
||||
id="stop37" />
|
||||
<stop
|
||||
offset="0.757812"
|
||||
stop-color="rgb(90.603638%, 90.432739%, 90.475464%)"
|
||||
stop-opacity="1"
|
||||
id="stop38" />
|
||||
<stop
|
||||
offset="0.785156"
|
||||
stop-color="rgb(90.296936%, 90.119934%, 90.164185%)"
|
||||
stop-opacity="1"
|
||||
id="stop39" />
|
||||
<stop
|
||||
offset="0.808594"
|
||||
stop-color="rgb(90.008545%, 89.826965%, 89.872742%)"
|
||||
stop-opacity="1"
|
||||
id="stop40" />
|
||||
<stop
|
||||
offset="0.832031"
|
||||
stop-color="rgb(89.749146%, 89.561462%, 89.608765%)"
|
||||
stop-opacity="1"
|
||||
id="stop41" />
|
||||
<stop
|
||||
offset="0.851562"
|
||||
stop-color="rgb(89.535522%, 89.344788%, 89.39209%)"
|
||||
stop-opacity="1"
|
||||
id="stop42" />
|
||||
<stop
|
||||
offset="0.867188"
|
||||
stop-color="rgb(89.343262%, 89.149475%, 89.196777%)"
|
||||
stop-opacity="1"
|
||||
id="stop43" />
|
||||
<stop
|
||||
offset="0.882812"
|
||||
stop-color="rgb(89.083862%, 88.885498%, 88.934326%)"
|
||||
stop-opacity="1"
|
||||
id="stop44" />
|
||||
<stop
|
||||
offset="0.910156"
|
||||
stop-color="rgb(88.798523%, 88.594055%, 88.644409%)"
|
||||
stop-opacity="1"
|
||||
id="stop45" />
|
||||
<stop
|
||||
offset="0.929688"
|
||||
stop-color="rgb(88.581848%, 88.374329%, 88.426208%)"
|
||||
stop-opacity="1"
|
||||
id="stop46" />
|
||||
<stop
|
||||
offset="0.945312"
|
||||
stop-color="rgb(88.320923%, 88.108826%, 88.162231%)"
|
||||
stop-opacity="1"
|
||||
id="stop47" />
|
||||
<stop
|
||||
offset="0.972656"
|
||||
stop-color="rgb(87.986755%, 87.768555%, 87.823486%)"
|
||||
stop-opacity="1"
|
||||
id="stop48" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="rgb(87.820435%, 87.599182%, 87.654114%)"
|
||||
stop-opacity="1"
|
||||
id="stop49" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<g
|
||||
clip-path="url(#clip-0)"
|
||||
id="g49"
|
||||
style="fill:#000000;fill-opacity:0">
|
||||
<path
|
||||
fill-rule="nonzero"
|
||||
fill="url(#radial-pattern-0)"
|
||||
d="M 0.5 0.5 L 0.5 3000.5 L 3000.5 3000.5 L 3000.5 0.5 Z M 0.5 0.5 "
|
||||
id="path49"
|
||||
style="fill:#000000;fill-opacity:0" />
|
||||
</g>
|
||||
<path
|
||||
fill="none"
|
||||
stroke-width="1"
|
||||
stroke-linecap="butt"
|
||||
stroke-linejoin="miter"
|
||||
stroke="#231f20"
|
||||
stroke-opacity="1"
|
||||
stroke-miterlimit="10"
|
||||
d="M 3000.5,0.5 H 0.5 v 3000 h 3000 z m 0,0"
|
||||
id="path50"
|
||||
inkscape:label="path50"
|
||||
style="display:inline;opacity:1" />
|
||||
<path
|
||||
fill-rule="nonzero"
|
||||
fill="rgb(12.980652%, 11.320496%, 11.306763%)"
|
||||
fill-opacity="1"
|
||||
d="M 1505.5 536.078125 C 1471.878906 536.078125 1444.519531 563.441406 1444.519531 597.058594 C 1444.519531 630.679688 1471.878906 658.039062 1505.5 658.039062 C 1539.121094 658.039062 1566.480469 630.679688 1566.480469 597.058594 C 1566.480469 563.441406 1539.121094 536.078125 1505.5 536.078125 Z M 1505.5 719.011719 C 1438.25 719.011719 1383.550781 664.308594 1383.550781 597.058594 C 1383.550781 529.808594 1438.25 475.109375 1505.5 475.109375 C 1572.75 475.109375 1627.449219 529.808594 1627.449219 597.058594 C 1627.449219 664.308594 1572.75 719.011719 1505.5 719.011719 Z M 1212.820312 920.230469 C 1196.011719 920.230469 1182.328125 933.910156 1182.328125 950.71875 L 1182.328125 2048.28125 C 1182.328125 2065.089844 1196.011719 2078.769531 1212.820312 2078.769531 L 1798.179688 2078.769531 C 1814.988281 2078.769531 1828.671875 2065.089844 1828.671875 2048.28125 L 1828.671875 950.71875 C 1828.671875 933.910156 1814.988281 920.230469 1798.179688 920.230469 Z M 1798.179688 2139.738281 L 1212.820312 2139.738281 C 1162.378906 2139.738281 1121.351562 2098.71875 1121.351562 2048.28125 L 1121.351562 950.71875 C 1121.351562 900.28125 1162.378906 859.261719 1212.820312 859.261719 L 1798.179688 859.261719 C 1848.621094 859.261719 1889.648438 900.28125 1889.648438 950.71875 L 1889.648438 2048.28125 C 1889.648438 2098.71875 1848.621094 2139.738281 1798.179688 2139.738281 Z M 1505.5 2340.960938 C 1471.878906 2340.960938 1444.519531 2368.320312 1444.519531 2401.941406 C 1444.519531 2435.558594 1471.878906 2462.921875 1505.5 2462.921875 C 1539.121094 2462.921875 1566.480469 2435.558594 1566.480469 2401.941406 C 1566.480469 2368.320312 1539.121094 2340.960938 1505.5 2340.960938 Z M 1505.5 2523.890625 C 1438.25 2523.890625 1383.550781 2469.179688 1383.550781 2401.941406 C 1383.550781 2334.691406 1438.25 2279.988281 1505.5 2279.988281 C 1572.75 2279.988281 1627.449219 2334.691406 1627.449219 2401.941406 C 1627.449219 2469.179688 1572.75 2523.890625 1505.5 2523.890625 Z M 2243.300781 2749.5 L 767.695312 2749.5 C 683.640625 2749.5 615.257812 2681.121094 615.257812 2597.058594 L 615.257812 1444.621094 C 615.257812 1427.78125 628.902344 1414.128906 645.742188 1414.128906 C 662.582031 1414.128906 676.230469 1427.78125 676.230469 1444.621094 L 676.230469 2597.058594 C 676.230469 2647.5 717.257812 2688.519531 767.695312 2688.519531 L 2243.300781 2688.519531 C 2293.738281 2688.519531 2334.769531 2647.5 2334.769531 2597.058594 L 2334.769531 401.941406 C 2334.769531 351.5 2293.738281 310.480469 2243.300781 310.480469 L 767.695312 310.480469 C 717.257812 310.480469 676.230469 351.5 676.230469 401.941406 L 676.230469 1261.699219 C 676.230469 1278.53125 662.582031 1292.179688 645.742188 1292.179688 C 628.902344 1292.179688 615.257812 1278.53125 615.257812 1261.699219 L 615.257812 401.941406 C 615.257812 317.878906 683.640625 249.5 767.695312 249.5 L 2243.300781 249.5 C 2327.359375 249.5 2395.738281 317.878906 2395.738281 401.941406 L 2395.738281 2597.058594 C 2395.738281 2681.121094 2327.359375 2749.5 2243.300781 2749.5 "
|
||||
id="path51"
|
||||
style="fill:#32cd32;fill-opacity:1" />
|
||||
<path
|
||||
fill-rule="nonzero"
|
||||
fill="rgb(12.980652%, 11.320496%, 11.306763%)"
|
||||
fill-opacity="1"
|
||||
d="m 1774.6846,1488.5956 h -524.3984 c -16.832,0 -30.4805,13.6523 -30.4805,30.4922 v 518.2891 h 585.3594 v -518.2891 c 0,-16.8399 -13.6484,-30.4922 -30.4805,-30.4922 z m -30.4883,60.9805 v 426.8203 h -463.4218 v -426.8203 h 463.4218"
|
||||
id="path52"
|
||||
style="fill:#32cd32;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,338 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="3001"
|
||||
height="3001"
|
||||
viewBox="0 0 3001 3001"
|
||||
version="1.1"
|
||||
id="svg52"
|
||||
sodipodi:docname="purpleswitch.svg"
|
||||
inkscape:version="1.4.2 (ebf0e940, 2025-05-08)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview52"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="false"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
showborder="false"
|
||||
borderlayer="false"
|
||||
inkscape:antialias-rendering="true"
|
||||
inkscape:zoom="0.14315544"
|
||||
inkscape:cx="1896.5399"
|
||||
inkscape:cy="1397.0828"
|
||||
inkscape:window-width="1440"
|
||||
inkscape:window-height="779"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="25"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg52" />
|
||||
<defs
|
||||
id="defs49">
|
||||
<clipPath
|
||||
id="clip-0">
|
||||
<path
|
||||
clip-rule="nonzero"
|
||||
d="M 0.5 0.5 L 3000.5 0.5 L 3000.5 3000.5 L 0.5 3000.5 Z M 0.5 0.5 "
|
||||
id="path1" />
|
||||
</clipPath>
|
||||
<radialGradient
|
||||
id="radial-pattern-0"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
cx="0"
|
||||
cy="0"
|
||||
fx="0"
|
||||
fy="0"
|
||||
r="1500"
|
||||
gradientTransform="matrix(1, 0, 0, 1, 1500.5, 1500.5)">
|
||||
<stop
|
||||
offset="0"
|
||||
stop-color="rgb(99.905396%, 99.90387%, 99.90387%)"
|
||||
stop-opacity="1"
|
||||
id="stop1" />
|
||||
<stop
|
||||
offset="0.015625"
|
||||
stop-color="rgb(99.64447%, 99.638367%, 99.639893%)"
|
||||
stop-opacity="1"
|
||||
id="stop2" />
|
||||
<stop
|
||||
offset="0.0429688"
|
||||
stop-color="rgb(99.311829%, 99.299622%, 99.302673%)"
|
||||
stop-opacity="1"
|
||||
id="stop3" />
|
||||
<stop
|
||||
offset="0.0703125"
|
||||
stop-color="rgb(98.979187%, 98.960876%, 98.965454%)"
|
||||
stop-opacity="1"
|
||||
id="stop4" />
|
||||
<stop
|
||||
offset="0.0976562"
|
||||
stop-color="rgb(98.64502%, 98.620605%, 98.626709%)"
|
||||
stop-opacity="1"
|
||||
id="stop5" />
|
||||
<stop
|
||||
offset="0.125"
|
||||
stop-color="rgb(98.312378%, 98.28186%, 98.28949%)"
|
||||
stop-opacity="1"
|
||||
id="stop6" />
|
||||
<stop
|
||||
offset="0.152344"
|
||||
stop-color="rgb(98.002625%, 97.966003%, 97.975159%)"
|
||||
stop-opacity="1"
|
||||
id="stop7" />
|
||||
<stop
|
||||
offset="0.175781"
|
||||
stop-color="rgb(97.717285%, 97.676086%, 97.686768%)"
|
||||
stop-opacity="1"
|
||||
id="stop8" />
|
||||
<stop
|
||||
offset="0.199219"
|
||||
stop-color="rgb(97.43042%, 97.384644%, 97.395325%)"
|
||||
stop-opacity="1"
|
||||
id="stop9" />
|
||||
<stop
|
||||
offset="0.222656"
|
||||
stop-color="rgb(97.146606%, 97.094727%, 97.106934%)"
|
||||
stop-opacity="1"
|
||||
id="stop10" />
|
||||
<stop
|
||||
offset="0.246094"
|
||||
stop-color="rgb(96.862793%, 96.80481%, 96.820068%)"
|
||||
stop-opacity="1"
|
||||
id="stop11" />
|
||||
<stop
|
||||
offset="0.269531"
|
||||
stop-color="rgb(96.600342%, 96.537781%, 96.554565%)"
|
||||
stop-opacity="1"
|
||||
id="stop12" />
|
||||
<stop
|
||||
offset="0.289062"
|
||||
stop-color="rgb(96.362305%, 96.295166%, 96.311951%)"
|
||||
stop-opacity="1"
|
||||
id="stop13" />
|
||||
<stop
|
||||
offset="0.308594"
|
||||
stop-color="rgb(96.122742%, 96.052551%, 96.069336%)"
|
||||
stop-opacity="1"
|
||||
id="stop14" />
|
||||
<stop
|
||||
offset="0.328125"
|
||||
stop-color="rgb(95.88623%, 95.811462%, 95.831299%)"
|
||||
stop-opacity="1"
|
||||
id="stop15" />
|
||||
<stop
|
||||
offset="0.347656"
|
||||
stop-color="rgb(95.648193%, 95.568848%, 95.588684%)"
|
||||
stop-opacity="1"
|
||||
id="stop16" />
|
||||
<stop
|
||||
offset="0.367188"
|
||||
stop-color="rgb(95.40863%, 95.326233%, 95.346069%)"
|
||||
stop-opacity="1"
|
||||
id="stop17" />
|
||||
<stop
|
||||
offset="0.386719"
|
||||
stop-color="rgb(95.196533%, 95.109558%, 95.13092%)"
|
||||
stop-opacity="1"
|
||||
id="stop18" />
|
||||
<stop
|
||||
offset="0.402344"
|
||||
stop-color="rgb(95.005798%, 94.915771%, 94.93866%)"
|
||||
stop-opacity="1"
|
||||
id="stop19" />
|
||||
<stop
|
||||
offset="0.417969"
|
||||
stop-color="rgb(94.813538%, 94.718933%, 94.743347%)"
|
||||
stop-opacity="1"
|
||||
id="stop20" />
|
||||
<stop
|
||||
offset="0.433594"
|
||||
stop-color="rgb(94.624329%, 94.526672%, 94.551086%)"
|
||||
stop-opacity="1"
|
||||
id="stop21" />
|
||||
<stop
|
||||
offset="0.449219"
|
||||
stop-color="rgb(94.43512%, 94.334412%, 94.358826%)"
|
||||
stop-opacity="1"
|
||||
id="stop22" />
|
||||
<stop
|
||||
offset="0.464844"
|
||||
stop-color="rgb(94.245911%, 94.140625%, 94.166565%)"
|
||||
stop-opacity="1"
|
||||
id="stop23" />
|
||||
<stop
|
||||
offset="0.480469"
|
||||
stop-color="rgb(94.05365%, 93.945312%, 93.971252%)"
|
||||
stop-opacity="1"
|
||||
id="stop24" />
|
||||
<stop
|
||||
offset="0.496094"
|
||||
stop-color="rgb(93.864441%, 93.753052%, 93.780518%)"
|
||||
stop-opacity="1"
|
||||
id="stop25" />
|
||||
<stop
|
||||
offset="0.511719"
|
||||
stop-color="rgb(93.603516%, 93.486023%, 93.515015%)"
|
||||
stop-opacity="1"
|
||||
id="stop26" />
|
||||
<stop
|
||||
offset="0.539062"
|
||||
stop-color="rgb(93.269348%, 93.145752%, 93.17627%)"
|
||||
stop-opacity="1"
|
||||
id="stop27" />
|
||||
<stop
|
||||
offset="0.566406"
|
||||
stop-color="rgb(92.961121%, 92.832947%, 92.86499%)"
|
||||
stop-opacity="1"
|
||||
id="stop28" />
|
||||
<stop
|
||||
offset="0.589844"
|
||||
stop-color="rgb(92.674255%, 92.539978%, 92.573547%)"
|
||||
stop-opacity="1"
|
||||
id="stop29" />
|
||||
<stop
|
||||
offset="0.613281"
|
||||
stop-color="rgb(92.388916%, 92.250061%, 92.285156%)"
|
||||
stop-opacity="1"
|
||||
id="stop30" />
|
||||
<stop
|
||||
offset="0.636719"
|
||||
stop-color="rgb(92.127991%, 91.984558%, 92.019653%)"
|
||||
stop-opacity="1"
|
||||
id="stop31" />
|
||||
<stop
|
||||
offset="0.65625"
|
||||
stop-color="rgb(91.888428%, 91.741943%, 91.778564%)"
|
||||
stop-opacity="1"
|
||||
id="stop32" />
|
||||
<stop
|
||||
offset="0.675781"
|
||||
stop-color="rgb(91.651917%, 91.500854%, 91.537476%)"
|
||||
stop-opacity="1"
|
||||
id="stop33" />
|
||||
<stop
|
||||
offset="0.695312"
|
||||
stop-color="rgb(91.436768%, 91.281128%, 91.319275%)"
|
||||
stop-opacity="1"
|
||||
id="stop34" />
|
||||
<stop
|
||||
offset="0.710938"
|
||||
stop-color="rgb(91.246033%, 91.087341%, 91.127014%)"
|
||||
stop-opacity="1"
|
||||
id="stop35" />
|
||||
<stop
|
||||
offset="0.726562"
|
||||
stop-color="rgb(91.056824%, 90.895081%, 90.934753%)"
|
||||
stop-opacity="1"
|
||||
id="stop36" />
|
||||
<stop
|
||||
offset="0.742188"
|
||||
stop-color="rgb(90.867615%, 90.701294%, 90.742493%)"
|
||||
stop-opacity="1"
|
||||
id="stop37" />
|
||||
<stop
|
||||
offset="0.757812"
|
||||
stop-color="rgb(90.603638%, 90.432739%, 90.475464%)"
|
||||
stop-opacity="1"
|
||||
id="stop38" />
|
||||
<stop
|
||||
offset="0.785156"
|
||||
stop-color="rgb(90.296936%, 90.119934%, 90.164185%)"
|
||||
stop-opacity="1"
|
||||
id="stop39" />
|
||||
<stop
|
||||
offset="0.808594"
|
||||
stop-color="rgb(90.008545%, 89.826965%, 89.872742%)"
|
||||
stop-opacity="1"
|
||||
id="stop40" />
|
||||
<stop
|
||||
offset="0.832031"
|
||||
stop-color="rgb(89.749146%, 89.561462%, 89.608765%)"
|
||||
stop-opacity="1"
|
||||
id="stop41" />
|
||||
<stop
|
||||
offset="0.851562"
|
||||
stop-color="rgb(89.535522%, 89.344788%, 89.39209%)"
|
||||
stop-opacity="1"
|
||||
id="stop42" />
|
||||
<stop
|
||||
offset="0.867188"
|
||||
stop-color="rgb(89.343262%, 89.149475%, 89.196777%)"
|
||||
stop-opacity="1"
|
||||
id="stop43" />
|
||||
<stop
|
||||
offset="0.882812"
|
||||
stop-color="rgb(89.083862%, 88.885498%, 88.934326%)"
|
||||
stop-opacity="1"
|
||||
id="stop44" />
|
||||
<stop
|
||||
offset="0.910156"
|
||||
stop-color="rgb(88.798523%, 88.594055%, 88.644409%)"
|
||||
stop-opacity="1"
|
||||
id="stop45" />
|
||||
<stop
|
||||
offset="0.929688"
|
||||
stop-color="rgb(88.581848%, 88.374329%, 88.426208%)"
|
||||
stop-opacity="1"
|
||||
id="stop46" />
|
||||
<stop
|
||||
offset="0.945312"
|
||||
stop-color="rgb(88.320923%, 88.108826%, 88.162231%)"
|
||||
stop-opacity="1"
|
||||
id="stop47" />
|
||||
<stop
|
||||
offset="0.972656"
|
||||
stop-color="rgb(87.986755%, 87.768555%, 87.823486%)"
|
||||
stop-opacity="1"
|
||||
id="stop48" />
|
||||
<stop
|
||||
offset="1"
|
||||
stop-color="rgb(87.820435%, 87.599182%, 87.654114%)"
|
||||
stop-opacity="1"
|
||||
id="stop49" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<g
|
||||
clip-path="url(#clip-0)"
|
||||
id="g49"
|
||||
style="fill:#000000;fill-opacity:0">
|
||||
<path
|
||||
fill-rule="nonzero"
|
||||
fill="url(#radial-pattern-0)"
|
||||
d="M 0.5 0.5 L 0.5 3000.5 L 3000.5 3000.5 L 3000.5 0.5 Z M 0.5 0.5 "
|
||||
id="path49"
|
||||
style="fill:#000000;fill-opacity:0" />
|
||||
</g>
|
||||
<path
|
||||
fill="none"
|
||||
stroke-width="10"
|
||||
stroke-linecap="butt"
|
||||
stroke-linejoin="miter"
|
||||
stroke="#231f20"
|
||||
stroke-opacity="1"
|
||||
stroke-miterlimit="10"
|
||||
d="M 30005,5 H 5 v 30000 h 30000 z m 0,0"
|
||||
transform="matrix(0.1,0,0,-0.1,0,3001)"
|
||||
id="path50"
|
||||
inkscape:label="path50"
|
||||
style="display:inline;opacity:1" />
|
||||
<path
|
||||
fill-rule="nonzero"
|
||||
fill="rgb(12.980652%, 11.320496%, 11.306763%)"
|
||||
fill-opacity="1"
|
||||
d="M 1505.5 536.078125 C 1471.878906 536.078125 1444.519531 563.441406 1444.519531 597.058594 C 1444.519531 630.679688 1471.878906 658.039062 1505.5 658.039062 C 1539.121094 658.039062 1566.480469 630.679688 1566.480469 597.058594 C 1566.480469 563.441406 1539.121094 536.078125 1505.5 536.078125 Z M 1505.5 719.011719 C 1438.25 719.011719 1383.550781 664.308594 1383.550781 597.058594 C 1383.550781 529.808594 1438.25 475.109375 1505.5 475.109375 C 1572.75 475.109375 1627.449219 529.808594 1627.449219 597.058594 C 1627.449219 664.308594 1572.75 719.011719 1505.5 719.011719 Z M 1212.820312 920.230469 C 1196.011719 920.230469 1182.328125 933.910156 1182.328125 950.71875 L 1182.328125 2048.28125 C 1182.328125 2065.089844 1196.011719 2078.769531 1212.820312 2078.769531 L 1798.179688 2078.769531 C 1814.988281 2078.769531 1828.671875 2065.089844 1828.671875 2048.28125 L 1828.671875 950.71875 C 1828.671875 933.910156 1814.988281 920.230469 1798.179688 920.230469 Z M 1798.179688 2139.738281 L 1212.820312 2139.738281 C 1162.378906 2139.738281 1121.351562 2098.71875 1121.351562 2048.28125 L 1121.351562 950.71875 C 1121.351562 900.28125 1162.378906 859.261719 1212.820312 859.261719 L 1798.179688 859.261719 C 1848.621094 859.261719 1889.648438 900.28125 1889.648438 950.71875 L 1889.648438 2048.28125 C 1889.648438 2098.71875 1848.621094 2139.738281 1798.179688 2139.738281 Z M 1505.5 2340.960938 C 1471.878906 2340.960938 1444.519531 2368.320312 1444.519531 2401.941406 C 1444.519531 2435.558594 1471.878906 2462.921875 1505.5 2462.921875 C 1539.121094 2462.921875 1566.480469 2435.558594 1566.480469 2401.941406 C 1566.480469 2368.320312 1539.121094 2340.960938 1505.5 2340.960938 Z M 1505.5 2523.890625 C 1438.25 2523.890625 1383.550781 2469.179688 1383.550781 2401.941406 C 1383.550781 2334.691406 1438.25 2279.988281 1505.5 2279.988281 C 1572.75 2279.988281 1627.449219 2334.691406 1627.449219 2401.941406 C 1627.449219 2469.179688 1572.75 2523.890625 1505.5 2523.890625 Z M 2243.300781 2749.5 L 767.695312 2749.5 C 683.640625 2749.5 615.257812 2681.121094 615.257812 2597.058594 L 615.257812 1444.621094 C 615.257812 1427.78125 628.902344 1414.128906 645.742188 1414.128906 C 662.582031 1414.128906 676.230469 1427.78125 676.230469 1444.621094 L 676.230469 2597.058594 C 676.230469 2647.5 717.257812 2688.519531 767.695312 2688.519531 L 2243.300781 2688.519531 C 2293.738281 2688.519531 2334.769531 2647.5 2334.769531 2597.058594 L 2334.769531 401.941406 C 2334.769531 351.5 2293.738281 310.480469 2243.300781 310.480469 L 767.695312 310.480469 C 717.257812 310.480469 676.230469 351.5 676.230469 401.941406 L 676.230469 1261.699219 C 676.230469 1278.53125 662.582031 1292.179688 645.742188 1292.179688 C 628.902344 1292.179688 615.257812 1278.53125 615.257812 1261.699219 L 615.257812 401.941406 C 615.257812 317.878906 683.640625 249.5 767.695312 249.5 L 2243.300781 249.5 C 2327.359375 249.5 2395.738281 317.878906 2395.738281 401.941406 L 2395.738281 2597.058594 C 2395.738281 2681.121094 2327.359375 2749.5 2243.300781 2749.5 "
|
||||
id="path51"
|
||||
style="fill:#967bb6;fill-opacity:1" />
|
||||
<path
|
||||
fill-rule="nonzero"
|
||||
fill="rgb(12.980652%, 11.320496%, 11.306763%)"
|
||||
fill-opacity="1"
|
||||
d="M 1767.699219 950.71875 L 1243.300781 950.71875 C 1226.46875 950.71875 1212.820312 964.371094 1212.820312 981.210938 L 1212.820312 1499.5 L 1798.179688 1499.5 L 1798.179688 981.210938 C 1798.179688 964.371094 1784.53125 950.71875 1767.699219 950.71875 Z M 1737.210938 1011.699219 L 1737.210938 1438.519531 L 1273.789062 1438.519531 L 1273.789062 1011.699219 L 1737.210938 1011.699219 "
|
||||
id="path52"
|
||||
style="fill:#967bb6;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,70 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Limegreenfire</title>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
|
||||
<script src="scripts.js" defer></script>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Tilt Neon">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="icon" href="./images/favicon.ico" type="image/x-icon">
|
||||
</head>
|
||||
<body>
|
||||
<div class="dashboard">
|
||||
<div class="dashboard-header">
|
||||
<div class="dashboard-title">LimegreenFire<span class="header-purple">Casa</span></div>
|
||||
<div id="currentDashboard" class="current-dashboard neonText">
|
||||
Local Network
|
||||
</div>
|
||||
</div>
|
||||
<div class="dashboard-field">
|
||||
<div class="dashboard-item">
|
||||
<a id="nginx" href="" class="button qnap">
|
||||
Nginx
|
||||
</a>
|
||||
</div>
|
||||
<div class="dashboard-item">
|
||||
<a id="qnas" href="" class="button qnap">
|
||||
Qnas
|
||||
</a>
|
||||
</div>
|
||||
<div class="dashboard-item">
|
||||
<a id="dockerRegistry" href="" class="button qnap">
|
||||
Docker Image Registry
|
||||
</a>
|
||||
</div>
|
||||
<div class="dashboard-item">
|
||||
<a id="jellyfin" href="" class="button qnap">
|
||||
Jellyfin
|
||||
</a>
|
||||
</div>
|
||||
<div class="dashboard-item">
|
||||
<a id="qtorrent" href="" class="button pc">
|
||||
qTorrent
|
||||
</a>
|
||||
</div>
|
||||
<div class="dashboard-item">
|
||||
<a id="radarr" href="" class="button pc">
|
||||
Radarr
|
||||
</a>
|
||||
</div>
|
||||
<div class="dashboard-item">
|
||||
<a id="sonarr" href="" class="button pc">
|
||||
Sonarr
|
||||
</a>
|
||||
</div>
|
||||
<div class="dashboard-item">
|
||||
<a id="lidarr" href="" class="button pc">
|
||||
Lidarr
|
||||
</a>
|
||||
</div>
|
||||
<div class="dashboard-item">
|
||||
<a id="gitRegistry" href="" class="button qnap">
|
||||
Git Registry
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,150 @@
|
||||
function getAppIP( network, device, app ) {
|
||||
let deviceIP;
|
||||
let port;
|
||||
|
||||
console.log(network, " - ", device, " - ", app)
|
||||
|
||||
if (network == 'tailscale') {
|
||||
deviceIP = (device == 'qnap') ? '100.97.249.88' : '100.115.109.63';
|
||||
} else {
|
||||
deviceIP = (device == 'qnap') ? '192.168.1.81' : '192.168.1.13';
|
||||
}
|
||||
|
||||
switch (app) {
|
||||
case 'nginx':
|
||||
port = ":81";
|
||||
break;
|
||||
case 'qnas':
|
||||
port = ":8080";
|
||||
break;
|
||||
case 'dockerRegistry':
|
||||
port = ":5000";
|
||||
break;
|
||||
case 'jellyfin':
|
||||
port = ":8096";
|
||||
break;
|
||||
case 'qtorrent':
|
||||
port = ":8181";
|
||||
break;
|
||||
case 'radarr':
|
||||
port = ":7878";
|
||||
break;
|
||||
default:
|
||||
port = ""
|
||||
// case sonarr:
|
||||
// port = ":"
|
||||
// break;
|
||||
// case lidarr:
|
||||
// port = ":"
|
||||
// break;
|
||||
// case gitRegistry:
|
||||
// port = ":"
|
||||
// break;
|
||||
};
|
||||
|
||||
return "http://" + deviceIP + port;
|
||||
}
|
||||
|
||||
function propigateIps( network ) {
|
||||
console.log('prop started: ', network)
|
||||
const ipHasPortRegex = /^http:\/\/(\d{1,3}\.){3}\d{1,3}:\d+$/;
|
||||
['qnap', 'pc'].forEach(function(deviceClass) {
|
||||
$("." + deviceClass).each(function() {
|
||||
let appName = $(this).attr("id");
|
||||
let ipAddress = getAppIP(network, deviceClass, appName);
|
||||
if (ipHasPortRegex.test(ipAddress)) {
|
||||
$(this).attr('href', ipAddress);
|
||||
} else {
|
||||
$(this).removeAttr('href');
|
||||
$(this).removeClass('button');
|
||||
$(this).addClass('button-disabled');
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function determineNetwork() {
|
||||
const apiUrlTailscale = 'http://100.97.249.88:3030/';
|
||||
try {
|
||||
fetch(apiUrlTailscale);
|
||||
console.log('determined: tailscale')
|
||||
return 'tailscale';
|
||||
} catch {
|
||||
console.log('determined: local')
|
||||
return'local';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function fillIpsBasedOnNetwork() {
|
||||
const apiUrlTailscale = 'http://100.97.249.88:3030/';
|
||||
let network;
|
||||
try {
|
||||
await fetch(apiUrlTailscale);
|
||||
console.log('determined: tailscale')
|
||||
network = 'tailscale'
|
||||
setHeader (network)
|
||||
propigateIps(network);
|
||||
} catch {
|
||||
console.log('determined: local')
|
||||
network = 'local'
|
||||
setHeader (network)
|
||||
propigateIps(network);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function setHeader( network ) {
|
||||
const networkName = network.charAt(0).toUpperCase() + network.slice(1) + ' Network';
|
||||
$("#currentDashboard").text(networkName);
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
fillIpsBasedOnNetwork();
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* document.addEventListener('DOMContentLoaded', function() {
|
||||
const changeDashboardButton = document.getElementById('changeDashboardButton');
|
||||
|
||||
changeDashboardButton.addEventListener('click', function() {
|
||||
console.log("click")
|
||||
$("#currentDashboard")
|
||||
const currentDashboard = $("#currentDashboard")
|
||||
|
||||
let currentDashboardClasses = currentDashboard.attr('class');
|
||||
let newNetwork = currentDashboardClasses.includes('tailscale') ? 'local' : 'tailscale';
|
||||
|
||||
['qnap', 'pc'].forEach(function(deviceClass) {
|
||||
$("." + deviceClass).each(function() {
|
||||
let appName = $(this).attr("id");
|
||||
let ipAddress = getIP(newNetwork, deviceClass, appName);
|
||||
if (ipRegex.test(ipAddress)) {
|
||||
$(this).attr('href', ipAddress);
|
||||
$(this).removeClass('button-disabled');
|
||||
$(this).addClass('button');
|
||||
} else {
|
||||
$(this).removeAttr('href');
|
||||
$(this).removeClass('button');
|
||||
$(this).addClass('button-disabled');
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
currentDashboard.toggleClass('local tailscale');
|
||||
let networkName = newNetwork.charAt(0).toUpperCase() + newNetwork.slice(1) + ' Network';
|
||||
currentDashboard.text(networkName)
|
||||
|
||||
const logoOn = $('#logoOn');
|
||||
const logoOff = $('#logoOff');
|
||||
let logoOnSrc = logoOn.attr('src');
|
||||
let logoOffSrc = logoOff.attr('src');
|
||||
|
||||
logoOn.attr('src', logoOffSrc)
|
||||
logoOff.attr('src', logoOnSrc)
|
||||
});
|
||||
}); */
|
||||
@@ -0,0 +1,309 @@
|
||||
body {
|
||||
background-color: black;
|
||||
transition: background-color 0.3s ease;
|
||||
background-image: url("./images/fullyblackfire.svg");
|
||||
background-size: auto 113%; /* Zooms the image in by 50% relative to the container */
|
||||
background-position: center 2px; /* Centers the zoomed image */
|
||||
background-repeat: no-repeat; /* Prevents the image from repeating */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media (min-width: 1140px) {
|
||||
body {
|
||||
background-position: center 6px;
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
|
||||
/* @media (min-width: 1140px) {
|
||||
body {
|
||||
flex-basis: 30%;
|
||||
}
|
||||
} */
|
||||
|
||||
body, html {
|
||||
margin: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dashboard {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
padding: 12px 46px;
|
||||
}
|
||||
|
||||
@media (min-width: 1140px) {
|
||||
.dashboard {
|
||||
height: 95%;
|
||||
}
|
||||
}
|
||||
|
||||
/* @media (min-width: 1140px) {
|
||||
.dashboard {
|
||||
background-image: none;
|
||||
}
|
||||
} */
|
||||
|
||||
.dashboard-title {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
-webkit-text-stroke: 1px black;
|
||||
}
|
||||
|
||||
.dashboard-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
font-size: 46px;
|
||||
font-weight: bold;
|
||||
color: #32CD32;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.header-purple {
|
||||
color: #967bb6;
|
||||
}
|
||||
|
||||
.current-dashboard {
|
||||
color: black;
|
||||
font-size: 36px;
|
||||
font-weight: normal;
|
||||
/* text-shadow: 4px 4px 10px #32CD32; */
|
||||
/* text-shadow: 3px 3px 0 #32CD32;
|
||||
text-shadow: -3px 3px 0 #32CD32;
|
||||
text-shadow: -3px -3px 0 #32CD32;
|
||||
text-shadow: 3px -3px 0 #32CD32; */
|
||||
}
|
||||
|
||||
.neonText {
|
||||
animation: flicker 1.5s infinite alternate;
|
||||
color: #fff;
|
||||
font-family: "Tilt Neon"
|
||||
}
|
||||
|
||||
@keyframes flicker {
|
||||
0%, 18%, 22%, 25%, 53%, 57%, 100% {
|
||||
text-shadow:
|
||||
0 0 4px #fff,
|
||||
0 0 11px #fff,
|
||||
0 0 19px #fff,
|
||||
0 0 40px #0fa,
|
||||
0 0 80px #0fa,
|
||||
0 0 90px #0fa,
|
||||
0 0 100px #0fa,
|
||||
0 0 150px #0fa;
|
||||
}
|
||||
20%, 24%, 55% {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-field {
|
||||
width: 80%;
|
||||
height: 90%;
|
||||
/* min-height: 62vh; */
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
padding: 24px;
|
||||
border-style: double;
|
||||
border-width: thick;
|
||||
border-color: #32CD32;
|
||||
border-radius: 25px;
|
||||
}
|
||||
|
||||
.dashboard-row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
.dashboard-row {
|
||||
flex-direction: row;
|
||||
padding: 10px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard-item {
|
||||
display: flex;
|
||||
flex: 1 1 90%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
/* width: 90%; */
|
||||
/* max-width: 364px; */
|
||||
padding: 5px 0px;
|
||||
/* padding: 0px 40px; */
|
||||
}
|
||||
|
||||
/* .dashboard-item:only-child {
|
||||
margin: 0 auto;
|
||||
} */
|
||||
|
||||
@media (min-width: 760px) {
|
||||
.dashboard-item {
|
||||
flex-basis: 45%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1140px) {
|
||||
.dashboard-item {
|
||||
flex-basis: 30%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* @media (min-width: 1000px) {
|
||||
.dashboard-item:only-child {
|
||||
padding: 60px 0px 10px;
|
||||
}
|
||||
} */
|
||||
|
||||
.single-item-row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
padding: 60px 0px 10px;
|
||||
}
|
||||
|
||||
.button {
|
||||
background-color: #32CD32;
|
||||
color: black;
|
||||
width: 100%;
|
||||
padding: 15px 32px;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
text-decoration: none;
|
||||
border-style: solid;
|
||||
border-width: medium;
|
||||
border-color: black;
|
||||
border-radius: 25px;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: black;
|
||||
color: #967bb6;
|
||||
border-color: #967bb6;
|
||||
}
|
||||
|
||||
.button-disabled {
|
||||
background-color: dimgrey;
|
||||
color: black;
|
||||
border-color: black;
|
||||
width: 100%;
|
||||
padding: 15px 32px;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
text-decoration: none;
|
||||
border-style: solid;
|
||||
border-width: medium;
|
||||
border-radius: 25px;
|
||||
}
|
||||
|
||||
.switch-button {
|
||||
position: absolute; /* Positions the button relative to the viewport */
|
||||
top: 40px;
|
||||
right: 0;
|
||||
z-index: 1000; /* Ensures the button appears above other content */
|
||||
/* Add other styling for your button (e.g., background-color, padding, font-size) */
|
||||
color: #967bb6;
|
||||
padding: 10px 15px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (min-width: 700px) {
|
||||
.switch-button {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.icon-button {
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#logoOff{
|
||||
display:none
|
||||
}
|
||||
#logoOn{
|
||||
display:block
|
||||
}
|
||||
|
||||
img {
|
||||
height: 8vh; /* Set your desired fixed height */
|
||||
width: auto; /* Allow the width to adjust automatically */
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
height: 120px; /* Set your desired fixed height */
|
||||
width: 350px; /* Allow the width to adjust automatically */
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.logo {
|
||||
background-image: url("./images/greenfirewithpurple.svg");
|
||||
height: 120px; /* Set your desired fixed height */
|
||||
width: auto; /* Allow the width to adjust automatically */
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.logo:hover {
|
||||
background-image: url("./images/fullyblackfire.svg");
|
||||
}
|
||||
|
||||
.image-container {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.image-container img {
|
||||
height: 100%; /* Image fills 100% of the container's height */
|
||||
width: 100%; /* Allow width to adjust proportionally */
|
||||
object-fit: contain; /* Crop and zoom to fill the container while maintaining aspect ratio */
|
||||
/* display: block; Remove extra space below the image */
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
.image-container img {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-switch {
|
||||
position: absolute; /* Position relative to the parent */
|
||||
top: 0; /* Align to the top edge */
|
||||
right: 0; /* Align to the right edge */
|
||||
/* Other styles for your toggle switch */
|
||||
}
|
||||
|
||||
.green-flame {
|
||||
background-color: #000000; /* Light background color */
|
||||
}
|
||||
|
||||
.black-flame{
|
||||
background-color: #32CD32; /* Dark background color */
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
# Network Dashboard (Sinatra Edition)
|
||||
|
||||
## Prep for development
|
||||
* Dockerize for dev
|
||||
* Move over css and images
|
||||
* Remove @post/template example stuff
|
||||
|
||||
|
||||
## Models
|
||||
|
||||
### Network
|
||||
* Has many devices
|
||||
* Type (Local, Tailscale, External?)
|
||||
|
||||
### Device
|
||||
* belongs to network
|
||||
* has many applications
|
||||
* Name (PC, Qnas, ect...)
|
||||
* IP
|
||||
|
||||
### Application
|
||||
* belongs to device
|
||||
* Name (Jellyfin, Nginx, ect...)
|
||||
* Port
|
||||
* Subdomain
|
||||
* Type
|
||||
|
||||
|
||||
## Views
|
||||
|
||||
### Dashboard
|
||||
* Replicate html/js/css apache dashboard look
|
||||
* Replace html/js with erb, move over css
|
||||
* Media Buttons (green), ? Buttons (purple), Admin Buttons (red) grouped by type
|
||||
* Alt view where apps ordered by device
|
||||
|
||||
### Update
|
||||
* Require login
|
||||
* Choose Edit/Update/Delete/Reorder for Application (Network and Device can stay in code)
|
||||
* Form to do the requested action
|
||||
|
||||
### Queue Radarr
|
||||
* Replicate logic in shortcut to queue item to Radarr
|
||||
* Put all in one form
|
||||
|
||||
### Status and Logs Dash (docker app to do this?)
|
||||
* Each app with current status
|
||||
* Logs from apps where it makes sense
|
||||
|
||||
|
||||
## Prep for use
|
||||
* Remove log4r (look up what it does first)
|
||||
* Remove testing stuff
|
||||
* Dockerize for production
|
||||