commit
c2b717bea6
@ -0,0 +1,2 @@
|
||||
---
|
||||
BUNDLE_PATH: "vendor/bundle"
|
||||
@ -0,0 +1,9 @@
|
||||
vendor
|
||||
db/*.sqlite
|
||||
.ruby-lsp
|
||||
logs/actions.log
|
||||
utils/custom_config.sh
|
||||
Steepfile
|
||||
caapp.private.key.pem
|
||||
caapp.public.key.pem
|
||||
!.gitignore
|
||||
@ -0,0 +1,62 @@
|
||||
FROM almalinux:9
|
||||
|
||||
RUN yum -y install dnf-plugins-core && \
|
||||
dnf config-manager --set-enabled crb
|
||||
|
||||
# Install build tools and dependencies for RVM / Ruby
|
||||
RUN yum -y update && \
|
||||
yum -y --allowerasing install \
|
||||
curl git gnupg2 \
|
||||
gcc gcc-c++ patch \
|
||||
readline-devel zlib-devel libyaml-devel libffi-devel openssl-devel ruby ruby-devel which procps-ng && \
|
||||
yum clean all
|
||||
|
||||
# Install RVM and Ruby 3.3.0
|
||||
RUN gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys \
|
||||
409B6B1796C275462A1703113804BB82D39DC0E3 \
|
||||
7D2BAF1CF37B13E2069D6956105BD0E739499BDB && \
|
||||
curl -sSL https://get.rvm.io | bash -s stable --ruby=3.3.0 && \
|
||||
echo 'source /etc/profile.d/rvm.sh' >> /etc/profile && \
|
||||
/bin/bash -lc "source /etc/profile.d/rvm.sh && gem install bundler"
|
||||
|
||||
# Make RVM binaries available
|
||||
ENV PATH="/usr/local/rvm/bin:${PATH}"
|
||||
|
||||
# Set Ruby 3.3.0 as the default version
|
||||
RUN /bin/bash -lc "source /etc/profile.d/rvm.sh && rvm use 3.3.0 --default"
|
||||
|
||||
# Create application user
|
||||
RUN useradd -m certuser
|
||||
|
||||
# Create project directory and set working directory
|
||||
WORKDIR /opt/cert/certcenter
|
||||
|
||||
# Copy only the specified directories and files
|
||||
COPY ca ./ca/
|
||||
COPY classes ./classes/
|
||||
COPY db ./db/
|
||||
COPY docs ./docs/
|
||||
COPY locale ./locale/
|
||||
COPY logs ./logs/
|
||||
COPY locks ./locks/
|
||||
COPY migration ./migration/
|
||||
COPY models ./models/
|
||||
COPY public ./public/
|
||||
COPY utils ./utils/
|
||||
COPY views ./views/
|
||||
COPY .bundle ./.bundle/
|
||||
COPY app.rb Gemfile Gemfile.lock ./
|
||||
|
||||
# Make the CA directory a bind mount point
|
||||
VOLUME /opt/cert/certcenter/ca
|
||||
VOLUME /opt/cert/certcenter/logs
|
||||
|
||||
# Prepare the application
|
||||
RUN /bin/bash -lc "source /etc/profile.d/rvm.sh && chmod +x ./utils/make_app_keys.sh" && \
|
||||
/bin/bash -lc "source /etc/profile.d/rvm.sh && ./utils/make_app_keys.sh ." && \
|
||||
/bin/bash -lc "source /etc/profile.d/rvm.sh && bundle install" && \
|
||||
/bin/bash -lc "source /etc/profile.d/rvm.sh && bundle exec sequel -m migration sqlite://db/base.sqlite"
|
||||
|
||||
EXPOSE 4567
|
||||
|
||||
CMD ["bash", "-lc", "source /etc/profile.d/rvm.sh && bundle exec ruby app.rb"]
|
||||
@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
source 'https://rubygems.org'
|
||||
|
||||
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
||||
|
||||
# gem "rails"
|
||||
|
||||
gem "sinatra", "~> 4.2"
|
||||
|
||||
gem "sqlite3", "~> 2.9"
|
||||
|
||||
gem "sequel", "~> 5.101"
|
||||
|
||||
gem "puma", "~> 7.2"
|
||||
|
||||
gem "rackup", "~> 2.3"
|
||||
|
||||
gem "rubyzip", "~> 3.2"
|
||||
|
||||
gem "jwt", "~> 3.1"
|
||||
|
||||
gem "openssl", "~> 4.0"
|
||||
|
||||
gem "i18n", "~> 1.14"
|
||||
@ -0,0 +1,61 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
base64 (0.3.0)
|
||||
bigdecimal (4.0.1)
|
||||
concurrent-ruby (1.3.6)
|
||||
i18n (1.14.8)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jwt (3.1.2)
|
||||
base64
|
||||
logger (1.7.0)
|
||||
mini_portile2 (2.8.9)
|
||||
mustermann (3.0.4)
|
||||
ruby2_keywords (~> 0.0.1)
|
||||
nio4r (2.7.5)
|
||||
openssl (4.0.0)
|
||||
puma (7.2.0)
|
||||
nio4r (~> 2.0)
|
||||
rack (3.2.4)
|
||||
rack-protection (4.2.1)
|
||||
base64 (>= 0.1.0)
|
||||
logger (>= 1.6.0)
|
||||
rack (>= 3.0.0, < 4)
|
||||
rack-session (2.1.1)
|
||||
base64 (>= 0.1.0)
|
||||
rack (>= 3.0.0)
|
||||
rackup (2.3.1)
|
||||
rack (>= 3)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (3.2.2)
|
||||
sequel (5.101.0)
|
||||
bigdecimal
|
||||
sinatra (4.2.1)
|
||||
logger (>= 1.6.0)
|
||||
mustermann (~> 3.0)
|
||||
rack (>= 3.0.0, < 4)
|
||||
rack-protection (= 4.2.1)
|
||||
rack-session (>= 2.0.0, < 3)
|
||||
tilt (~> 2.0)
|
||||
sqlite3 (2.9.0)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
sqlite3 (2.9.0-x86_64-linux-gnu)
|
||||
tilt (2.7.0)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
i18n (~> 1.14)
|
||||
jwt (~> 3.1)
|
||||
openssl (~> 4.0)
|
||||
puma (~> 7.2)
|
||||
rackup (~> 2.3)
|
||||
rubyzip (~> 3.2)
|
||||
sequel (~> 5.101)
|
||||
sinatra (~> 4.2)
|
||||
sqlite3 (~> 2.9)
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.22
|
||||
@ -0,0 +1,18 @@
|
||||
LOCK_PATH = 'locks/lock'.freeze
|
||||
GRANTED_UTILS = [
|
||||
'utils/config.sh',
|
||||
'utils/make_client_cert.sh',
|
||||
'utils/make_client_revoke.sh',
|
||||
'utils/make_server_cert.sh',
|
||||
'utils/make_server_revoke.sh',
|
||||
'utils/prepare.sh'
|
||||
].freeze
|
||||
PER_PAGE = 30
|
||||
LIFE_TOKEN = 300
|
||||
ALLOWED_IPS = [
|
||||
# Example: '192.168.1.10',
|
||||
# Add allowed IP addresses here
|
||||
'*'
|
||||
]
|
||||
PORT = 4567
|
||||
IPBIND = '0.0.0.0'.freeze
|
||||
@ -0,0 +1,20 @@
|
||||
class Paginator
|
||||
attr_reader :page, :per_page
|
||||
|
||||
def initialize(params, per_page, custom_name = 'p')
|
||||
@current_page = params[custom_name].nil? || params[custom_name].to_i < 1 ? 1 : params[custom_name].to_i
|
||||
@per_page = per_page
|
||||
end
|
||||
|
||||
def get_page(items)
|
||||
start_index = (@current_page - 1) * @per_page
|
||||
items[start_index, @per_page]
|
||||
end
|
||||
|
||||
def pages_info(items)
|
||||
total_pages = (items.length / @per_page.to_f).ceil
|
||||
(1..total_pages).map do |page_number|
|
||||
{ page: page_number, is_current: page_number == @current_page }
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,137 @@
|
||||
require "open3"
|
||||
require "logger"
|
||||
|
||||
class Runner
|
||||
attr_reader :cmd, :exit_status, :stdout, :stderr, :pid, :log
|
||||
|
||||
# Run a command, return runner instance
|
||||
# @param cmd [String,Array<String>] command to execute
|
||||
def self.run(*cmd)
|
||||
Runner.new(*cmd).run
|
||||
end
|
||||
|
||||
# Run a command, raise Runner::Error if it fails
|
||||
# @param cmd [String,Array<String>] command to execute
|
||||
# @raise [Runner::Error]
|
||||
def self.run!(*cmd)
|
||||
Runner.new(*cmd).run!
|
||||
end
|
||||
|
||||
# Run a command, return true if it succeeds, false if not
|
||||
# @param cmd [String,Array<String>] command to execute
|
||||
# @return [Boolean]
|
||||
def self.run?(*cmd)
|
||||
Runner.new(*cmd).run?
|
||||
end
|
||||
|
||||
Error = Class.new(StandardError)
|
||||
|
||||
# @param cmd [String,Array<String>] command to execute
|
||||
def initialize(cmd, log = nil)
|
||||
@cmd = cmd.is_a?(Array) ? cmd.join(" ") : cmd
|
||||
@stdout = +""
|
||||
@stderr = +""
|
||||
@exit_status = nil
|
||||
@log = log
|
||||
end
|
||||
|
||||
# @return [Boolean] success or failure?
|
||||
def success?
|
||||
exit_status.zero?
|
||||
end
|
||||
|
||||
# Run the command, return self
|
||||
# @return [Runner]
|
||||
def run
|
||||
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
|
||||
until [stdout, stderr].all?(&:eof?)
|
||||
readable = IO.select([stdout, stderr])
|
||||
next unless readable&.first
|
||||
|
||||
readable.first.each do |stream|
|
||||
data = +""
|
||||
# rubocop:disable Lint/HandleExceptions
|
||||
begin
|
||||
stream.read_nonblock(1024, data)
|
||||
rescue EOFError
|
||||
# ignore, it's expected for read_nonblock to raise EOFError
|
||||
# when all is read
|
||||
end
|
||||
|
||||
if stream == stdout
|
||||
@stdout << data
|
||||
if log.nil?
|
||||
$stdout.write(data)
|
||||
else
|
||||
log.info(data)
|
||||
end
|
||||
else
|
||||
@stderr << data
|
||||
if log.nil?
|
||||
$stderr.write(data)
|
||||
else
|
||||
log.error(data)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@exit_status = wait_thr.value.exitstatus
|
||||
@pid = wait_thr.pid
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
# Run the command no output, return self
|
||||
# @return [Runner]
|
||||
def run_clean
|
||||
Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
|
||||
until [stdout, stderr].all?(&:eof?)
|
||||
readable = IO.select([stdout, stderr])
|
||||
next unless readable&.first
|
||||
|
||||
readable.first.each do |stream|
|
||||
data = +""
|
||||
# rubocop:disable Lint/HandleExceptions
|
||||
begin
|
||||
stream.read_nonblock(1024, data)
|
||||
rescue EOFError
|
||||
# ignore, it's expected for read_nonblock to raise EOFError
|
||||
# when all is read
|
||||
end
|
||||
|
||||
if stream == stdout
|
||||
@stdout << data
|
||||
unless log.nil?
|
||||
log.info(data)
|
||||
end
|
||||
else
|
||||
@stderr << data
|
||||
unless log.nil?
|
||||
log.error(data)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@exit_status = wait_thr.value.exitstatus
|
||||
@pid = wait_thr.pid
|
||||
end
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
# Run the command and return stdout, raise if fails
|
||||
# @return stdout [String]
|
||||
# @raise [Runner::Error]
|
||||
def run!
|
||||
return run.stdout if run.success?
|
||||
|
||||
raise(Error, "command failed, exit: %d - stdout: %s / stderr: %s" % [exit_status, stdout, stderr])
|
||||
end
|
||||
|
||||
# Run the command and return true if success, false if failure
|
||||
# @return success [Boolean]
|
||||
def run?
|
||||
run.success?
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,141 @@
|
||||
---
|
||||
en:
|
||||
errors:
|
||||
cannot_determine_root_dir: "Unable to determine root directory of the certificate center"
|
||||
no_result_file: "Result file not found"
|
||||
record_not_found: "Record with the specified ID not found"
|
||||
root_ca_not_detected: "ROOT CA not detected"
|
||||
cannot_get_certificate_info: "Unable to get certificate information"
|
||||
command_execution_error: "Command execution error: %{command}"
|
||||
argument_error_days: "Days must be a number greater than 0"
|
||||
argument_error_domains_ips: "Domains and IPs list cannot be empty"
|
||||
argument_error_server_domain: "Server domain cannot be empty"
|
||||
argument_error_client_id: "Client ID cannot be empty"
|
||||
no_permission: You do not have access rights to the given URL
|
||||
authorization_error: "Authorization error: cannot recognize session data"
|
||||
token_expired: Token has expired
|
||||
revocation_missing_id: Missing certificate ID for revocation
|
||||
revocation_failed: Certificate revocation finished with error
|
||||
server_domain_missing: Missing server domain
|
||||
client_id_missing: Missing client identifier
|
||||
validity_days_missing: Validity period not set
|
||||
cert_created_error: Certificate creation failed
|
||||
domains_missing: Missing domain list for certificate
|
||||
user_not_found: User does not exist
|
||||
all_fields_required: All fields are required
|
||||
invalid_json: Invalid JSON format
|
||||
invalid_username_password: Invalid username or password
|
||||
page_not_found: Page not found
|
||||
user_already_exists: User already exists
|
||||
search_parameters_not_set: Search parameters not set
|
||||
access_denied: Access denied
|
||||
pages:
|
||||
servers: Servers
|
||||
clients: Clients
|
||||
revocation: Revocation
|
||||
not_found: Page not found
|
||||
users: Users
|
||||
login: Login
|
||||
install_server: Install Server
|
||||
api: API
|
||||
root: Root
|
||||
messages:
|
||||
install_not_possible: Installation impossible
|
||||
install_detected: Installation already performed or manually detected
|
||||
missing_utilities: "Missing required utilities: %{missing}"
|
||||
install_success: Installation completed successfully
|
||||
install_incomplete: Please fill all required fields.
|
||||
install_failed: Installation failed, clear the directory %{cert_path}, delete utils/custom_config.h and restart the installation.
|
||||
install_success_descr: Go to / and log in as admin.
|
||||
client_readme: |
|
||||
Generated set of keys for installing on client machine for access:
|
||||
|
||||
- private key: `%{private_key}`;
|
||||
- server certificate: `%{server_cert}`;
|
||||
- CA chain: `%{ca_chain}`.
|
||||
server_readme: |
|
||||
Generated set of keys for installing on server:
|
||||
|
||||
- private key: `%{private_key}`;
|
||||
- server certificate: `%{server_cert}`;
|
||||
- CA chain: `%{ca_chain}`;
|
||||
- revoked certificates list: `%{crl}`.
|
||||
user_added: User saved
|
||||
user_deleted: User deleted
|
||||
views:
|
||||
cert_val_day: Number of days the certificate will be valid
|
||||
authorize: Login
|
||||
user_name: Username
|
||||
enter_login: Enter login
|
||||
password: Password
|
||||
enter_pasword: Enter password
|
||||
please_enter_all_required_fields: Please fill in all required fields.
|
||||
certificate_information: Certificate information
|
||||
revoke_information: Revocation information
|
||||
user: User
|
||||
logout: Logout
|
||||
install_server_title: Complete the installation and configuration of the certification server
|
||||
install_server_description: In case configuration files and initialized environment were not found, you need to perform a preliminary setup
|
||||
cert_path_label: Path to the directory where the certificate database will be stored
|
||||
country_name_label: Country code
|
||||
org_name_label: Organization name (Organization name)
|
||||
common_name_label: Division name (Common name)
|
||||
cert_password_label: Certificate password (root and intermediate)
|
||||
enter_cert_path_placeholder: Enter the directory path
|
||||
enter_country_name_placeholder: Enter country code
|
||||
enter_org_name_placeholder: Enter organization name
|
||||
enter_common_name_placeholder: Enter division name
|
||||
enter_cert_password_placeholder: Enter password
|
||||
save: Save
|
||||
create_user_button: Create user
|
||||
user_list_tab: User list
|
||||
edit_user_tab: Edit user
|
||||
user_name_header: Username
|
||||
role_header: Role
|
||||
email_header: Email
|
||||
created_at_header: Creation date
|
||||
actions_header: Actions
|
||||
card_header_edit_user: Edit user
|
||||
login_label: Login
|
||||
password_label: Password
|
||||
email_label: Email
|
||||
role_label: Role
|
||||
submit_create_user: Create user
|
||||
submit_update_user: Update user
|
||||
modal_title_confirm: Confirm action
|
||||
modal_body_confirm: Are you shure you want to delete user?
|
||||
modal_body_confirm_revoke: Are you sure you want to revoke the certificate?
|
||||
modal_btn_delete: Delete
|
||||
modal_btn_cancel: Cancel
|
||||
no_email: None
|
||||
role_user: user
|
||||
role_creator: creator
|
||||
role_admin: admin
|
||||
role_unknown: unknown
|
||||
create_request_cert_button: Request new certificate
|
||||
domains_label: Domains and IPs separated by commas
|
||||
validity_days_label: Number of days the certificate will be valid
|
||||
domains_placeholder: example.com, 192.168.1.1
|
||||
request_cert_submit: Request certificate
|
||||
request_cert_info: You can later download the certificate and additional information on the certificate list page or on the certificate information page. The first domain name will be the certificate identifier and must be unique.
|
||||
server_certs_tab: Server certificates list
|
||||
selected_cert_info_tab: Selected certificate information
|
||||
status_header: Status
|
||||
id_header: ID
|
||||
date_header: Date
|
||||
revoke_date_header: Revocation date
|
||||
info_header: Info
|
||||
revoke_cert_tooltip: Revoke certificate
|
||||
download_cert_tooltip: Download certificate
|
||||
view_cert_tooltip: View certificate
|
||||
view_clients_tooltip: View client certificates for server
|
||||
cert_info_card_title: Certificate information
|
||||
revoke_info_card_title: Revocation information
|
||||
additional_actions: Additional actions
|
||||
revoke_button: Revoke
|
||||
server_access_label: Server to which access will be granted
|
||||
client_name_email_label: Client name or its email (must be unique)
|
||||
client_placeholder: user@user.example
|
||||
list_clients_tab: Client certificates list
|
||||
filtered_certs_tab: Filtered certificates
|
||||
delete_user: Delete user
|
||||
@ -0,0 +1,17 @@
|
||||
require 'sequel'
|
||||
require 'digest'
|
||||
|
||||
Sequel.migration do
|
||||
change do
|
||||
create_table(:users) do
|
||||
primary_key :id
|
||||
String :login, null: false, unique: true
|
||||
String :password, null: false
|
||||
String :email
|
||||
Integer :role, null: false
|
||||
DateTime :create_at, default: Sequel.lit('CURRENT_TIMESTAMP')
|
||||
end
|
||||
|
||||
self[:users].insert(login: 'admin', password: Digest::SHA256.hexdigest('admin'), role: 2, email: 'admin@admin')
|
||||
end
|
||||
end
|
||||
@ -0,0 +1,152 @@
|
||||
require 'i18n'
|
||||
require_relative 'users'
|
||||
|
||||
class UserSessionData
|
||||
attr_accessor :user_info, :error
|
||||
|
||||
def initialize(name, password, init = nil)
|
||||
@user_info = nil
|
||||
@error = nil
|
||||
if init.nil?
|
||||
user = get_user(name, password)
|
||||
if user
|
||||
@user_info = user
|
||||
else
|
||||
@error = I18n.t('errors.invalid_username_password')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def auth?
|
||||
!@user_info.nil?
|
||||
end
|
||||
|
||||
def role
|
||||
if auth?
|
||||
@user_info[:role]
|
||||
else
|
||||
-1
|
||||
end
|
||||
end
|
||||
|
||||
def user_info
|
||||
@user_info
|
||||
end
|
||||
|
||||
def add_user(name, password, email, role)
|
||||
@error = nil
|
||||
user = User.where(login: name).first
|
||||
if user
|
||||
@error = I18n.t('errors.user_already_exists')
|
||||
else
|
||||
User.create(login: name, password: Digest::SHA256.hexdigest(password.strip), email: email, role: role)
|
||||
end
|
||||
end
|
||||
|
||||
def list_users()
|
||||
User.order(Sequel.asc(:login)).all
|
||||
end
|
||||
|
||||
def user_info(name, id = nil)
|
||||
@error = nil
|
||||
if name.nil? && id.nil?
|
||||
@error = I18n.t('errors.search_parameters_not_set')
|
||||
retrun nil
|
||||
end
|
||||
user = nil
|
||||
if id.nil?
|
||||
user = User.where(login: name).first
|
||||
else
|
||||
user = User.where(id: id).first
|
||||
end
|
||||
if user
|
||||
user
|
||||
else
|
||||
@error = I18n.t('errors.user_not_found')
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def del_user(name, id = nil)
|
||||
@error = nil
|
||||
if name.nil? && id.nil?
|
||||
@error = I18n.t('errors.search_parameters_not_set')
|
||||
return
|
||||
end
|
||||
user = nil
|
||||
if id.nil?
|
||||
user = User.where(login: name).first
|
||||
else
|
||||
user = User.where(id: id).first
|
||||
end
|
||||
if user
|
||||
user.delete
|
||||
else
|
||||
@error = I18n.t('errors.user_not_found')
|
||||
end
|
||||
end
|
||||
|
||||
def update_user(name, password, email, role, id = nil)
|
||||
@error = nil
|
||||
if name.nil? && id.nil?
|
||||
@error = I18n.t('errors.search_parameters_not_set')
|
||||
return
|
||||
end
|
||||
user = nil
|
||||
if id.nil?
|
||||
user = User.where(login: name).first
|
||||
else
|
||||
user = User.where(id: id).first
|
||||
end
|
||||
if user
|
||||
changes = {}
|
||||
changes[:password] = Digest::SHA256.hexdigest(password) unless password.nil? || password.empty?
|
||||
changes[:email] = email unless email.nil? || email.empty?
|
||||
changes[:role] = case role.to_i
|
||||
when 0 then 0
|
||||
when 1 then 1
|
||||
when 2 then 2
|
||||
else user[:role]
|
||||
end
|
||||
user.update(changes) unless changes.empty?
|
||||
else
|
||||
@error = I18n.t('errors.user_not_found')
|
||||
end
|
||||
end
|
||||
|
||||
def err
|
||||
@error
|
||||
end
|
||||
|
||||
def tok
|
||||
@user_info.nil?
|
||||
end
|
||||
|
||||
def login
|
||||
@user_info[:login]
|
||||
end
|
||||
|
||||
# Методы для сериализации и десериализации объекта в JWT токене
|
||||
def serialize
|
||||
{ user_info: @user_info.to_hash, error: @error }.to_json
|
||||
end
|
||||
|
||||
def self.deserialize(token)
|
||||
data = JSON.parse(token, symbolize_names: true)
|
||||
instance = new(nil, nil, true)
|
||||
user_data = data[:user_info]
|
||||
|
||||
instance.instance_variable_set(:@user_info, user_data)
|
||||
instance.instance_variable_set(:@error, data[:error])
|
||||
instance
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_user(name, password)
|
||||
user = User.where(login: name).first
|
||||
return unless user && user[:password] == Digest::SHA256.hexdigest(password)
|
||||
|
||||
user
|
||||
end
|
||||
end
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,230 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") -s SERVER -u USER -p PASS <command> [args]
|
||||
|
||||
Commands:
|
||||
install <login> <password> <email> # Create initial admin user (no auth needed)
|
||||
listserv # List server certificates
|
||||
listclient # List client certificates
|
||||
addserv <domains> <validity_days> # Add server certificate
|
||||
addclient <server_domain> <client> <validity_days> # Add client certificate
|
||||
listuser # List users
|
||||
adduser <login> <password> <email> <role> # Add user (role numeric)
|
||||
revokecert <id> # Revoke certificate
|
||||
deleteuser <id> # Delete user
|
||||
edituser <id> <login> <password> <role> # Edit user
|
||||
certdetail <id> # Cert detail
|
||||
rootdetail # Root cert detail
|
||||
help # Show this help
|
||||
|
||||
Options:
|
||||
-s SERVER Base URL of the API (default: http://127.0.0.1:4567)
|
||||
-u USER Username for authentication
|
||||
-p PASS Password for authentication
|
||||
EOF
|
||||
}
|
||||
|
||||
# Parse global options
|
||||
SERVER="http://127.0.0.1:4567"
|
||||
USERNAME=""
|
||||
PASSWORD=""
|
||||
|
||||
while getopts ":s:u:p:h" opt; do
|
||||
case $opt in
|
||||
s) SERVER="$OPTARG" ;;
|
||||
u) USERNAME="$OPTARG" ;;
|
||||
p) PASSWORD="$OPTARG" ;;
|
||||
h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option -$OPTARG"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND - 1))
|
||||
|
||||
COMMAND="$1"
|
||||
shift
|
||||
|
||||
# Helper: get token
|
||||
get_token() {
|
||||
local login="$1"
|
||||
local pass="$2"
|
||||
local resp
|
||||
resp=$(curl -s -X POST "$SERVER/api/v1/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"login\":\"$login\",\"password\":\"$pass\"}")
|
||||
local err
|
||||
err=$(echo "$resp" | jq -r '.error')
|
||||
if [[ "$err" != "null" && -n "$err" ]]; then
|
||||
echo "Login error: $err" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "$resp" | jq -r '.content.token'
|
||||
}
|
||||
|
||||
# Helper: perform request
|
||||
do_req() {
|
||||
local method="$1"
|
||||
local url="$2"
|
||||
local data="$3"
|
||||
local token="$4"
|
||||
local json_data
|
||||
if [[ -n "$token" ]]; then
|
||||
json_data=$(echo "$data" | jq --arg token "$token" '. + {token: $token}')
|
||||
else
|
||||
json_data="$data"
|
||||
fi
|
||||
local resp
|
||||
resp=$(curl -s -X "$method" "$url" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$json_data")
|
||||
local err
|
||||
err=$(echo "$resp" | jq -r '.error')
|
||||
if [[ "$err" != "null" && -n "$err" ]]; then
|
||||
echo "Error: $err" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "$resp" | jq -r '.content'
|
||||
}
|
||||
|
||||
# Commands requiring authentication
|
||||
auth_required_commands=("listserv" "listclient" "addserv" "addclient" "listuser" "adduser" "revokecert" "deleteuser" "edituser" "certdetail" "rootdetail")
|
||||
needs_auth=false
|
||||
for cmd in "${auth_required_commands[@]}"; do
|
||||
if [[ "$COMMAND" == "$cmd" ]]; then
|
||||
needs_auth=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if $needs_auth; then
|
||||
if [[ -z "$USERNAME" || -z "$PASSWORD" ]]; then
|
||||
echo "Username and password required for command '$COMMAND'" >&2
|
||||
exit 1
|
||||
fi
|
||||
TOKEN=$(get_token "$USERNAME" "$PASSWORD")
|
||||
if [[ -z "$TOKEN" ]]; then
|
||||
echo "Failed to obtain token" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
case "$COMMAND" in
|
||||
install)
|
||||
if [[ $# -ne 3 ]]; then
|
||||
echo "install requires <login> <password> <email>" >&2
|
||||
exit 1
|
||||
fi
|
||||
login="$1"
|
||||
pw="$2"
|
||||
email="$3"
|
||||
do_req POST "$SERVER/api/v1/adduser" "{\"login\":\"$login\",\"password\":\"$pw\",\"email\":\"$email\",\"role\":1}" ""
|
||||
;;
|
||||
listserv)
|
||||
do_req POST "$SERVER/api/v1/servers" "{}" "$TOKEN"
|
||||
;;
|
||||
listclient)
|
||||
do_req POST "$SERVER/api/v1/clients" "{}" "$TOKEN"
|
||||
;;
|
||||
addserv)
|
||||
if [[ $# -ne 2 ]]; then
|
||||
echo "addserv requires <domains> <validity_days>" >&2
|
||||
exit 1
|
||||
fi
|
||||
domains="$1"
|
||||
days="$2"
|
||||
do_req POST "$SERVER/api/v1/addserver" "{\"domains\":\"$domains\",\"validity_days\":$days}" "$TOKEN"
|
||||
;;
|
||||
addclient)
|
||||
if [[ $# -ne 3 ]]; then
|
||||
echo "addclient requires <server_domain> <client> <validity_days>" >&2
|
||||
exit 1
|
||||
fi
|
||||
server_domain="$1"
|
||||
client="$2"
|
||||
validity_days="$3"
|
||||
do_req POST "$SERVER/api/v1/addclient" "{\"server_domain\":\"$server_domain\",\"client\":\"$client\",\"validity_days\":\"$validity_days\"}" "$TOKEN"
|
||||
;;
|
||||
listuser)
|
||||
do_req POST "$SERVER/api/v1/ulist" "{}" "$TOKEN"
|
||||
;;
|
||||
adduser)
|
||||
if [[ $# -ne 4 ]]; then
|
||||
echo "adduser requires <login> <password> <email> <role:user|creator|admin>" >&2
|
||||
exit 1
|
||||
fi
|
||||
login="$1"
|
||||
pw="$2"
|
||||
email="$3"
|
||||
role="$4"
|
||||
case "$role" in
|
||||
user) role=0 ;;
|
||||
creator) role=1 ;;
|
||||
admin) role=2 ;;
|
||||
*) role=0 ;;
|
||||
esac
|
||||
do_req POST "$SERVER/api/v1/adduser" "{\"login\":\"$login\",\"password\":\"$pw\",\"email\":\"$email\",\"role\":$role}" "$TOKEN"
|
||||
;;
|
||||
revokecert)
|
||||
if [[ $# -ne 1 ]]; then
|
||||
echo "revokecert requires <id>" >&2
|
||||
exit 1
|
||||
fi
|
||||
id="$1"
|
||||
do_req POST "$SERVER/api/v1/revoke/$id" "{}" "$TOKEN"
|
||||
;;
|
||||
deleteuser)
|
||||
if [[ $# -ne 1 ]]; then
|
||||
echo "deleteuser requires <id>" >&2
|
||||
exit 1
|
||||
fi
|
||||
id="$1"
|
||||
do_req POST "$SERVER/api/v1/deleteuser/$id" "{}" "$TOKEN"
|
||||
;;
|
||||
edituser)
|
||||
if [[ $# -ne 5 ]]; then
|
||||
echo "edituser requires <id> <login> <password> <email> <role: user|creator|admin>" >&2
|
||||
exit 1
|
||||
fi
|
||||
id="$1"
|
||||
login="$2"
|
||||
pw="$3"
|
||||
role="$5"
|
||||
email="$4"
|
||||
case "$role" in
|
||||
user) role=0 ;;
|
||||
creator) role=1 ;;
|
||||
admin) role=2 ;;
|
||||
*) role=0 ;;
|
||||
esac
|
||||
do_req POST "$SERVER/api/v1/edituser/$id" "{\"login\":\"$login\",\"password\":\"$pw\",\"role\":$role,\"email\":\"$email\"}" "$TOKEN"
|
||||
;;
|
||||
certdetail)
|
||||
if [[ $# -ne 1 ]]; then
|
||||
echo "certdetail requires <id>" >&2
|
||||
exit 1
|
||||
fi
|
||||
id="$1"
|
||||
do_req POST "$SERVER/api/v1/certinfo/$id" "{}" "$TOKEN"
|
||||
;;
|
||||
rootdetail)
|
||||
do_req POST "$SERVER/api/v1/root" "{}" "$TOKEN"
|
||||
;;
|
||||
help | --help | -h)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
echo "Unknown command: $COMMAND" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [ -e custom_config.sh ]; then
|
||||
source custom_config.sh
|
||||
else
|
||||
|
||||
ROOT_DIR="."
|
||||
|
||||
COUNTRY_NAME="RU"
|
||||
ORG_NAME="Regenal Organization"
|
||||
COMM_NAME="General Name"
|
||||
SERT_PASS=""
|
||||
|
||||
fi
|
||||
|
||||
if [ -z "$SERT_PASS" ]; then
|
||||
if [[ "$LANG" =~ ^ru ]]; then
|
||||
echo "Установите пароль для корневого сертификата и промежуточного"
|
||||
else
|
||||
echo "Please set a password for the root certificate and intermediate"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PATH_TO_CA="$ROOT_DIR/ca"
|
||||
ROOT_CA="$PATH_TO_CA/root"
|
||||
IMM_CA="$PATH_TO_CA/intermediate"
|
||||
CLI_CA="$PATH_TO_CA/client_certs"
|
||||
@ -0,0 +1,10 @@
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<%= I18n.t('pages.api') %>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<pre><%= ERB::Util.html_escape(File.read('docs/API.md')) %></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,69 @@
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<%= I18n.t('views.install_server_title') %>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text text-center"><%= I18n.t('views.install_server_description') %></p>
|
||||
<div class="container">
|
||||
<form id="cert-form" action="/install" method="POST" class="d-flex flex-column align-items-center">
|
||||
<div class="mb-3 w-100">
|
||||
<label for="cert-path" class="form-label"><%= I18n.t('views.cert_path_label') %></label>
|
||||
<input type="text" class="form-control w-100 required" id="cert-path" name="cert-path"
|
||||
placeholder="<%= I18n.t('views.enter_cert_path_placeholder') %>">
|
||||
</div>
|
||||
<div class="mb-3 w-100">
|
||||
<label for="validity-days" class="form-label"><%= I18n.t('views.cert_val_day') %></label>
|
||||
<input type="text" class="form-control w-100 required" id="validity-days" name="validity-days"
|
||||
placeholder="3650">
|
||||
</div>
|
||||
<div class="mb-3 w-100">
|
||||
<label for="country-name" class="form-label"><%= I18n.t('views.country_name_label') %></label>
|
||||
<input type="text" class="form-control w-100 required" id="country-name" name="country-name"
|
||||
placeholder="<%= I18n.t('views.enter_country_name_placeholder') %>">
|
||||
</div>
|
||||
<div class="mb-3 w-100">
|
||||
<label for="org-name" class="form-label"><%= I18n.t('views.org_name_label') %></label>
|
||||
<input type="text" class="form-control w-100 required" id="org-name" name="org-name"
|
||||
placeholder="<%= I18n.t('views.enter_org_name_placeholder') %>">
|
||||
</div>
|
||||
<div class="mb-3 w-100">
|
||||
<label for="common-name" class="form-label"><%= I18n.t('views.common_name_label') %></label>
|
||||
<input type="text" class="form-control w-100 required" id="common-name" name="common-name"
|
||||
placeholder="<%= I18n.t('views.enter_common_name_placeholder') %>">
|
||||
</div>
|
||||
<div class="mb-3 w-100">
|
||||
<label for="cert-password" class="form-label"><%= I18n.t('views.cert_password_label') %></label>
|
||||
<input type="password" class="form-control w-100 required" id="cert-password" name="cert-password"
|
||||
placeholder="<%= I18n.t('views.enter_cert_password_placeholder') %>">
|
||||
</div>
|
||||
<div class="d-flex justify-content-center w-100">
|
||||
<button type="button" class="btn btn-primary" id="submit-btn"><%= I18n.t('views.save') %></button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('#submit-btn').click(function () {
|
||||
var isValid = true;
|
||||
$('.required').each(function() {
|
||||
if ($(this).val() === '') {
|
||||
isValid = false;
|
||||
$(this).addClass('is-invalid');
|
||||
} else {
|
||||
$(this).removeClass('is-invalid');
|
||||
}
|
||||
});
|
||||
|
||||
if (isValid) {
|
||||
$('#cert-form').submit();
|
||||
} else {
|
||||
alert('<%= I18n.t('views.please_enter_all_required_fields') %>');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,6 @@
|
||||
<div class="card text-white mb-3 p-3">
|
||||
<div class="card-header bg-danger text-center"><%= ERB::Util.html_escape(@error) %></div>
|
||||
<div class="card-body text-black">
|
||||
<pre><%= ERB::Util.html_escape(@log) %></pre>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,10 @@
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<%= @reason %>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text text-center"><%= @info_descr %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,61 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title><%= @page_name %></title>
|
||||
<link rel="stylesheet" href="/js/bootstrap.min.css" />
|
||||
<script src="/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/js/jquery-4.0.0.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<% if @menu %>
|
||||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="https://brepo.ru">brepo.ru</a>
|
||||
<span class="navbar-text pe-2 me-2 border-end"><%= I18n.t('views.user') %>: <%= session[:user].login %></span>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup"
|
||||
aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
|
||||
<div class="navbar-nav">
|
||||
<a class="nav-link
|
||||
<% if @page_name == I18n.t('pages.servers') %>
|
||||
active
|
||||
<% end %>" aria-current="page" href="/"><%= I18n.t('pages.servers') %></a>
|
||||
<a class="nav-link
|
||||
<% if @page_name == I18n.t('pages.clients') %>
|
||||
active
|
||||
<% end %>" aria-current="page" href="/clients"><%= I18n.t('pages.clients') %></a>
|
||||
<% if hasperms?('admin') %>
|
||||
<a class="nav-link
|
||||
<% if @page_name == I18n.t('pages.users') %>
|
||||
active
|
||||
<% end %>
|
||||
" href="/ulist"><%= I18n.t('pages.users') %></a>
|
||||
<% end %>
|
||||
<a class="nav-link <%= 'active' if @page_name == I18n.t('pages.api') %>" href="/apiinfo"><%= I18n.t('pages.api') %></a>
|
||||
<a class="nav-link <%= 'active' if @page_name == I18n.t('pages.root') %>" href="/root"><%= I18n.t('pages.root') %></a>
|
||||
<a class="nav-link" href="/logout"><%= I18n.t('views.logout') %></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<% end %>
|
||||
|
||||
<div class="container pb-5"></div>
|
||||
|
||||
<%= yield %>
|
||||
|
||||
<div class="container pb-5"></div>
|
||||
|
||||
<div class="container-fluid text-center bg-light p-2">
|
||||
Made by BayRepo © 2026
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -0,0 +1,245 @@
|
||||
<% if hasperms?('creator') %>
|
||||
<div class="container p-3">
|
||||
<p class="d-inline-flex gap-1">
|
||||
<button class="btn btn-outline-success" type="button" data-bs-toggle="collapse" data-bs-target="#collapseForm" aria-expanded="false" aria-controls="collapseForm">
|
||||
<%= I18n.t('views.create_request_cert_button') %>
|
||||
</button>
|
||||
</p>
|
||||
<div class="collapse" id="collapseForm">
|
||||
<div class="card card-body">
|
||||
<form action="/addserver" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="domains" class="form-label"><%= I18n.t('views.domains_label') %></label>
|
||||
<input type="text" class="form-control" id="domains" name="domains" required placeholder="<%= I18n.t('views.domains_placeholder') %>">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="validityDays" class="form-label"><%= I18n.t('views.validity_days_label') %></label>
|
||||
<input type="number" class="form-control" id="validityDays" name="validity_days" required value="365">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><%= I18n.t('views.request_cert_submit') %></button>
|
||||
<p class="mt-2"><%= I18n.t('views.request_cert_info') %></p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="container">
|
||||
<ul class="nav nav-pills mb-3" id="liclist-tab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link
|
||||
<% if @tab == 0 %>
|
||||
active
|
||||
<% end %>
|
||||
" id="pills-slist-tab" data-bs-toggle="pill" data-bs-target="#pills-slist"
|
||||
type="button" role="tab" aria-controls="pills-slist"
|
||||
<% if @tab == 0 %>
|
||||
aria-selected="true"
|
||||
<% else %>
|
||||
aria-selected="false"
|
||||
<% end %>
|
||||
>
|
||||
<%= I18n.t('views.server_certs_tab') %>
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link
|
||||
<% if @tab == 1 %>
|
||||
active
|
||||
<% end %>
|
||||
" id="pills-clist-tab" data-bs-toggle="pill" data-bs-target="#pills-clist"
|
||||
type="button" role="tab" aria-controls="pills-clist"
|
||||
<% if @tab == 0 %>
|
||||
aria-selected="false" disabled
|
||||
<% else %>
|
||||
aria-selected="true"
|
||||
<% end %>
|
||||
>
|
||||
<%= I18n.t('views.selected_cert_info_tab') %>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" id="liclist-tabContent">
|
||||
<div class="tab-pane fade
|
||||
<% if @tab == 0 %>
|
||||
show active
|
||||
<% end %>
|
||||
" id="pills-slist" role="tabpanel" aria-labelledby="pills-slist-tab" tabindex="0">
|
||||
<div class="container text-center">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"><%= I18n.t('views.status_header') %></th>
|
||||
<th scope="col"><%= I18n.t('views.id_header') %></th>
|
||||
<th scope="col"><%= I18n.t('views.date_header') %></th>
|
||||
<th scope="col"><%= I18n.t('views.revoke_date_header') %></th>
|
||||
<th scope="col"><%= I18n.t('views.info_header') %></th>
|
||||
<th scope="col"><%= I18n.t('views.actions_header') %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @list_serv.each do |item| %>
|
||||
<tr
|
||||
<% if item[:status] != 'V' %>
|
||||
class="table-danger"
|
||||
<% elsif item[:expired] %>
|
||||
class="table-warning"
|
||||
<% end %>
|
||||
>
|
||||
<td><%= item[:status] %></td>
|
||||
<td><%= item[:id] %></td>
|
||||
<td><%= item[:date] %></td>
|
||||
<td><%= item[:revoke_date] %></td>
|
||||
<td>CN=<%= item[:ui][:CN] %>, O=<%= item[:ui][:O] %></td>
|
||||
<td>
|
||||
<% if hasperms?('creator') %>
|
||||
<a href="/revoke/<%= ERB::Util.url_encode(item[:id]) %>" data-bs-toggle="tooltip" data-bs-title="<%= I18n.t('views.revoke_cert_tooltip') %>" class="icon-link revoke-cert">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
|
||||
fill="currentColor" class="bi bi-sign-stop" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M3.16 10.08c-.931 0-1.447-.493-1.494-1.132h.653c.065.346.396.583.891.583.524 0 .83-.246.83-.62 0-.303-.203-.467-.637-.572l-.656-.164c-.61-.147-.978-.51-.978-1.078 0-.706.597-1.184 1.444-1.184.853 0 1.386.475 1.436 1.087h-.645c-.064-.32-.352-.542-.797-.542-.472 0-.77.246-.77.6 0 .261.196.437.553.522l.654.161c.673.164 1.06.487 1.06 1.11 0 .736-.574 1.228-1.544 1.228Zm3.427-3.51V10h-.665V6.57H4.753V6h3.006v.568H6.587Z" />
|
||||
<path fill-rule="evenodd"
|
||||
d="M11.045 7.73v.544c0 1.131-.636 1.805-1.661 1.805-1.026 0-1.664-.674-1.664-1.805V7.73c0-1.136.638-1.807 1.664-1.807s1.66.674 1.66 1.807Zm-.674.547v-.553c0-.827-.422-1.234-.987-1.234-.572 0-.99.407-.99 1.234v.553c0 .83.418 1.237.99 1.237.565 0 .987-.408.987-1.237m1.15-2.276h1.535c.82 0 1.316.55 1.316 1.292 0 .747-.501 1.289-1.321 1.289h-.865V10h-.665zm1.436 2.036c.463 0 .735-.272.735-.744s-.272-.741-.735-.741h-.774v1.485z" />
|
||||
<path fill-rule="evenodd"
|
||||
d="M4.893 0a.5.5 0 0 0-.353.146L.146 4.54A.5.5 0 0 0 0 4.893v6.214a.5.5 0 0 0 .146.353l4.394 4.394a.5.5 0 0 0 .353.146h6.214a.5.5 0 0 0 .353-.146l4.394-4.394a.5.5 0 0 0 .146-.353V4.893a.5.5 0 0 0-.146-.353L11.46.146A.5.5 0 0 0 11.107 0zM1 5.1 5.1 1h5.8L15 5.1v5.8L10.9 15H5.1L1 10.9z" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="/download/<%= ERB::Util.url_encode(item[:id]) %>" data-bs-toggle="tooltip" data-bs-title="<%= I18n.t('views.download_cert_tooltip') %>" class="icon-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-arrow-down" viewBox="0 0 16 16">
|
||||
<path d="M8.5 6.5a.5.5 0 0 0-1 0v3.793L6.354 9.146a.5.5 0 1 0-.708.708l2 2a.5.5 0 0 0 .708 0l2-2a.5.5 0 0 0-.708-.708L8.5 10.293z"/>
|
||||
<path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2M9.5 3A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<% end %>
|
||||
<a href="/shows/<%= ERB::Util.url_encode(item[:id]) %>" data-bs-toggle="tooltip" data-bs-title="<%= I18n.t('views.view_cert_tooltip') %>" class="icon-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
|
||||
fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8M1.173 8a13 13 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5s3.879 1.168 5.168 2.457A13 13 0 0 1 14.828 8q-.086.13-.195.288c-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5s-3.879-1.168-5.168-2.457A13 13 0 0 1 1.172 8z" />
|
||||
<path
|
||||
d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5M4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="/clients/<%= ERB::Util.url_encode(item[:id]) %>" data-bs-toggle="tooltip" data-bs-title="<%= I18n.t('views.view_clients_tooltip') %>" class="icon-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-funnel" viewBox="0 0 16 16">
|
||||
<path d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5zm1 .5v1.308l4.372 4.858A.5.5 0 0 1 7 8.5v5.306l2-.666V8.5a.5.5 0 0 1 .128-.334L13.5 3.308V2z"/>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="container text-centered">
|
||||
<nav>
|
||||
<ul class="pagination pagination-sm">
|
||||
<% @pages.each do |item| %>
|
||||
<li class="page-item
|
||||
<% if item[:is_current] %>
|
||||
active
|
||||
<% end %>
|
||||
">
|
||||
<a class="page-link" aria-current="page"
|
||||
<% unless item[:is_current] %>
|
||||
href="/?p=<%= item[:page] %>"
|
||||
<% end %>><%= item[:page] %></a>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade
|
||||
<% if @tab == 1 %>
|
||||
show active
|
||||
<% end %>
|
||||
" id="pills-clist" role="tabpanel" aria-labelledby="pills-clist-tab" tabindex="0">
|
||||
<% if @tab == 1 %>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<%= @cert_info[:name] %> id: <%= @cert_info[:id] %>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><%= I18n.t('views.cert_info_card_title') %></h5>
|
||||
<div class="card">
|
||||
<div class="card-body overflow-x-auto">
|
||||
<pre>
|
||||
<%= @cert_info[:common] %>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
<h5 class="card-title"><%= I18n.t('views.revoke_info_card_title') %></h5>
|
||||
<div class="card">
|
||||
<div class="card-body overflow-x-auto">
|
||||
<pre>
|
||||
<%= @cert_info[:revoke] %>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
<% if hasperms?('creator') %>
|
||||
<h5 class="card-title"><%= I18n.t('views.additional_actions') %></h5>
|
||||
<div class="card">
|
||||
<p><%= I18n.t('views.download_cert_tooltip') %>
|
||||
<a href="/download/<%= ERB::Util.url_encode(@cert_info[:id]) %>" data-bs-toggle="tooltip" data-bs-title="<%= I18n.t('views.download_cert_tooltip') %>" class="icon-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-arrow-down" viewBox="0 0 16 16">
|
||||
<path d="M8.5 6.5a.5.5 0 0 0-1 0v3.793L6.354 9.146a.5.5 0 1 0-.708.708l2 2a.5.5 0 0 0 .708 0l2-2a.5.5 0 0 0-.708-.708L8.5 10.293z"/>
|
||||
<path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2M9.5 3A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z"/>
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
Nothing
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="confirmModal" class="modal fade" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="confirmModalLabel"><%= I18n.t('views.modal_title_confirm') %></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p><%= I18n.t('views.modal_body_confirm_revoke') %></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" id="revokeButton" class="btn btn-danger"><%= I18n.t('views.revoke_button') %></button>
|
||||
<button type="button" id="cancelButton" class="btn btn-secondary" data-bs-dismiss="modal"><%= I18n.t('views.modal_btn_cancel') %></button>
|
||||
<input type="hidden" value="" id="modal-redirect-url"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
|
||||
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
|
||||
$('.revoke-cert').click(function(event) {
|
||||
event.preventDefault(); // Prevent the default action (navigation)
|
||||
var href = $(this).attr('href'); // Get the value of data-href
|
||||
if (href) {
|
||||
$('#modal-redirect-url').val(href);
|
||||
$('#confirmModal').modal('show'); // Show the modal
|
||||
}
|
||||
});
|
||||
|
||||
$('#cancelButton').click(function() {
|
||||
$('#confirmModal').modal('hide'); // Hide the modal when "Cancel" is clicked
|
||||
});
|
||||
|
||||
$('#revokeButton').click(function() {
|
||||
var redirectUrl = $('#modal-redirect-url').val(); // Get the saved URL
|
||||
if (redirectUrl) {
|
||||
window.location.href = redirectUrl; // Redirect to the saved URL
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@ -0,0 +1,371 @@
|
||||
<% if hasperms?('creator') %>
|
||||
<div class="container p-3">
|
||||
<p class="d-inline-flex gap-1">
|
||||
<button class="btn btn-outline-warning" type="button" data-bs-toggle="collapse" data-bs-target="#collapseForm" aria-expanded="false" aria-controls="collapseForm">
|
||||
<%= I18n.t('views.create_request_cert_button') %>
|
||||
</button>
|
||||
</p>
|
||||
<div class="collapse" id="collapseForm">
|
||||
<div class="card card-body">
|
||||
<form action="/addclient" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="server_domain" class="form-label"><%= I18n.t('views.server_access_label') %></label>
|
||||
<select class="form-select" aria-label="<%= I18n.t('views.server_access_label') %>" id="server_domain" name="server_domain" required>
|
||||
<% fst = true %>
|
||||
<% @list_servers_full.each do |item| %>
|
||||
<% if fst == true %>
|
||||
<% fst = false %>
|
||||
<option value="<%= item %>"selected><%= item %></option>
|
||||
<% else %>
|
||||
<option value="<%= item %>"><%= item %></option>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="client" class="form-label"><%= I18n.t('views.client_name_email_label') %></label>
|
||||
<input type="text" class="form-control" id="client" name="client" required placeholder="<%= I18n.t('views.client_placeholder') %>">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="validityDays" class="form-label"><%= I18n.t('views.validity_days_label') %></label>
|
||||
<input type="number" class="form-control" id="validityDays" name="validity_days" required value="365">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><%= I18n.t('views.request_cert_submit') %></button>
|
||||
<p class="mt-2"><%= I18n.t('views.request_cert_info') %></p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="container">
|
||||
<ul class="nav nav-pills mb-3" id="liclist-tab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link
|
||||
<% if @tab == 0 %>
|
||||
active
|
||||
<% end %>
|
||||
" id="pills-slist-tab" data-bs-toggle="pill" data-bs-target="#pills-slist"
|
||||
type="button" role="tab" aria-controls="pills-slist"
|
||||
<% if @tab == 0 %>
|
||||
aria-selected="true"
|
||||
<% else %>
|
||||
aria-selected="false"
|
||||
<% end %>
|
||||
>
|
||||
<%= I18n.t('views.list_clients_tab') %>
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link
|
||||
<% if @tab == 1 %>
|
||||
active
|
||||
<% end %>
|
||||
" id="pills-clist-tab" data-bs-toggle="pill" data-bs-target="#pills-clist"
|
||||
type="button" role="tab" aria-controls="pills-clist"
|
||||
<% if @tab == 1 %>
|
||||
aria-selected="true"
|
||||
<% else %>
|
||||
aria-selected="false" disabled
|
||||
<% end %>
|
||||
>
|
||||
<%= I18n.t('views.filtered_certs_tab') %>
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link
|
||||
<% if @tab == 2 %>
|
||||
active
|
||||
<% end %>
|
||||
" id="pills-ilist-tab" data-bs-toggle="pill" data-bs-target="#pills-ilist"
|
||||
type="button" role="tab" aria-controls="pills-ilist"
|
||||
<% if @tab == 2 %>
|
||||
aria-selected="true"
|
||||
<% else %>
|
||||
aria-selected="false" disabled
|
||||
<% end %>
|
||||
>
|
||||
<%= I18n.t('views.selected_cert_info_tab') %>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" id="liclist-tabContent">
|
||||
<div class="tab-pane fade
|
||||
<% if @tab == 0 %>
|
||||
show active
|
||||
<% end %>
|
||||
" id="pills-slist" role="tabpanel" aria-labelledby="pills-slist-tab"
|
||||
tabindex="0">
|
||||
<div class="container text-center">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"><%= I18n.t('views.status_header') %></th>
|
||||
<th scope="col"><%= I18n.t('views.id_header') %></th>
|
||||
<th scope="col"><%= I18n.t('views.date_header') %></th>
|
||||
<th scope="col"><%= I18n.t('views.revoke_date_header') %></th>
|
||||
<th scope="col"><%= I18n.t('views.info_header') %></th>
|
||||
<th scope="col"><%= I18n.t('views.actions_header') %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @list_clients.each do |item| %>
|
||||
<tr
|
||||
<% if item[:status] != 'V' %>
|
||||
class="table-danger"
|
||||
<% elsif item[:expired] %>
|
||||
class="table-warning"
|
||||
<% end %>
|
||||
>
|
||||
<td><%= item[:status] %></td>
|
||||
<td><%= item[:id] %></td>
|
||||
<td><%= item[:date] %></td>
|
||||
<td><%= item[:revoke_date] %></td>
|
||||
<td>CN=<%= item[:ui][:CN] %>, O=<%= item[:ui][:O] %></td>
|
||||
<td>
|
||||
<% if hasperms?('creator') %>
|
||||
<a href="/revoke/<%= ERB::Util.url_encode(item[:id]) %>" data-bs-toggle="tooltip" data-bs-title="<%= I18n.t('views.revoke_cert_tooltip') %>" class="icon-link revoke-cert">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
|
||||
fill="currentColor" class="bi bi-sign-stop" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M3.16 10.08c-.931 0-1.447-.493-1.494-1.132h.653c.065.346.396.583.891.583.524 0 .83-.246.83-.62 0-.303-.203-.467-.637-.572l-.656-.164c-.61-.147-.978-.51-.978-1.078 0-.706.597-1.184 1.444-1.184.853 0 1.386.475 1.436 1.087h-.645c-.064-.32-.352-.542-.797-.542-.472 0-.77.246-.77.6 0 .261.196.437.553.522l.654.161c.673.164 1.06.487 1.06 1.11 0 .736-.574 1.228-1.544 1.228Zm3.427-3.51V10h-.665V6.57H4.753V6h3.006v.568H6.587Z" />
|
||||
<path fill-rule="evenodd"
|
||||
d="M11.045 7.73v.544c0 1.131-.636 1.805-1.661 1.805-1.026 0-1.664-.674-1.664-1.805V7.73c0-1.136.638-1.807 1.664-1.807s1.66.674 1.66 1.807Zm-.674.547v-.553c0-.827-.422-1.234-.987-1.234-.572 0-.99.407-.99 1.234v.553c0 .83.418 1.237.99 1.237.565 0 .987-.408.987-1.237m1.15-2.276h1.535c.82 0 1.316.55 1.316 1.292 0 .747-.501 1.289-1.321 1.289h-.865V10h-.665zm1.436 2.036c.463 0 .735-.272.735-.744s-.272-.741-.735-.741h-.774v1.485z" />
|
||||
<path fill-rule="evenodd"
|
||||
d="M4.893 0a.5.5 0 0 0-.353.146L.146 4.54A.5.5 0 0 0 0 4.893v6.214a.5.5 0 0 0 .146.353l4.394 4.394a.5.5 0 0 0 .353.146h6.214a.5.5 0 0 0 .353-.146l4.394-4.394a.5.5 0 0 0 .146-.353V4.893a.5.5 0 0 0-.146-.353L11.46.146A.5.5 0 0 0 11.107 0zM1 5.1 5.1 1h5.8L15 5.1v5.8L10.9 15H5.1L1 10.9z" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="/download/<%= ERB::Util.url_encode(item[:id]) %>" data-bs-toggle="tooltip" data-bs-title="<%= I18n.t('views.download_cert_tooltip') %>" class="icon-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-arrow-down" viewBox="0 0 16 16">
|
||||
<path d="M8.5 6.5a.5.5 0 0 0-1 0v3.793L6.354 9.146a.5.5 0 1 0-.708.708l2 2a.5.5 0 0 0 .708 0l2-2a.5.5 0 0 0-.708-.708L8.5 10.293z"/>
|
||||
<path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2M9.5 3A1.5.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<% end %>
|
||||
<a href="/showc/<%= ERB::Util.url_encode(item[:id]) %>" data-bs-toggle="tooltip" data-bs-title="<%= I18n.t('views.view_cert_tooltip') %>" class="icon-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
|
||||
fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8M1.173 8a13 13 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5s3.879 1.168 5.168 2.457A13 13 0 0 1 14.828 8q-.086.13-.195.288c-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5s-3.879-1.168-5.168-2.457A13 13 0 0 1 1.172 8z" />
|
||||
<path
|
||||
d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5M4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="/clients/<%= ERB::Util.url_encode(item[:id]) %>" data-bs-toggle="tooltip" data-bs-title="<%= I18n.t('views.view_clients_tooltip') %>" class="icon-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-funnel" viewBox="0 0 16 16">
|
||||
<path d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5zm1 .5v1.308l4.372 4.858A.5.5 0 0 1 7 8.5v5.306l2-.666V8.5a.5.5 0 0 1 .128-.334L13.5 3.308V2z"/>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="container text-centered">
|
||||
<nav>
|
||||
<ul class="pagination pagination-sm">
|
||||
<% @pages.each do |item| %>
|
||||
<li class="page-item
|
||||
<% if item[:is_current] %>
|
||||
active
|
||||
<% end %>
|
||||
">
|
||||
<a class="page-link" aria-current="page"
|
||||
<% unless item[:is_current] %>
|
||||
href="/clients?p=<%= item[:page] %>"
|
||||
<% end %>><%= item[:page] %></a>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade
|
||||
<% if @tab == 1 %>
|
||||
show active
|
||||
<% end %>
|
||||
" id="pills-clist" role="tabpanel" aria-labelledby="pills-clist-tab" tabindex="0">
|
||||
<% if @tab == 1 %>
|
||||
<div class="container text-center">
|
||||
<p><%= @server_name %>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"><%= I18n.t('views.status_header') %></th>
|
||||
<th scope="col"><%= I18n.t('views.id_header') %></th>
|
||||
<th scope="col"><%= I18n.t('views.date_header') %></th>
|
||||
<th scope="col"><%= I18n.t('views.revoke_date_header') %></th>
|
||||
<th scope="col"><%= I18n.t('views.info_header') %></th>
|
||||
<th scope="col"><%= I18n.t('views.actions_header') %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @list_clients_short.each do |item| %>
|
||||
<tr
|
||||
<% if item[:status] != 'V' %>
|
||||
class="table-danger"
|
||||
<% elsif item[:expired] %>
|
||||
class="table-warning"
|
||||
<% end %>
|
||||
>
|
||||
<td><%= item[:status] %></td>
|
||||
<td><%= item[:id] %></td>
|
||||
<td><%= item[:date] %></td>
|
||||
<td><%= item[:revoke_date] %></td>
|
||||
<td>CN=<%= item[:ui][:CN] %>, O=<%= item[:ui][:O] %></td>
|
||||
<td>
|
||||
<% if hasperms?('creator') %>
|
||||
<a href="/revoke/<%= ERB::Util.url_encode(item[:id]) %>" data-bs-toggle="tooltip" data-bs-title="<%= I18n.t('views.revoke_cert_tooltip') %>" class="icon-link revoke-cert">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
|
||||
fill="currentColor" class="bi bi-sign-stop" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M3.16 10.08c-.931 0-1.447-.493-1.494-1.132h.653c.065.346.396.583.891.583.524 0 .83-.246.83-.62 0-.303-.203-.467-.637-.572l-.656-.164c-.61-.147-.978-.51-.978-1.078 0-.706.597-1.184 1.444-1.184.853 0 1.386.475 1.436 1.087h-.645c-.064-.32-.352-.542-.797-.542-.472 0-.77.246-.77.6 0 .261.196.437.553.522l.654.161c.673.164 1.06.487 1.06 1.11 0 .736-.574 1.228-1.544 1.228Zm3.427-3.51V10h-.665V6.57H4.753V6h3.006v.568H6.587Z" />
|
||||
<path fill-rule="evenodd"
|
||||
d="M11.045 7.73v.544c0 1.131-.636 1.805-1.661 1.805-1.026 0-1.664-.674-1.664-1.805V7.73c0-1.136.638-1.807 1.664-1.807s1.66.674 1.66 1.807Zm-.674.547v-.553c0-.827-.422-1.234-.987-1.234-.572 0-.99.407-.99 1.234v.553c0 .83.418 1.237.99 1.237.565 0 .987-.408.987-1.237m1.15-2.276h1.535c.82 0 1.316.55 1.316 1.292 0 .747-.501 1.289-1.321 1.289h-.865V10h-.665zm1.436 2.036c.463 0 .735-.272.735-.744s-.272-.741-.735-.741h-.774v1.485z" />
|
||||
<path fill-rule="evenodd"
|
||||
d="M4.893 0a.5.5 0 0 0-.353.146L.146 4.54A.5.5 0 0 0 0 4.893v6.214a.5.5 0 0 0 .146.353l4.394 4.394a.5.5 0 0 0 .353.146h6.214a.5.5 0 0 0 .353-.146l4.394-4.394a.5.5 0 0 0 .146-.353V4.893a.5.5 0 0 0-.146-.353L11.46.146A.5.5 0 0 0 11.107 0zM1 5.1 5.1 1h5.8L15 5.1v5.8L10.9 15H5.1L1 10.9z" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="/download/<%= ERB::Util.url_encode(item[:id]) %>" data-bs-toggle="tooltip" data-bs-title="<%= I18n.t('views.download_cert_tooltip') %>" class="icon-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-arrow-down" viewBox="0 0 16 16">
|
||||
<path d="M8.5 6.5a.5.5 0 0 0-1 0v3.793L6.354 9.146a.5.5 0 1 0-.708.708l2 2a.5.5 0 0 0 .708 0l2-2a.5.5 0 0 0-.708-.708L8.5 10.293z"/>
|
||||
<path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2M9.5 3A1.5.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<% end %>
|
||||
<a href="/showc/<%= ERB::Util.url_encode(item[:id]) %>" data-bs-toggle="tooltip" data-bs-title="<%= I18n.t('views.view_cert_tooltip') %>" class="icon-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"
|
||||
fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8M1.173 8a13 13 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5s3.879 1.168 5.168 2.457A13 13 0 0 1 14.828 8q-.086.13-.195.288c-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5s-3.879-1.168-5.168-2.457A13 13 0 0 1 1.172 8z" />
|
||||
<path
|
||||
d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5M4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0" />
|
||||
</svg>
|
||||
</a>
|
||||
<a href="/clients/<%= ERB::Util.url_encode(item[:id]) %>" data-bs-toggle="tooltip" data-bs-title="<%= I18n.t('views.view_clients_tooltip') %>" class="icon-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-funnel" viewBox="0 0 16 16">
|
||||
<path d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5zm1 .5v1.308l4.372 4.858A.5.5 0 0 1 7 8.5v5.306l2-.666V8.5a.5.5 0 0 1 .128-.334L13.5 3.308V2z"/>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="container text-centered">
|
||||
<nav>
|
||||
<ul class="pagination pagination-sm">
|
||||
<% @pages_short.each do |item| %>
|
||||
<li class="page-item
|
||||
<% if item[:is_current] %>
|
||||
active
|
||||
<% end %>
|
||||
">
|
||||
<a class="page-link" aria-current="page"
|
||||
<% unless item[:is_current] %>
|
||||
href="/clients/<%= @id %>?fp=<%= item[:page] %>"
|
||||
<% end %>><%= item[:page] %></a>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
Nothing
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="tab-pane fade
|
||||
<% if @tab == 2 %>
|
||||
show active
|
||||
<% end %>
|
||||
" id="pills-ilist" role="tabpanel" aria-labelledby="pills-ilist-tab" tabindex="0">
|
||||
<% if @tab == 2 %>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<%= @cert_info[:name] %> id: <%= @cert_info[:id] %>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><%= I18n.t('views.cert_info_card_title') %></h5>
|
||||
<div class="card">
|
||||
<div class="card-body overflow-x-auto">
|
||||
<pre>
|
||||
<%= @cert_info[:common] %>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
<h5 class="card-title"><%= I18n.t('views.revoke_info_card_title') %></h5>
|
||||
<div class="card">
|
||||
<div class="card-body overflow-x-auto">
|
||||
<pre>
|
||||
<%= @cert_info[:revoke] %>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
<% if hasperms?('creator') %>
|
||||
<h5 class="card-title"><%= I18n.t('views.additional_actions') %></h5>
|
||||
<div class="card">
|
||||
<p><%= I18n.t('views.download_cert_tooltip') %>
|
||||
<a href="/download/<%= ERB::Util.url_encode(@cert_info[:id]) %>" data-bs-toggle="tooltip" data-bs-title="<%= I18n.t('views.download_cert_tooltip') %>" class="icon-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-file-earmark-arrow-down" viewBox="0 0 16 16">
|
||||
<path d="M8.5 6.5a.5.5 0 0 0-1 0v3.793L6.354 9.146a.5.5 0 1 0-.708.708l2 2a.5.5 0 0 0 .708 0l2-2a.5.5 0 0 0-.708-.708L8.5 10.293z"/>
|
||||
<path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2M9.5 3A1.5.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z"/>
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
Nothing
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="confirmModal" class="modal fade" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="confirmModalLabel"><%= I18n.t('views.modal_title_confirm') %></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p><%= I18n.t('views.modal_body_confirm_revoke') %></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" id="revokeButton" class="btn btn-danger"><%= I18n.t('views.revoke_button') %></button>
|
||||
<button type="button" id="cancelButton" class="btn btn-secondary" data-bs-dismiss="modal"><%= I18n.t('views.modal_btn_cancel') %></button>
|
||||
<input type="hidden" value="" id="modal-redirect-url"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
|
||||
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
|
||||
$('.revoke-cert').click(function(event) {
|
||||
event.preventDefault(); // Prevent the default action (navigation)
|
||||
var href = $(this).attr('href'); // Get the value of data-href
|
||||
if (href) {
|
||||
$('#modal-redirect-url').val(href);
|
||||
$('#confirmModal').modal('show'); // Show the modal
|
||||
}
|
||||
});
|
||||
|
||||
$('#cancelButton').click(function() {
|
||||
$('#confirmModal').modal('hide'); // Hide the modal when "Cancel" is clicked
|
||||
});
|
||||
|
||||
$('#revokeButton').click(function() {
|
||||
var redirectUrl = $('#modal-redirect-url').val(); // Get the saved URL
|
||||
if (redirectUrl) {
|
||||
window.location.href = redirectUrl; // Redirect to the saved URL
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@ -0,0 +1,51 @@
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<%= I18n.t('views.authorize') %>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<% unless @error.nil? %>
|
||||
<div class="alert alert-danger" role="alert"><%= @error %></div>
|
||||
<% end %>
|
||||
<div class="container">
|
||||
<form id="login-form" action="/login" method="POST" class="d-flex flex-column align-items-center">
|
||||
<div class="mb-3 w-100">
|
||||
<label for="login" class="form-label"><%= I18n.t('views.user_name') %></label>
|
||||
<input type="text" class="form-control w-100 required" id="login" name="login"
|
||||
placeholder="<%= I18n.t('views.enter_login') %>">
|
||||
</div>
|
||||
<div class="mb-3 w-100">
|
||||
<label for="password" class="form-label"><%= I18n.t('views.password') %></label>
|
||||
<input type="password" class="form-control w-100 required" id="password" name="password"
|
||||
placeholder="<%= I18n.t('views.enter_password') %>">
|
||||
</div>
|
||||
<div class="d-flex justify-content-center w-100">
|
||||
<button type="button" class="btn btn-primary" id="submit-btn"><%= I18n.t('views.authorize') %></button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('#submit-btn').click(function () {
|
||||
var isValid = true;
|
||||
$('.required').each(function() {
|
||||
if ($(this).val() === '') {
|
||||
isValid = false;
|
||||
$(this).addClass('is-invalid');
|
||||
} else {
|
||||
$(this).removeClass('is-invalid');
|
||||
}
|
||||
});
|
||||
|
||||
if (isValid) {
|
||||
$('#login-form').submit();
|
||||
} else {
|
||||
alert('<%= I18n.t('views.please_enter_all_required_fields') %>');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,10 @@
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<%= @reason %>!!!
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-danger text-center" role="alert"><%= @info_descr %></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,23 @@
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<%= @cert_info[:name] %>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title"><%= I18n.t('views.certificate_information') %></h5>
|
||||
<div class="card">
|
||||
<div class="card-body overflow-x-auto">
|
||||
<pre>
|
||||
<%= @cert_info[:common] %>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
<h5 class="card-title"><%= I18n.t('views.revoke_information') %></h5>
|
||||
<div class="card">
|
||||
<div class="card-body overflow-x-auto">
|
||||
<pre>
|
||||
<%= @cert_info[:revoke] %>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,240 @@
|
||||
<div class="container p-3">
|
||||
<p class="d-inline-flex gap-1">
|
||||
<button class="btn btn-outline-success" type="button" data-bs-toggle="collapse" data-bs-target="#collapseFormUser" aria-expanded="false" aria-controls="collapseFormUser">
|
||||
<%= I18n.t('views.create_user_button') %>
|
||||
</button>
|
||||
</p>
|
||||
<div class="collapse" id="collapseFormUser">
|
||||
<div class="card card-body">
|
||||
<form action="/adduser" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="login" class="form-label"><%= I18n.t('views.login_label') %></label>
|
||||
<input type="text" class="form-control" id="login" name="login" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label"><%= I18n.t('views.password_label') %></label>
|
||||
<input type="password" class="form-control" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label"><%= I18n.t('views.email_label') %></label>
|
||||
<input type="email" class="form-control" id="email" name="email">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="role" class="form-label"><%= I18n.t('views.role_label') %></label>
|
||||
<select class="form-select" id="role" name="role">
|
||||
<option value="0" selected><%= I18n.t('views.role_user') %></option>
|
||||
<option value="1"><%= I18n.t('views.role_creator') %></option>
|
||||
<option value="2"><%= I18n.t('views.role_admin') %></option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><%= I18n.t('views.submit_create_user') %></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<ul class="nav nav-pills mb-3" id="userlist-tab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link
|
||||
<% if @tab == 0 %>
|
||||
active
|
||||
<% end %>
|
||||
" id="pills-ulist-tab" data-bs-toggle="pill" data-bs-target="#pills-ulist" type="button" role="tab" aria-controls="pills-ulist"
|
||||
<% if @tab == 0 %>
|
||||
aria-selected="true"
|
||||
<% else %>
|
||||
aria-selected="false"
|
||||
<% end %>
|
||||
>
|
||||
<%= I18n.t('views.user_list_tab') %>
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link
|
||||
<% if @tab == 1 %>
|
||||
active
|
||||
<% end %>
|
||||
" id="pills-edit-tab" data-bs-toggle="pill" data-bs-target="#pills-edit" type="button" role="tab" aria-controls="pills-edit"
|
||||
<% if @tab == 1 %>
|
||||
aria-selected="true"
|
||||
<% else %>
|
||||
aria-selected="false" disabled
|
||||
<% end %>
|
||||
>
|
||||
<%= I18n.t('views.edit_user_tab') %>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" id="userlist-tabContent">
|
||||
<div class="tab-pane fade
|
||||
<% if @tab == 0 %>
|
||||
show active
|
||||
<% end %>
|
||||
" id="pills-ulist" role="tabpanel" aria-labelledby="pills-ulist-tab" tabindex="0">
|
||||
<div class="container text-center">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"><%= I18n.t('views.user_name_header') %></th>
|
||||
<th scope="col"><%= I18n.t('views.role_header') %></th>
|
||||
<th scope="col"><%= I18n.t('views.email_header') %></th>
|
||||
<th scope="col"><%= I18n.t('views.created_at_header') %></th>
|
||||
<th scope="col"><%= I18n.t('views.actions_header') %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @users.each do |user| %>
|
||||
<tr>
|
||||
<td><%= user[:login] %></td>
|
||||
<td>
|
||||
<%= case user[:role]
|
||||
when 0 then I18n.t('views.role_user')
|
||||
when 1 then I18n.t('views.role_creator')
|
||||
when 2 then I18n.t('views.role_admin')
|
||||
else I18n.t('views.role_unknown')
|
||||
end %>
|
||||
</td>
|
||||
<td>
|
||||
<% if user[:email].nil? || user[:email].strip == '' %>
|
||||
<%= I18n.t('views.no_email') %>
|
||||
<% else %>
|
||||
<%= user[:email] %>
|
||||
<% end %>
|
||||
</td>
|
||||
<td><%= user[:create_at].strftime('%Y-%m-%d') %></td>
|
||||
<td>
|
||||
<a href="/edituser/<%= ERB::Util.url_encode(user[:id]) %>" data-bs-toggle="tooltip" data-bs-title="<%= I18n.t('views.edit_user_tab') %>" class="icon-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pen" viewBox="0 0 16 16">
|
||||
<path d="m13.498.795.149-.149a1.207 1.207 0 1 1 1.707 1.708l-.149.148a1.5 1.5 0 0 1-.059 2.059L4.854 14.854a.5.5 0 0 1-.233.131l-4 1a.5.5 0 0 1-.606-.606l1-4a.5.5 0 0 1 .131-.232l9.642-9.642a.5.5 0 0 0-.642.056L6.854 4.854a.5.5 0 1 1-.708-.708L9.44.854A1.5 1.5 0 0 1 11.5.796a1.5 1.5 0 0 1 1.998-.001m-.644.766a.5.5 0 0 0-.707 0L1.95 11.756l-.764 3.057 3.057-.764L14.44 3.854a.5.5 0 0 0 0-.708z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a href="/deleteuser/<%= ERB::Util.url_encode(user[:id]) %>" data-bs-toggle="tooltip" data-bs-title="<%= I18n.t('views.delete_user') %>" class="icon-link revoke-cert">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash3" viewBox="0 0 16 16">
|
||||
<path d="M6.5 1h3a.5.5 0 0 1 .5.5v1H6v-1a.5.5 0 0 1 .5-.5M11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3A1.5 1.5 0 0 0 5 1.5v1H1.5a.5.5 0 0 0 0 1h.538l.853 10.66A2 2 0 0 0 4.885 16h6.23a2 2 0 0 0 1.994-1.84l.853-10.66h.538a.5.5 0 0 0 0-1zm1.958 1-.846 10.58a1 1 0 0 1-.997.92h-6.23a1 1 0 0 1-.997-.92L3.042 3.5zm-7.487 1a.5.5 0 0 1 .528.47l.5 8.5a.5.5 0 0 1-.998.06L5 5.03a.5.5 0 0 1 .47-.53Zm5.058 0a.5.5 0 0 1 .47.53l-.5 8.5a.5.5 0 1 1-.998-.06l.5-8.5a.5.5 0 0 1 .528-.47M8 4.5a.5.5 0 0 1 .5.5v8.5a.5.5 0 0 1-1 0V5a.5.5 0 0 1 .5-.5"/>
|
||||
</svg>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="container text-centered">
|
||||
<nav>
|
||||
<ul class="pagination pagination-sm">
|
||||
<% @pages.each do |item| %>
|
||||
<li class="page-item
|
||||
<% if item[:is_current] %>
|
||||
active
|
||||
<% end %>"
|
||||
>
|
||||
<a class="page-link" aria-current="page" href="/users?p=<%= item[:page] %>">
|
||||
<%= item[:page] %>
|
||||
</a>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade
|
||||
<% if @tab == 1 %>
|
||||
show active
|
||||
<% end %>
|
||||
" id="pills-edit" role="tabpanel" aria-labelledby="pills-edit-tab" tabindex="0">
|
||||
<% if @tab == 1 %>
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<%= I18n.t('views.card_header_edit_user') %>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form action="/edituser/<%= @selected_user[:id] %>" method="post" id="editUserForm">
|
||||
<input type="hidden" name="id" value="<%= @selected_user[:id] %>">
|
||||
<div class="mb-3">
|
||||
<label for="login" class="form-label"><%= I18n.t('views.login_label') %></label>
|
||||
<input type="text" class="form-control" id="login" name="login" value="<%= @selected_user[:login] %>" readonly>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="password" class="form-label"><%= I18n.t('views.password_label') %></label>
|
||||
<input type="password" class="form-control" id="password" name="password">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label"><%= I18n.t('views.email_label') %></label>
|
||||
<input type="email" class="form-control" id="email" name="email" value="<%= @selected_user[:email] %>">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="role" class="form-label"><%= I18n.t('views.role_label') %></label>
|
||||
<select class="form-select" id="role" name="role">
|
||||
<% if @selected_user[:role] == 0 %>
|
||||
<option value="0" selected><%= I18n.t('views.role_user') %></option>
|
||||
<% else %>
|
||||
<option value="0"><%= I18n.t('views.role_user') %></option>
|
||||
<% end %>
|
||||
<% if @selected_user[:role] == 1 %>
|
||||
<option value="1" selected><%= I18n.t('views.role_creator') %></option>
|
||||
<% else %>
|
||||
<option value="1"><%= I18n.t('views.role_creator') %></option>
|
||||
<% end %>
|
||||
<% if @selected_user[:role] == 2 %>
|
||||
<option value="2" selected><%= I18n.t('views.role_admin') %></option>
|
||||
<% else %>
|
||||
<option value="2"><%= I18n.t('views.role_admin') %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><%= I18n.t('views.submit_update_user') %></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
Nothing
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="confirmModal" class="modal fade" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="confirmModalLabel"><%= I18n.t('views.modal_title_confirm') %></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p><%= I18n.t('views.modal_body_confirm') %></p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" id="revokeButton" class="btn btn-danger"><%= I18n.t('views.modal_btn_delete') %></button>
|
||||
<button type="button" id="cancelButton" class="btn btn-secondary" data-bs-dismiss="modal"><%= I18n.t('views.modal_btn_cancel') %></button>
|
||||
<input type="hidden" value="" id="modal-redirect-url"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
|
||||
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
|
||||
$('.revoke-cert').click(function(event) {
|
||||
event.preventDefault(); // Prevent the default action (navigation)
|
||||
var href = $(this).attr('href'); // Get the value of data-href
|
||||
if (href) {
|
||||
$('#modal-redirect-url').val(href);
|
||||
$('#confirmModal').modal('show'); // Show the modal
|
||||
}
|
||||
});
|
||||
|
||||
$('#cancelButton').click(function() {
|
||||
$('#confirmModal').modal('hide'); // Hide the modal when "Cancel" is clicked
|
||||
});
|
||||
|
||||
$('#revokeButton').click(function() {
|
||||
var redirectUrl = $('#modal-redirect-url').val(); // Get the saved URL
|
||||
if (redirectUrl) {
|
||||
window.location.href = redirectUrl; // Redirect to the saved URL
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
Loading…
Reference in new issue