SuperAdmin
This commit is contained in:
@@ -13,28 +13,30 @@ defineProps({
|
||||
|
||||
<template>
|
||||
<header
|
||||
class="flex items-center px-8 py-4 bg-white border-b border-slate-100"
|
||||
class="sticky top-0 z-10 bg-n-background border-b border-n-weak"
|
||||
role="banner"
|
||||
>
|
||||
<a :href="responseSourcePath" class="text-woot-500 hover:underline mr-4">
|
||||
{{ 'Back' }}
|
||||
</a>
|
||||
<div
|
||||
class="border border-solid border-slate-100 text-slate-700 mr-4 p-2 rounded-full"
|
||||
>
|
||||
<svg width="24" height="24"><use xlink:href="#icon-mist-fill" /></svg>
|
||||
</div>
|
||||
<div class="flex flex-col h-14 justify-center">
|
||||
<h1 id="page-title" class="text-base font-medium text-slate-900">
|
||||
{{ 'Robin AI playground' }}
|
||||
</h1>
|
||||
<p class="text-sm text-slate-600">
|
||||
{{ 'Chat with the source' }}
|
||||
<span class="font-medium">
|
||||
{{ responseSourceName }}
|
||||
</span>
|
||||
{{ 'and evaluate it’s efficiency.' }}
|
||||
</p>
|
||||
<div class="flex items-center h-16 mx-auto max-w-[60rem]">
|
||||
<a :href="responseSourcePath" class="text-woot-500 hover:underline mr-4">
|
||||
Back
|
||||
</a>
|
||||
<div
|
||||
class="border border-solid border-n-weak text-n-slate-11 mr-4 p-2 rounded-full"
|
||||
>
|
||||
<svg width="24" height="24"><use xlink:href="#icon-mist-fill" /></svg>
|
||||
</div>
|
||||
<div class="flex flex-col justify-center">
|
||||
<h1 id="page-title" class="text-lg font-medium text-n-slate-12">
|
||||
Robin AI playground
|
||||
</h1>
|
||||
<p class="text-sm text-n-slate-11">
|
||||
Chat with the source
|
||||
<span class="font-medium text-n-slate-12">
|
||||
{{ responseSourceName }}
|
||||
</span>
|
||||
and evaluate it's efficiency.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
@@ -38,38 +38,77 @@ const { accountsCount, usersCount, inboxesCount, conversationsCount } =
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full h-full">
|
||||
<header class="main-content__header" role="banner">
|
||||
<h1 id="page-title" class="main-content__page-title">
|
||||
{{ 'Admin Dashboard' }}
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
<section class="main-content__body main-content__body--flush">
|
||||
<div class="report--list">
|
||||
<div class="report-card">
|
||||
<div class="metric">{{ accountsCount }}</div>
|
||||
<div>{{ 'Accounts' }}</div>
|
||||
</div>
|
||||
<div class="report-card">
|
||||
<div class="metric">{{ usersCount }}</div>
|
||||
<div>{{ 'Users' }}</div>
|
||||
</div>
|
||||
<div class="report-card">
|
||||
<div class="metric">{{ inboxesCount }}</div>
|
||||
<div>{{ 'Inboxes' }}</div>
|
||||
</div>
|
||||
<div class="report-card">
|
||||
<div class="metric">{{ conversationsCount }}</div>
|
||||
<div>{{ 'Conversations' }}</div>
|
||||
<section class="flex flex-col w-full h-full">
|
||||
<header
|
||||
class="sticky top-0 z-10 bg-n-background border-b border-n-weak"
|
||||
role="banner"
|
||||
>
|
||||
<div
|
||||
class="flex items-center justify-between w-full h-20 mx-auto max-w-[60rem]"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="border border-n-weak p-2 rounded-full text-n-slate-11"
|
||||
>
|
||||
<svg width="24" height="24">
|
||||
<use xlink:href="#icon-dashboard-line" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex flex-col justify-center">
|
||||
<h1 id="page-title" class="text-base font-medium tracking-tight text-n-slate-12">
|
||||
Admin Dashboard
|
||||
</h1>
|
||||
<p class="text-xs text-n-slate-11 m-0">
|
||||
Accounts, users, and conversation metrics
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- eslint-disable vue/no-static-inline-styles -->
|
||||
<BarChart
|
||||
class="p-8 w-full"
|
||||
:collection="chartData"
|
||||
style="max-height: 500px"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="flex-1 px-6 overflow-y-auto">
|
||||
<div class="w-full mx-auto max-w-[60rem] py-4">
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
|
||||
<div
|
||||
class="p-4 rounded-xl bg-n-solid-2 outline outline-1 outline-n-container shadow-sm"
|
||||
>
|
||||
<div class="text-3xl font-semibold text-n-slate-12">
|
||||
{{ accountsCount }}
|
||||
</div>
|
||||
<div class="text-sm text-n-slate-11">Accounts</div>
|
||||
</div>
|
||||
<div
|
||||
class="p-4 rounded-xl bg-n-solid-2 outline outline-1 outline-n-container shadow-sm"
|
||||
>
|
||||
<div class="text-3xl font-semibold text-n-slate-12">
|
||||
{{ usersCount }}
|
||||
</div>
|
||||
<div class="text-sm text-n-slate-11">Users</div>
|
||||
</div>
|
||||
<div
|
||||
class="p-4 rounded-xl bg-n-solid-2 outline outline-1 outline-n-container shadow-sm"
|
||||
>
|
||||
<div class="text-3xl font-semibold text-n-slate-12">
|
||||
{{ inboxesCount }}
|
||||
</div>
|
||||
<div class="text-sm text-n-slate-11">Inboxes</div>
|
||||
</div>
|
||||
<div
|
||||
class="p-4 rounded-xl bg-n-solid-2 outline outline-1 outline-n-container shadow-sm"
|
||||
>
|
||||
<div class="text-3xl font-semibold text-n-slate-12">
|
||||
{{ conversationsCount }}
|
||||
</div>
|
||||
<div class="text-sm text-n-slate-11">Conversations</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="p-6 rounded-xl bg-n-solid-2 outline outline-1 outline-n-container shadow-sm"
|
||||
>
|
||||
<BarChart class="w-full max-h-[500px]" :collection="chartData" />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -99,37 +99,41 @@ export default {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="flex flex-col w-full h-full bg-slate-25">
|
||||
<section class="flex flex-col w-full h-full bg-n-background">
|
||||
<PlaygroundHeader
|
||||
:response-source-name="componentData.responseSourceName"
|
||||
:response-source-path="componentData.responseSourcePath"
|
||||
/>
|
||||
<div class="flex-1 px-8 py-4 overflow-auto">
|
||||
<div
|
||||
v-for="message in messages"
|
||||
:id="`message-${message.id}`"
|
||||
:key="message.id"
|
||||
>
|
||||
<UserMessage
|
||||
v-if="message.type === 'User'"
|
||||
:message="formatMessage(message.content)"
|
||||
/>
|
||||
<BotMessage v-else :message="formatMessage(message.content)" />
|
||||
<div class="flex-1 px-6 py-4 overflow-auto">
|
||||
<div class="w-full mx-auto max-w-[60rem]">
|
||||
<div
|
||||
v-for="message in messages"
|
||||
:id="`message-${message.id}`"
|
||||
:key="message.id"
|
||||
>
|
||||
<UserMessage
|
||||
v-if="message.type === 'User'"
|
||||
:message="formatMessage(message.content)"
|
||||
/>
|
||||
<BotMessage v-else :message="formatMessage(message.content)" />
|
||||
</div>
|
||||
<TypingIndicator v-if="isWaiting" />
|
||||
</div>
|
||||
<TypingIndicator v-if="isWaiting" />
|
||||
</div>
|
||||
<div class="w-full px-8 py-6">
|
||||
<textarea
|
||||
ref="messageInput"
|
||||
v-model="messageContent"
|
||||
:rows="4"
|
||||
class="resize-none block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border !outline-2 border-slate-100 focus:ring-woot-500 focus:border-woot-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-woot-500 dark:focus:border-woot-500"
|
||||
placeholder="Type a message... [CMD/CTRL + Enter to send]"
|
||||
autofocus
|
||||
autocomplete="off"
|
||||
@keydown.meta.enter="onMessageSend"
|
||||
@keydown.ctrl.enter="onMessageSend"
|
||||
/>
|
||||
<div class="sticky bottom-0 z-10 px-6 py-6 bg-n-background">
|
||||
<div class="w-full mx-auto max-w-[60rem]">
|
||||
<textarea
|
||||
ref="messageInput"
|
||||
v-model="messageContent"
|
||||
:rows="4"
|
||||
class="resize-none block p-3 w-full text-sm text-n-slate-12 bg-n-solid-2 rounded-xl border border-n-weak focus:ring-2 focus:ring-woot-500 focus:border-woot-500 outline-none"
|
||||
placeholder="Type a message... [CMD/CTRL + Enter to send]"
|
||||
autofocus
|
||||
autocomplete="off"
|
||||
@keydown.meta.enter="onMessageSend"
|
||||
@keydown.ctrl.enter="onMessageSend"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -16,11 +16,10 @@ that displays all possible records to associate with.
|
||||
[1]: http://www.rubydoc.info/gems/administrate/Administrate/Field/BelongsTo
|
||||
%>
|
||||
|
||||
<div class="field-unit__label">
|
||||
<%= f.label field.permitted_attribute %>
|
||||
</div>
|
||||
<div class="field-unit__field">
|
||||
<div class="flex flex-col gap-1">
|
||||
<%= f.label field.permitted_attribute, class: "text-sm font-medium text-n-slate-11" %>
|
||||
<%= f.select(field.permitted_attribute,
|
||||
options_for_select(field.associated_resource_options, field.selected_option),
|
||||
include_blank: field.include_blank_option) %>
|
||||
{ include_blank: field.include_blank_option },
|
||||
{ class: "h-9 px-3 min-w-[200px] text-sm text-n-slate-12 bg-n-solid-2 border border-n-weak rounded-lg focus:ring-2 focus:ring-woot-500 focus:border-woot-500 outline-none" }) %>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="flex flex-col gap-1 field-unit--belongs-to-search">
|
||||
<%= f.label field.permitted_attribute, class: "text-sm font-medium text-n-slate-11" %>
|
||||
<%= f.select(field.permitted_attribute,
|
||||
nil,
|
||||
{ include_blank: true },
|
||||
class: "h-9 px-3 min-w-[200px] text-sm text-n-slate-12 bg-n-solid-2 border border-n-weak rounded-lg focus:ring-2 focus:ring-woot-500 focus:border-woot-500 outline-none selectize-input",
|
||||
'data-url': polymorphic_url([namespace, field.associated_class], format: :json)) do %>
|
||||
<%= options_for_select(field.associated_resource_options, field.selected_option) %>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<%= f.check_box(
|
||||
field.attribute,
|
||||
class: "h-4 w-4 text-woot-500 bg-n-solid-2 border border-n-weak rounded focus:ring-2 focus:ring-woot-500 focus:ring-offset-0"
|
||||
) %>
|
||||
<%= f.label field.attribute, class: "text-sm font-medium text-n-slate-12" %>
|
||||
</div>
|
||||
@@ -0,0 +1,8 @@
|
||||
<div class="flex flex-col gap-1">
|
||||
<%= f.label field.attribute, class: "text-sm font-medium text-n-slate-11" %>
|
||||
<%= f.email_field(
|
||||
field.attribute,
|
||||
value: field.data,
|
||||
class: "h-9 px-3 text-sm text-n-slate-12 bg-n-solid-2 border border-n-weak rounded-lg focus:ring-2 focus:ring-woot-500 focus:border-woot-500 outline-none"
|
||||
) %>
|
||||
</div>
|
||||
@@ -0,0 +1,8 @@
|
||||
<div class="flex flex-col gap-1">
|
||||
<%= f.label field.attribute, class: "text-sm font-medium text-n-slate-11" %>
|
||||
<%= f.number_field(
|
||||
field.attribute,
|
||||
value: field.data,
|
||||
class: "h-9 px-3 text-sm text-n-slate-12 bg-n-solid-2 border border-n-weak rounded-lg focus:ring-2 focus:ring-woot-500 focus:border-woot-500 outline-none"
|
||||
) %>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="flex flex-col gap-1">
|
||||
<%= f.label field.attribute, class: "text-sm font-medium text-n-slate-11" %>
|
||||
<%= f.password_field(
|
||||
field.attribute,
|
||||
class: "h-9 px-3 text-sm text-n-slate-12 bg-n-solid-2 border border-n-weak rounded-lg focus:ring-2 focus:ring-woot-500 focus:border-woot-500 outline-none"
|
||||
) %>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
<div class="flex flex-col gap-1">
|
||||
<%= f.label field.attribute, class: "text-sm font-medium text-n-slate-11" %>
|
||||
<%= f.select(
|
||||
field.attribute,
|
||||
options_for_select(field.selectable_options, field.data),
|
||||
{ include_blank: field.include_blank_option },
|
||||
{ class: "h-9 px-3 min-w-[120px] text-sm text-n-slate-12 bg-n-solid-2 border border-n-weak rounded-lg focus:ring-2 focus:ring-woot-500 focus:border-woot-500 outline-none" }
|
||||
) %>
|
||||
</div>
|
||||
@@ -0,0 +1,8 @@
|
||||
<div class="flex flex-col gap-1">
|
||||
<%= f.label field.attribute, class: "text-sm font-medium text-n-slate-11" %>
|
||||
<%= f.text_field(
|
||||
field.attribute,
|
||||
value: field.data,
|
||||
class: "h-9 px-3 text-sm text-n-slate-12 bg-n-solid-2 border border-n-weak rounded-lg focus:ring-2 focus:ring-woot-500 focus:border-woot-500 outline-none"
|
||||
) %>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
<div class="flex flex-col gap-1">
|
||||
<%= f.label field.attribute, class: "text-sm font-medium text-n-slate-11" %>
|
||||
<%= f.text_area(
|
||||
field.attribute,
|
||||
value: field.data,
|
||||
rows: 4,
|
||||
class: "px-3 py-2 text-sm text-n-slate-12 bg-n-solid-2 border border-n-weak rounded-lg focus:ring-2 focus:ring-woot-500 focus:border-woot-500 outline-none resize-y"
|
||||
) %>
|
||||
</div>
|
||||
@@ -30,7 +30,7 @@ By default, it renders:
|
||||
<%= render "icons" %>
|
||||
<div class="flex w-full h-full">
|
||||
<%= render "navigation" -%>
|
||||
<main class="w-full overflow-auto" role="main">
|
||||
<main class="w-full overflow-auto bg-n-background" role="main">
|
||||
<%= render "flashes" -%>
|
||||
<%= yield %>
|
||||
</main>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<section class="main-content__body">
|
||||
<hr/>
|
||||
<%= form_for([:reset_cache, namespace, page.resource], method: :post, html: { class: "form" }) do |f| %>
|
||||
<div class="form-actions">
|
||||
<p class="pb-3">This will clear the IndexedDB cache keys from redis. <br>The next load will fetch the data from backend.</p>
|
||||
<%= f.submit 'Reset Frontend Cache' %>
|
||||
</div>
|
||||
<div class="px-6 py-4">
|
||||
<h3 class="text-sm font-medium text-n-slate-12 mb-2">Reset Frontend Cache</h3>
|
||||
<p class="text-xs text-n-slate-11 mb-3">This will clear the IndexedDB cache keys from redis. The next load will fetch the data from backend.</p>
|
||||
<%= form_for([:reset_cache, namespace, page.resource], method: :post) do |f| %>
|
||||
<%= f.submit 'Reset Frontend Cache', class: "inline-flex items-center justify-center h-8 px-3 text-sm font-medium text-n-slate-12 bg-n-alpha-2 rounded-lg hover:bg-n-alpha-3 transition-colors cursor-pointer" %>
|
||||
<% end %>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
<% if ENV.fetch('ENABLE_ACCOUNT_SEEDING', !Rails.env.production?) %>
|
||||
<section class="main-content__body">
|
||||
<hr/>
|
||||
<%= form_for([:seed, namespace, page.resource], method: :post, html: { class: "form" }) do |f| %>
|
||||
|
||||
<div class="form-actions">
|
||||
<div class="pb-3">
|
||||
<p>Click the button to generate seed data into this account for demos.</p>
|
||||
<p class="text-color-red">Note: This will clear all the existing data in this account.</p>
|
||||
</div>
|
||||
<%= f.submit 'Generate Seed Data' %>
|
||||
</div>
|
||||
<% if ENV.fetch('ENABLE_ACCOUNT_SEEDING', !Rails.env.production?) %>
|
||||
<div class="px-6 py-4">
|
||||
<h3 class="text-sm font-medium text-n-slate-12 mb-2">Generate Seed Data</h3>
|
||||
<p class="text-xs text-n-slate-11 mb-1">Click the button to generate seed data into this account for demos.</p>
|
||||
<p class="text-xs text-ruby-500 mb-3">Note: This will clear all the existing data in this account.</p>
|
||||
<%= form_for([:seed, namespace, page.resource], method: :post) do |f| %>
|
||||
<%= f.submit 'Generate Seed Data', class: "inline-flex items-center justify-center h-8 px-3 text-sm font-medium text-ruby-500 bg-ruby-100 rounded-lg hover:bg-ruby-200 transition-colors cursor-pointer" %>
|
||||
<% end %>
|
||||
</section>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
@@ -15,84 +15,169 @@ as well as a link to its edit page.
|
||||
|
||||
[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Show %>
|
||||
|
||||
<% content_for(:title) do
|
||||
t("administrate.actions.show_resource", name: page.page_title)
|
||||
end %>
|
||||
<% content_for(:title) do
|
||||
t("administrate.actions.show_resource", name: page.page_title)
|
||||
end %>
|
||||
|
||||
<header class="main-content__header" role="banner">
|
||||
<h1 class="main-content__page-title">
|
||||
<%= content_for(:title) %>
|
||||
</h1>
|
||||
<section class="flex flex-col w-full h-full">
|
||||
<header class="sticky top-0 z-10 bg-n-background border-b border-n-weak" role="banner">
|
||||
<div class="flex items-center justify-between w-full h-20 mx-auto max-w-[60rem]">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="border border-n-weak p-2 rounded-full text-n-slate-11">
|
||||
<svg width="24" height="24"><use xlink:href="#icon-building-4-line" /></svg>
|
||||
</div>
|
||||
<div class="flex flex-col justify-center">
|
||||
<h1 class="text-base font-medium tracking-tight text-n-slate-12">
|
||||
<%= content_for(:title) %>
|
||||
</h1>
|
||||
<p class="text-xs text-n-slate-11 m-0">Billing, features, and user management</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<%= if accessible_action?(page.resource, :edit)
|
||||
link_to("Edit", [:edit, namespace, page.resource], class: "button")
|
||||
end %>
|
||||
<div class="flex items-center gap-2">
|
||||
<% if accessible_action?(page.resource, :edit) %>
|
||||
<%= link_to("Edit", [:edit, namespace, page.resource], class: "inline-flex items-center justify-center h-8 px-3 text-sm font-medium text-white bg-woot-500 rounded-lg hover:bg-woot-600 transition-colors") %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="main-content__body">
|
||||
<div class="pr-16">
|
||||
<% page.attributes.each do |title, attributes| %>
|
||||
<% if title.present? && title == 'all_features' %>
|
||||
<% regular_features, premium_features =
|
||||
attributes.partition do |attr|
|
||||
!SuperAdmin::AccountFeaturesHelper.account_premium_features.include?(
|
||||
attr.data.keys.first,
|
||||
)
|
||||
end %>
|
||||
|
||||
<div class="space-y-4">
|
||||
<% regular_features.each do |attribute| %>
|
||||
<div class="attribute-label" id="<%= attribute.name %>">
|
||||
<%= t(
|
||||
"helpers.label.#{resource_name}.#{attribute.name}",
|
||||
default: page.resource.class.human_attribute_name(attribute.name),
|
||||
) %>
|
||||
</div>
|
||||
|
||||
<div class="attribute-data attribute-data--<%=attribute.html_class%>"><%= render_field attribute, page: page %></div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<hr class="my-8 border-n-weak">
|
||||
|
||||
<div class="space-y-4">
|
||||
<% premium_features.each do |attribute| %>
|
||||
<div class="attribute-label" id="<%= attribute.name %>">
|
||||
<%= t(
|
||||
"helpers.label.#{resource_name}.#{attribute.name}",
|
||||
default: page.resource.class.human_attribute_name(attribute.name),
|
||||
) %>
|
||||
</div>
|
||||
|
||||
<div class="attribute-data attribute-data--<%=attribute.html_class%>"><%= render_field attribute, page: page %></div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<fieldset class="<%= "field-unit--nested" if title.present? %>">
|
||||
<% if title.present? %>
|
||||
<legend><%= t "helpers.label.#{page.resource_name}.#{title}", default: title %></legend>
|
||||
<% end %>
|
||||
<main class="flex-1 px-6 overflow-y-auto">
|
||||
<div class="w-full mx-auto max-w-[60rem] py-6 space-y-6">
|
||||
|
||||
<%# Basic Information Card %>
|
||||
<div class="rounded-xl bg-n-solid-2 shadow outline-1 outline outline-n-container overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-n-weak">
|
||||
<h2 class="text-base font-medium text-n-slate-12">Account Information</h2>
|
||||
</div>
|
||||
<div class="divide-y divide-n-weak">
|
||||
<% page.attributes.each do |title, attributes| %>
|
||||
<% next if title.present? %>
|
||||
<% attributes.each do |attribute| %>
|
||||
<div class="attribute-label" id="<%= attribute.name %>">
|
||||
<%= t(
|
||||
"helpers.label.#{resource_name}.#{attribute.name}",
|
||||
default: page.resource.class.human_attribute_name(attribute.name),
|
||||
) %>
|
||||
<% next if ['account_users', 'custom_attributes', 'all_features'].include?(attribute.name.to_s) %>
|
||||
<div class="grid grid-cols-[180px,1fr] items-center min-h-12 px-6">
|
||||
<span class="text-sm text-n-slate-11">
|
||||
<%= t(
|
||||
"helpers.label.#{resource_name}.#{attribute.name}",
|
||||
default: page.resource.class.human_attribute_name(attribute.name),
|
||||
) %>
|
||||
</span>
|
||||
<span class="text-sm text-n-slate-12 py-3">
|
||||
<%= render_field attribute, page: page %>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="attribute-data attribute-data--<%=attribute.html_class%>"><%= render_field attribute, page: page %></div>
|
||||
<% end %>
|
||||
</fieldset>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%# Account Users Card %>
|
||||
<% page.attributes.each do |title, attributes| %>
|
||||
<% next if title.present? %>
|
||||
<% attributes.each do |attribute| %>
|
||||
<% if attribute.name.to_s == 'account_users' && page.resource.account_users.any? %>
|
||||
<div class="rounded-xl bg-n-solid-2 shadow outline-1 outline outline-n-container overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-n-weak">
|
||||
<h2 class="text-base font-medium text-n-slate-12">Account Users</h2>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead class="bg-n-slate-1">
|
||||
<tr>
|
||||
<th class="text-left py-3 px-6 text-sm font-medium text-n-slate-11">Account</th>
|
||||
<th class="text-left py-3 px-6 text-sm font-medium text-n-slate-11">User</th>
|
||||
<th class="text-left py-3 px-6 text-sm font-medium text-n-slate-11">Inviter</th>
|
||||
<th class="text-left py-3 px-6 text-sm font-medium text-n-slate-11">Role</th>
|
||||
<th class="text-left py-3 px-6 text-sm font-medium text-n-slate-11"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-n-weak">
|
||||
<% page.resource.account_users.each do |account_user| %>
|
||||
<tr class="hover:bg-n-alpha-1">
|
||||
<td class="py-3 px-6 text-sm text-n-slate-12">
|
||||
<%= link_to "##{account_user.account_id} #{account_user.account.name}", super_admin_account_path(account_user.account), class: "text-n-slate-12 hover:underline" %>
|
||||
</td>
|
||||
<td class="py-3 px-6 text-sm text-n-slate-12">
|
||||
<%= link_to "##{account_user.user_id} #{account_user.user.name}", super_admin_user_path(account_user.user), class: "text-n-slate-12 hover:underline" %>
|
||||
</td>
|
||||
<td class="py-3 px-6 text-sm text-n-slate-11">
|
||||
<% if account_user.inviter.present? %>
|
||||
<%= link_to account_user.inviter.name, super_admin_user_path(account_user.inviter), class: "text-n-slate-12 hover:underline" %>
|
||||
<% else %>
|
||||
<span class="text-n-slate-10">---</span>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="py-3 px-6">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-md text-xs font-medium bg-n-alpha-2 text-n-slate-12 capitalize">
|
||||
<%= account_user.role %>
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-3 px-6 text-right">
|
||||
<%= link_to "Destroy", super_admin_account_user_path(account_user), method: :delete, data: { confirm: "Are you sure?" }, class: "text-sm text-ruby-500 hover:text-ruby-600 hover:underline" %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%# Custom Attributes Card %>
|
||||
<% if page.resource.custom_attributes.present? %>
|
||||
<div class="rounded-xl bg-n-solid-2 shadow outline-1 outline outline-n-container overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-n-weak">
|
||||
<h2 class="text-base font-medium text-n-slate-12">Custom Attributes</h2>
|
||||
</div>
|
||||
<div class="divide-y divide-n-weak">
|
||||
<% page.resource.custom_attributes.each do |key, value| %>
|
||||
<div class="grid grid-cols-[180px,1fr] items-start min-h-12 px-6">
|
||||
<span class="text-sm text-n-slate-11 py-3">
|
||||
<%= key.to_s.titleize %>
|
||||
</span>
|
||||
<span class="text-sm text-n-slate-12 py-3 break-all font-mono">
|
||||
<%= value %>
|
||||
</span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%# Features Section %>
|
||||
<% page.attributes.each do |title, attributes| %>
|
||||
<% next if title.present? %>
|
||||
<% attributes.each do |attribute| %>
|
||||
<% if attribute.name.to_s == 'all_features' %>
|
||||
<div class="rounded-xl bg-n-solid-2 shadow outline-1 outline outline-n-container overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-n-weak">
|
||||
<h2 class="text-base font-medium text-n-slate-12">Features</h2>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<%= render_field attribute, page: page %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%# Actions Card %>
|
||||
<div class="rounded-xl bg-n-solid-2 shadow outline-1 outline outline-n-container overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-n-weak">
|
||||
<h2 class="text-base font-medium text-n-slate-12">Actions</h2>
|
||||
</div>
|
||||
<div class="divide-y divide-n-weak">
|
||||
<div class="px-6 py-4">
|
||||
<h3 class="text-sm font-medium text-n-slate-12 mb-3">Add User to Account</h3>
|
||||
<%= render 'super_admin/shared/account_user_form', page: page, namespace: namespace, resource_type: 'account' %>
|
||||
</div>
|
||||
<%= render partial: "seed_data", locals: { page: page } %>
|
||||
<%= render partial: "reset_cache", locals: { page: page } %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<%= render 'super_admin/shared/account_user_form', page: page, namespace: namespace, resource_type: 'account' %>
|
||||
|
||||
<%= render partial: "seed_data", locals: { page: page } %>
|
||||
|
||||
<%= render partial: "reset_cache", locals: { page: page } %>
|
||||
</main>
|
||||
</section>
|
||||
|
||||
@@ -2,78 +2,92 @@
|
||||
Configure Settings - <%= @config.titleize %>
|
||||
<% end %>
|
||||
|
||||
<header class="main-content__header" role="banner">
|
||||
<h1 class="main-content__page-title" id="page-title">
|
||||
<%= content_for(:title) %>
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
.eye-icon.eye-hide path {
|
||||
d: path('M3 3l18 18M10.5 10.677a2 2 0 002.823 2.823M7.362 7.561C5.68 8.74 4.279 10.42 3 12c1.889 2.991 5.282 6 9 6 1.55 0 3.043-.523 4.395-1.35M12 6c4.008 0 6.701 3.009 9 6a15.66 15.66 0 01-1.078 1.5');
|
||||
}
|
||||
</style>
|
||||
|
||||
<section class="main-content__body">
|
||||
<%= form_with url: super_admin_app_config_url(config: @config) , method: :post do |form| %>
|
||||
<% @allowed_configs.each do |key| %>
|
||||
<div class="flex mb-8">
|
||||
<div class="field-unit__label">
|
||||
<%= form.label "app_config[#{key}]", @installation_configs[key]&.dig('display_title') || key %>
|
||||
<section class="flex flex-col w-full h-full">
|
||||
<header class="sticky top-0 z-10 bg-n-background border-b border-n-weak" role="banner">
|
||||
<div class="flex items-center justify-between w-full h-20 mx-auto max-w-[60rem]">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="border border-n-weak p-2 rounded-full text-n-slate-11">
|
||||
<svg width="24" height="24"><use xlink:href="#icon-settings-2-line" /></svg>
|
||||
</div>
|
||||
<div class="-mt-2 field-unit__field ">
|
||||
<% if @installation_configs[key]&.dig('type') == 'boolean' %>
|
||||
<%= form.select "app_config[#{key}]",
|
||||
[["True", true], ["False", false]],
|
||||
{ selected: ActiveModel::Type::Boolean.new.cast(@app_config[key]) },
|
||||
class: "mt-2 border border-slate-100 p-1 rounded-md"
|
||||
%>
|
||||
<% elsif @installation_configs[key]&.dig('type') == 'code' %>
|
||||
<%= form.text_area "app_config[#{key}]",
|
||||
value: @app_config[key],
|
||||
rows: 12,
|
||||
wrap: 'off',
|
||||
class: "mt-2 border font-mono text-xs border-slate-100 p-1 rounded-md overflow-scroll"
|
||||
%>
|
||||
<% elsif @installation_configs[key]&.dig('type') == 'secret' %>
|
||||
<div class="relative">
|
||||
<%= form.password_field "app_config[#{key}]",
|
||||
id: "app_config_#{key}",
|
||||
value: @app_config[key],
|
||||
class: "mt-2 border border-slate-100 p-1.5 pr-8 rounded-md w-full"
|
||||
%>
|
||||
<button
|
||||
type="button"
|
||||
class="absolute reset-base !bg-white top-1/2 !outline-0 !text-n-slate-11 -translate-y-1/2 right-2 p-1 hover:!bg-n-slate-5 rounded-sm toggle-password"
|
||||
data-target="app_config_<%= key %>"
|
||||
>
|
||||
<svg class="eye-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 5C5.63636 5 2 12 2 12C2 12 5.63636 19 12 19C18.3636 19 22 12 22 12C22 12 18.3636 5 12 5Z"/>
|
||||
<path d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<% elsif @installation_configs[key]&.dig('type') == 'select' && @installation_configs[key]&.dig('options').present? %>
|
||||
<%= form.select "app_config[#{key}]",
|
||||
@installation_configs[key]['options'].map { |val, label| [label, val] },
|
||||
{ selected: @app_config[key] },
|
||||
class: "mt-2 border border-slate-100 p-1 rounded-md"
|
||||
%>
|
||||
<% else %>
|
||||
<%= form.text_field "app_config[#{key}]", value: @app_config[key] %>
|
||||
<% end %>
|
||||
<%if @installation_configs[key]&.dig('description').present? %>
|
||||
<p class="pt-2 text-xs italic text-slate-400">
|
||||
<%= @installation_configs[key]&.dig('description') %>
|
||||
</p>
|
||||
<% end %>
|
||||
<div class="flex flex-col justify-center">
|
||||
<h1 class="text-base font-medium tracking-tight text-n-slate-12" id="page-title">
|
||||
<%= content_for(:title) %>
|
||||
</h1>
|
||||
<p class="text-xs text-n-slate-11 m-0">Environment and instance configuration</p>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="form-actions">
|
||||
<%= form.submit "Submit" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</header>
|
||||
|
||||
<main class="flex-1 px-6 overflow-y-auto">
|
||||
<div class="w-full mx-auto max-w-[60rem] py-4">
|
||||
<%= form_with url: super_admin_app_config_url(config: @config), method: :post do |form| %>
|
||||
<% @allowed_configs.each do |key| %>
|
||||
<div class="flex mb-8">
|
||||
<div class="field-unit__label">
|
||||
<%= form.label "app_config[#{key}]", @installation_configs[key]&.dig('display_title') || key %>
|
||||
</div>
|
||||
<div class="-mt-2 field-unit__field">
|
||||
<% if @installation_configs[key]&.dig('type') == 'boolean' %>
|
||||
<%= form.select "app_config[#{key}]",
|
||||
[["True", true], ["False", false]],
|
||||
{ selected: ActiveModel::Type::Boolean.new.cast(@app_config[key]) },
|
||||
class: "mt-2 border border-n-weak p-1 rounded-md bg-n-solid-2"
|
||||
%>
|
||||
<% elsif @installation_configs[key]&.dig('type') == 'code' %>
|
||||
<%= form.text_area "app_config[#{key}]",
|
||||
value: @app_config[key],
|
||||
rows: 12,
|
||||
wrap: 'off',
|
||||
class: "mt-2 border font-mono text-xs border-n-weak p-1 rounded-md overflow-scroll bg-n-solid-2"
|
||||
%>
|
||||
<% elsif @installation_configs[key]&.dig('type') == 'secret' %>
|
||||
<div class="relative">
|
||||
<%= form.password_field "app_config[#{key}]",
|
||||
id: "app_config_#{key}",
|
||||
value: @app_config[key],
|
||||
class: "mt-2 border border-n-weak p-1.5 pr-8 rounded-md w-full bg-n-solid-2"
|
||||
%>
|
||||
<button
|
||||
type="button"
|
||||
class="absolute reset-base !bg-transparent top-1/2 !outline-0 !text-n-slate-11 -translate-y-1/2 right-2 p-1 hover:!bg-n-alpha-2 rounded-sm toggle-password"
|
||||
data-target="app_config_<%= key %>"
|
||||
>
|
||||
<svg class="eye-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 5C5.63636 5 2 12 2 12C2 12 5.63636 19 12 19C18.3636 19 22 12 22 12C22 12 18.3636 5 12 5Z"/>
|
||||
<path d="M12 15C13.6569 15 15 13.6569 15 12C15 10.3431 13.6569 9 12 9C10.3431 9 9 10.3431 9 12C9 13.6569 10.3431 15 12 15Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<% elsif @installation_configs[key]&.dig('type') == 'select' && @installation_configs[key]&.dig('options').present? %>
|
||||
<%= form.select "app_config[#{key}]",
|
||||
@installation_configs[key]['options'].map { |val, label| [label, val] },
|
||||
{ selected: @app_config[key] },
|
||||
class: "mt-2 border border-n-weak p-1 rounded-md bg-n-solid-2"
|
||||
%>
|
||||
<% else %>
|
||||
<%= form.text_field "app_config[#{key}]", value: @app_config[key], class: "border border-n-weak p-1.5 rounded-md bg-n-solid-2" %>
|
||||
<% end %>
|
||||
<% if @installation_configs[key]&.dig('description').present? %>
|
||||
<p class="pt-2 text-xs italic text-n-slate-11">
|
||||
<%= @installation_configs[key]&.dig('description') %>
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="pt-4">
|
||||
<%= form.submit "Submit", class: "inline-flex items-center justify-center h-8 px-4 text-sm font-medium text-white bg-woot-500 rounded-lg hover:bg-woot-600 transition-colors cursor-pointer" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</main>
|
||||
</section>
|
||||
|
||||
<% content_for :javascript do %>
|
||||
|
||||
@@ -18,78 +18,80 @@ to display a collection of resources in an HTML table.
|
||||
[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Collection
|
||||
%>
|
||||
|
||||
<table aria-labelledby="<%= table_title %>">
|
||||
<thead>
|
||||
<tr>
|
||||
<% collection_presenter.attribute_types.each do |attr_name, attr_type| %>
|
||||
<th class="cell-label
|
||||
cell-label--<%= attr_type.html_class %>
|
||||
cell-label--<%= collection_presenter.ordered_html_class(attr_name) %>"
|
||||
scope="col"
|
||||
role="columnheader"
|
||||
aria-sort="<%= sort_order(collection_presenter.ordered_html_class(attr_name)) %>">
|
||||
<%= link_to(sanitized_order_params(page, collection_field_name).merge(
|
||||
collection_presenter.order_params_for(attr_name, key: collection_field_name)
|
||||
)) do %>
|
||||
<%= t(
|
||||
"helpers.label.#{collection_presenter.resource_name}.#{attr_name}",
|
||||
default: attr_name.to_s,
|
||||
).titleize %>
|
||||
<% if collection_presenter.ordered_by?(attr_name) %>
|
||||
<span class="cell-label__sort-indicator cell-label__sort-indicator--<%= collection_presenter.ordered_html_class(attr_name) %>">
|
||||
<svg aria-hidden="true">
|
||||
<use xlink:href="#icon-up-caret" />
|
||||
</svg>
|
||||
</span>
|
||||
<div class="rounded-xl bg-n-solid-2 shadow outline-1 outline outline-n-container overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full" aria-labelledby="<%= table_title %>">
|
||||
<thead class="bg-n-slate-1">
|
||||
<tr>
|
||||
<% collection_presenter.attribute_types.each do |attr_name, attr_type| %>
|
||||
<th class="text-left py-3 px-4 text-sm font-medium text-n-slate-11"
|
||||
scope="col"
|
||||
role="columnheader"
|
||||
aria-sort="<%= sort_order(collection_presenter.ordered_html_class(attr_name)) %>">
|
||||
<%= link_to(sanitized_order_params(page, collection_field_name).merge(
|
||||
collection_presenter.order_params_for(attr_name, key: collection_field_name)
|
||||
), class: "inline-flex items-center gap-1 hover:text-n-slate-12") do %>
|
||||
<%= t(
|
||||
"helpers.label.#{collection_presenter.resource_name}.#{attr_name}",
|
||||
default: attr_name.to_s,
|
||||
).titleize %>
|
||||
<% if collection_presenter.ordered_by?(attr_name) %>
|
||||
<span class="<%= collection_presenter.ordered_html_class(attr_name) == 'asc' ? '' : 'rotate-180' %>">
|
||||
<svg class="w-3 h-3" aria-hidden="true">
|
||||
<use xlink:href="#icon-up-caret" />
|
||||
</svg>
|
||||
</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</th>
|
||||
<% end %>
|
||||
<% [existing_action?(collection_presenter.resource_name, :edit),
|
||||
existing_action?(collection_presenter.resource_name, :destroy)].count(true).times do %>
|
||||
<th scope="col" class="py-3 px-4"></th>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="divide-y divide-n-weak">
|
||||
<% resources.each do |resource| %>
|
||||
<tr class="js-table-row hover:bg-n-alpha-1 transition-colors"
|
||||
tabindex="0"
|
||||
<% if existing_action? collection_presenter.resource_name, :show %>
|
||||
<%= %(role=link data-url=#{polymorphic_path([namespace, resource])}) %>
|
||||
<% end %>
|
||||
>
|
||||
<% collection_presenter.attributes_for(resource).each do |attribute| %>
|
||||
<td class="py-3 px-4 text-sm text-n-slate-12">
|
||||
<% if authorized_action? resource, :show -%>
|
||||
<a href="<%= polymorphic_path([namespace, resource]) -%>"
|
||||
class="hover:underline"
|
||||
>
|
||||
<%= render_field attribute %>
|
||||
</a>
|
||||
<% end -%>
|
||||
</td>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</th>
|
||||
<% end %>
|
||||
<% [existing_action?(collection_presenter.resource_name, :edit),
|
||||
existing_action?(collection_presenter.resource_name, :destroy)].count(true).times do %>
|
||||
<th scope="col"></th>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<% resources.each do |resource| %>
|
||||
<tr class="js-table-row"
|
||||
tabindex="0"
|
||||
<% if existing_action? collection_presenter.resource_name, :show %>
|
||||
<%= %(role=link data-url=#{polymorphic_path([namespace, resource])}) %>
|
||||
<% end %>
|
||||
>
|
||||
<% collection_presenter.attributes_for(resource).each do |attribute| %>
|
||||
<td class="cell-data cell-data--<%= attribute.html_class %>">
|
||||
<% if authorized_action? resource, :show -%>
|
||||
<a href="<%= polymorphic_path([namespace, resource]) -%>"
|
||||
class="action-show"
|
||||
>
|
||||
<%= render_field attribute %>
|
||||
</a>
|
||||
<% end -%>
|
||||
</td>
|
||||
<% end %>
|
||||
<% if existing_action? collection_presenter.resource_name, :edit %>
|
||||
<td class="py-3 px-4 text-right"><%= link_to(
|
||||
t("administrate.actions.edit"),
|
||||
[:edit, namespace, resource],
|
||||
class: "text-sm text-woot-500 hover:text-woot-600 hover:underline",
|
||||
) if authorized_action? resource, :edit%></td>
|
||||
<% end %>
|
||||
|
||||
<% if existing_action? collection_presenter.resource_name, :edit %>
|
||||
<td><%= link_to(
|
||||
t("administrate.actions.edit"),
|
||||
[:edit, namespace, resource],
|
||||
class: "action-edit",
|
||||
) if authorized_action? resource, :edit%></td>
|
||||
<% if existing_action? collection_presenter.resource_name, :destroy %>
|
||||
<td class="py-3 px-4 text-right"><%= link_to(
|
||||
t("administrate.actions.destroy"),
|
||||
[namespace, resource],
|
||||
class: "text-sm text-ruby-500 hover:text-ruby-600 hover:underline",
|
||||
method: :delete,
|
||||
data: { confirm: t("administrate.actions.confirm") }
|
||||
) if authorized_action? resource, :destroy %></td>
|
||||
<% end %>
|
||||
</tr>
|
||||
<% end %>
|
||||
|
||||
<% if existing_action? collection_presenter.resource_name, :destroy %>
|
||||
<td><%= link_to(
|
||||
t("administrate.actions.destroy"),
|
||||
[namespace, resource],
|
||||
class: "text-color-red",
|
||||
method: :delete,
|
||||
data: { confirm: t("administrate.actions.confirm") }
|
||||
) if authorized_action? resource, :destroy %></td>
|
||||
<% end %>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -28,27 +28,23 @@ for a collection of resources.
|
||||
%>
|
||||
|
||||
<% if dashboard_class.const_defined?(:COLLECTION_FILTERS) && !dashboard_class::COLLECTION_FILTERS.empty? %>
|
||||
<div class="flex items-center bg-gray-100 border-0 rounded-md shadow-none relative w-[260px]">
|
||||
<div class="flex items-center h-10 px-2 w-full">
|
||||
<div class="flex items-center justify-center flex-shrink-0 mr-2 text-gray-500" title="Filter by">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex-1 h-full min-w-0 relative">
|
||||
<select id="filter-select" class="appearance-none bg-transparent border-0 text-gray-700 cursor-pointer text-sm h-full overflow-hidden truncate whitespace-nowrap w-full pr-7 pl-0 py-2 focus:outline-none bg-[url('data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2712%27 height=%2712%27 viewBox=%270 0 12 12%27%3E%3Cpath fill=%27%23293f54%27 d=%27M6 9L1 4h10z%27/%3E%3C/svg%3E')] bg-[right_0.25rem_center] bg-no-repeat bg-[length:0.75rem]" onchange="applyFilter(this.value)">
|
||||
<option value="">All records</option>
|
||||
<% dashboard_class::COLLECTION_FILTERS.each do |filter_name, _| %>
|
||||
<option value="<%= filter_name %>" <%= 'selected' if filter_name.to_s == current_filter %>>
|
||||
<%= filter_name.to_s.titleize %>
|
||||
</option>
|
||||
<% end %>
|
||||
</select>
|
||||
<% if current_filter %>
|
||||
<a href="?" class="flex items-center justify-center rounded-full text-gray-500 text-xl font-bold h-[18px] w-[18px] leading-none absolute right-5 top-1/2 -translate-y-1/2 no-underline z-2 hover:text-gray-900" title="Clear filter">×</a>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="relative flex items-center">
|
||||
<div class="absolute left-3 text-n-slate-10">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon>
|
||||
</svg>
|
||||
</div>
|
||||
<select id="filter-select" class="h-9 pl-9 pr-8 text-sm text-n-slate-12 bg-n-solid-2 border border-n-weak rounded-lg cursor-pointer appearance-none focus:ring-2 focus:ring-woot-500 focus:border-woot-500 outline-none bg-[url('data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2712%27 height=%2712%27 viewBox=%270 0 12 12%27%3E%3Cpath fill=%27%236b7280%27 d=%27M6 9L1 4h10z%27/%3E%3C/svg%3E')] bg-[right_0.5rem_center] bg-no-repeat bg-[length:0.75rem]" onchange="applyFilter(this.value)">
|
||||
<option value="">All records</option>
|
||||
<% dashboard_class::COLLECTION_FILTERS.each do |filter_name, _| %>
|
||||
<option value="<%= filter_name %>" <%= 'selected' if filter_name.to_s == current_filter %>>
|
||||
<%= filter_name.to_s.titleize %>
|
||||
</option>
|
||||
<% end %>
|
||||
</select>
|
||||
<% if current_filter %>
|
||||
<a href="?" class="absolute right-6 text-n-slate-10 hover:text-n-slate-12 text-lg font-medium" title="Clear filter">×</a>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<li class="px-4 mb-1">
|
||||
<% text_class_name = current_page?(url) ? 'text-woot-500 bg-slate-25' : 'text-slate-800' %>
|
||||
<%= link_to(url, class: text_class_name + " -ml-1 focus:outline-none cursor-pointer flex items-center px-2 py-1.5 text-slate-800 cursor-pointer hover:text-woot-500 hover:bg-slate-25 rounded-lg") do %>
|
||||
<svg width="16" height="16"><use xlink:href="#<%= icon %>" /></svg>
|
||||
<span class="ml-2 text-sm"><%= label %></span>
|
||||
<li>
|
||||
<% is_active = current_page?(url) %>
|
||||
<% base_class = "flex items-center gap-2 px-2.5 py-2 text-sm rounded-lg transition-colors" %>
|
||||
<% active_class = is_active ? "text-woot-500 bg-woot-50" : "text-n-slate-11 hover:text-n-slate-12 hover:bg-n-alpha-1" %>
|
||||
<%= link_to(url, class: "#{base_class} #{active_class}") do %>
|
||||
<svg width="20" height="20" class="flex-shrink-0"><use xlink:href="#<%= icon %>" /></svg>
|
||||
<span><%= label %></span>
|
||||
<% end %>
|
||||
</li>
|
||||
|
||||
@@ -19,18 +19,18 @@ as defined by the routes in the `admin/` namespace
|
||||
}
|
||||
%>
|
||||
|
||||
<div class="border-slate-100 border-r w-56 flex-shrink-0 justify-between h-full flex flex-col" role="navigation">
|
||||
<div class="border-n-weak border-r w-56 flex-shrink-0 justify-between h-full flex flex-col bg-n-background" role="navigation">
|
||||
<div>
|
||||
<div class="flex mx-4 mb-4 border-slate-100 border-b py-6">
|
||||
<%= link_to image_tag('/brand-assets/logo_thumbnail.svg', alt: 'Chatwoot Admin Dashboard', class: 'h-10'), super_admin_root_url %>
|
||||
<div class="flex flex-col ml-3">
|
||||
<div class="text-sm">Chatwoot <%= Chatwoot.config[:version] %></div>
|
||||
<div class="text-xs text-slate-700 mt-0.5">Super Admin Console</div>
|
||||
<div class="flex items-center mx-4 border-n-weak border-b py-5">
|
||||
<%= link_to image_tag('/brand-assets/logo_thumbnail.svg', alt: 'Chatwoot Admin Dashboard', class: 'h-8'), super_admin_root_url %>
|
||||
<div class="flex flex-col ml-2.5">
|
||||
<div class="text-sm font-medium text-n-slate-12">Chatwoot <%= Chatwoot.config[:version] %></div>
|
||||
<div class="text-xs text-n-slate-11">Super Admin Console</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="my-4">
|
||||
<%= render partial: "nav_item", locals: { icon: 'icon-grid-line', url: super_admin_root_url, label: 'Dashboard' } %>
|
||||
<ul class="py-4 px-3 space-y-0.5">
|
||||
<%= render partial: "nav_item", locals: { icon: 'icon-dashboard-line', url: super_admin_root_url, label: 'Dashboard' } %>
|
||||
<% Administrate::Namespace.new(namespace).resources.each do |resource| %>
|
||||
<% next if ["account_users", "access_tokens", "installation_configs", "dashboard", "devise/sessions", "app_configs", "instance_statuses", "settings"].include? resource.resource %>
|
||||
<%= render partial: "nav_item", locals: {
|
||||
@@ -44,11 +44,11 @@ as defined by the routes in the `admin/` namespace
|
||||
<%= render 'settings_menu', open: settings_open? %>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<ul class="my-4">
|
||||
<div class="border-t border-n-weak">
|
||||
<ul class="py-4 px-3 space-y-0.5">
|
||||
<%= render partial: "nav_item", locals: { icon: 'icon-mist-fill', url: sidekiq_web_url, label: 'Sidekiq Dashboard' } %>
|
||||
<%= render partial: "nav_item", locals: { icon: 'icon-health-book-line', url: super_admin_instance_status_url, label: 'Instance Health' } %>
|
||||
<%= render partial: "nav_item", locals: { icon: 'icon-dashboard-line', url: '/', label: 'Agent Dashboard' } %>
|
||||
<%= render partial: "nav_item", locals: { icon: 'icon-grid-line', url: '/', label: 'Agent Dashboard' } %>
|
||||
<%= render partial: "nav_item", locals: { icon: 'icon-logout-circle-r-line', url: super_admin_logout_url, label: 'Logout' } %>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<form class="search" role="search">
|
||||
<label class="search__label" for="search">
|
||||
<svg class="search__eyeglass-icon" role="img">
|
||||
<form class="relative flex items-center" role="search">
|
||||
<label class="absolute left-3 text-n-slate-10" for="search">
|
||||
<svg class="w-4 h-4" role="img">
|
||||
<title>
|
||||
<%= t("administrate.search.label", resource: resource_name) %>
|
||||
</title>
|
||||
@@ -8,17 +8,19 @@
|
||||
</svg>
|
||||
</label>
|
||||
|
||||
<input class="search__input"
|
||||
<input class="h-9 w-[200px] pl-9 pr-8 text-sm text-n-slate-12 bg-n-solid-2 border border-n-weak rounded-lg focus:ring-2 focus:ring-woot-500 focus:border-woot-500 outline-none placeholder:text-n-slate-10"
|
||||
id="search"
|
||||
type="search"
|
||||
name="search"
|
||||
placeholder="<%= t("administrate.search.label", resource: resource_name) %>"
|
||||
value="<%= search_term %>">
|
||||
|
||||
<%= link_to clear_search_params, class: "search__clear-link" do %>
|
||||
<svg class="search__clear-icon" role="img">
|
||||
<title><%= t("administrate.search.clear") %></title>
|
||||
<use xlink:href="#icon-cancel" />
|
||||
</svg>
|
||||
<% if search_term.present? %>
|
||||
<%= link_to clear_search_params, class: "absolute right-2 text-n-slate-10 hover:text-n-slate-12" do %>
|
||||
<svg class="w-4 h-4" role="img">
|
||||
<title><%= t("administrate.search.clear") %></title>
|
||||
<use xlink:href="#icon-cancel" />
|
||||
</svg>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</form>
|
||||
@@ -1,21 +1,23 @@
|
||||
<li class="px-4 mb-1">
|
||||
<li>
|
||||
<details class="group" <%= 'open' if open %>>
|
||||
<summary class="-ml-1 flex items-center px-2 py-1.5 cursor-pointer rounded-lg <%= open ? 'text-woot-500 bg-slate-25' : 'text-slate-800 hover:text-woot-500 hover:bg-slate-25' %> list-none">
|
||||
<%= link_to super_admin_settings_url, class: 'flex items-center flex-1' do %>
|
||||
<svg width="16" height="16"><use xlink:href="#icon-settings-2-line" /></svg>
|
||||
<span class="ml-2 text-sm">Settings</span>
|
||||
<summary class="flex items-center gap-2 px-2.5 py-2 text-sm rounded-lg transition-colors cursor-pointer list-none <%= open ? 'text-woot-500 bg-woot-50' : 'text-n-slate-11 hover:text-n-slate-12 hover:bg-n-alpha-1' %>">
|
||||
<%= link_to super_admin_settings_url, class: 'flex items-center gap-2 flex-1' do %>
|
||||
<svg width="20" height="20" class="flex-shrink-0"><use xlink:href="#icon-settings-2-line" /></svg>
|
||||
<span>Settings</span>
|
||||
<% end %>
|
||||
<svg class="ml-auto w-4 h-4 transition-transform duration-200 group-open:rotate-180" viewBox="0 0 20 20" fill="currentColor">
|
||||
<svg class="w-4 h-4 transition-transform duration-200 group-open:rotate-180" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</summary>
|
||||
<ul class="ml-4 mt-1">
|
||||
<ul class="mt-1 ml-7 space-y-0.5">
|
||||
<% settings_pages.each do |_feature_key, attrs| %>
|
||||
<% url = super_admin_app_config_url(config: attrs['config_key']) %>
|
||||
<li class="px-4 mb-1">
|
||||
<% text_class = current_page?(url) ? 'text-woot-500 bg-slate-25' : 'text-slate-800' %>
|
||||
<%= link_to url, class: text_class + ' -ml-1 flex items-center px-2 py-1.5 hover:text-woot-500 hover:bg-slate-25 rounded-lg' do %>
|
||||
<span class="ml-2 text-sm"><%= attrs['name'] %></span>
|
||||
<li>
|
||||
<% is_active = current_page?(url) %>
|
||||
<% base_class = "flex items-center px-2.5 py-1.5 text-sm rounded-lg transition-colors" %>
|
||||
<% active_class = is_active ? "text-woot-500 bg-woot-50" : "text-n-slate-11 hover:text-n-slate-12 hover:bg-n-alpha-1" %>
|
||||
<%= link_to url, class: "#{base_class} #{active_class}" do %>
|
||||
<span><%= attrs['name'] %></span>
|
||||
<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
|
||||
@@ -27,49 +27,70 @@ It renders the `_table` partial to display details about the resources.
|
||||
<%= display_resource_name(page.resource_name) %>
|
||||
<% end %>
|
||||
|
||||
<header class="main-content__header" role="banner">
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<h1 class="main-content__page-title m-0 mr-6" id="page-title">
|
||||
<%= content_for(:title) %>
|
||||
</h1>
|
||||
<%
|
||||
resource_icons = {
|
||||
'account' => 'icon-building-4-line',
|
||||
'user' => 'icon-user-follow-line',
|
||||
'account_user' => 'icon-user-follow-line',
|
||||
'agent_bot' => 'icon-robot-line',
|
||||
'response_source' => 'icon-book-2-line',
|
||||
'response_document' => 'icon-draft-line',
|
||||
'response' => 'icon-reply-line'
|
||||
}
|
||||
current_icon = resource_icons[page.resource_name] || 'icon-apps-2-line'
|
||||
%>
|
||||
|
||||
<div class="flex items-center">
|
||||
<% if show_search_bar %>
|
||||
<div class="flex items-center">
|
||||
<%= render("filters", page: page) %>
|
||||
<div class="ml-3">
|
||||
<%= render(
|
||||
"search",
|
||||
search_term: search_term,
|
||||
resource_name: display_resource_name(page.resource_name)
|
||||
) %>
|
||||
</div>
|
||||
<section class="flex flex-col w-full h-full">
|
||||
<header class="sticky top-0 z-10 bg-n-background border-b border-n-weak" role="banner">
|
||||
<div class="flex items-center justify-between w-full h-20 mx-auto max-w-[60rem]">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="border border-n-weak p-2 rounded-full text-n-slate-11">
|
||||
<svg width="24" height="24"><use xlink:href="#<%= current_icon %>" /></svg>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="flex flex-col justify-center">
|
||||
<h1 class="text-base font-medium tracking-tight text-n-slate-12" id="page-title">
|
||||
<%= content_for(:title) %>
|
||||
</h1>
|
||||
<p class="text-xs text-n-slate-11 m-0">Manage all registered <%= page.resource_name.pluralize.downcase %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="whitespace-nowrap ml-4">
|
||||
<%= link_to(
|
||||
t(
|
||||
"administrate.actions.new_resource",
|
||||
name: page.resource_name.titleize.downcase
|
||||
),
|
||||
[:new, namespace, page.resource_path.to_sym],
|
||||
class: "button",
|
||||
) if accessible_action?(new_resource, :new) %>
|
||||
<div class="flex items-center gap-3">
|
||||
<% if show_search_bar %>
|
||||
<%= render("filters", page: page) %>
|
||||
<%= render(
|
||||
"search",
|
||||
search_term: search_term,
|
||||
resource_name: display_resource_name(page.resource_name)
|
||||
) %>
|
||||
<% end %>
|
||||
|
||||
<% if accessible_action?(new_resource, :new) %>
|
||||
<%= link_to(
|
||||
t(
|
||||
"administrate.actions.new_resource",
|
||||
name: page.resource_name.titleize.downcase
|
||||
),
|
||||
[:new, namespace, page.resource_path.to_sym],
|
||||
class: "inline-flex items-center justify-center h-8 px-3 text-sm font-medium text-white bg-woot-500 rounded-lg hover:bg-woot-600 transition-colors",
|
||||
) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</header>
|
||||
|
||||
<section class="main-content__body main-content__body--flush">
|
||||
<%= render(
|
||||
"collection",
|
||||
collection_presenter: page,
|
||||
collection_field_name: resource_name,
|
||||
page: page,
|
||||
resources: resources,
|
||||
table_title: "page-title"
|
||||
) %>
|
||||
<main class="flex-1 px-6 overflow-y-auto">
|
||||
<div class="w-full mx-auto max-w-[60rem] py-4">
|
||||
<%= render(
|
||||
"collection",
|
||||
collection_presenter: page,
|
||||
collection_field_name: resource_name,
|
||||
page: page,
|
||||
resources: resources,
|
||||
table_title: "page-title"
|
||||
) %>
|
||||
|
||||
<%= paginate resources, param_name: '_page'%>
|
||||
<%= paginate resources, param_name: '_page'%>
|
||||
</div>
|
||||
</main>
|
||||
</section>
|
||||
|
||||
@@ -18,48 +18,86 @@ as well as a link to its edit page.
|
||||
|
||||
<% content_for(:title) { t("administrate.actions.show_resource", name: page.page_title) } %>
|
||||
|
||||
<header class="main-content__header">
|
||||
<h1 class="main-content__page-title">
|
||||
<%= content_for(:title) %>
|
||||
</h1>
|
||||
<%
|
||||
resource_icons = {
|
||||
'account' => 'icon-building-4-line',
|
||||
'user' => 'icon-user-follow-line',
|
||||
'account_user' => 'icon-user-follow-line',
|
||||
'agent_bot' => 'icon-robot-line',
|
||||
'response_source' => 'icon-book-2-line',
|
||||
'response_document' => 'icon-draft-line',
|
||||
'response' => 'icon-reply-line'
|
||||
}
|
||||
current_icon = resource_icons[page.resource_name] || 'icon-folder-3-line'
|
||||
%>
|
||||
|
||||
<div>
|
||||
<%= link_to(
|
||||
"Edit",
|
||||
[:edit, namespace, page.resource],
|
||||
class: "button",
|
||||
) if accessible_action?(page.resource, :edit) %>
|
||||
<section class="flex flex-col w-full h-full">
|
||||
<header class="sticky top-0 z-10 bg-n-background border-b border-n-weak" role="banner">
|
||||
<div class="flex items-center justify-between w-full h-20 mx-auto max-w-[60rem]">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="border border-n-weak p-2 rounded-full text-n-slate-11">
|
||||
<svg width="24" height="24"><use xlink:href="#<%= current_icon %>" /></svg>
|
||||
</div>
|
||||
<div class="flex flex-col justify-center">
|
||||
<h1 class="text-base font-medium tracking-tight text-n-slate-12">
|
||||
<%= content_for(:title) %>
|
||||
</h1>
|
||||
<p class="text-xs text-n-slate-11 m-0">Resource details and configuration</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= link_to(
|
||||
t("administrate.actions.destroy"),
|
||||
[namespace, page.resource],
|
||||
class: "button button--danger",
|
||||
method: :delete,
|
||||
data: { confirm: t("administrate.actions.confirm") }
|
||||
) if accessible_action?(page.resource, :destroy) %>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="main-content__body">
|
||||
<dl>
|
||||
<% page.attributes.each do |title, attributes| %>
|
||||
<fieldset class="<%= "field-unit--nested" if title.present? %>">
|
||||
<% if title.present? %>
|
||||
<legend><%= t "helpers.label.#{page.resource_name}.#{title}", default: title %></legend>
|
||||
<% end %>
|
||||
|
||||
<% attributes.each do |attribute| %>
|
||||
<dt class="attribute-label" id="<%= attribute.name %>">
|
||||
<%= t(
|
||||
"helpers.label.#{resource_name}.#{attribute.name}",
|
||||
default: page.resource.class.human_attribute_name(attribute.name),
|
||||
<div class="flex items-center gap-2">
|
||||
<% if accessible_action?(page.resource, :edit) %>
|
||||
<%= link_to(
|
||||
t("administrate.actions.edit"),
|
||||
[:edit, namespace, page.resource],
|
||||
class: "inline-flex items-center justify-center h-8 px-3 text-sm font-medium text-white bg-woot-500 rounded-lg hover:bg-woot-600 transition-colors"
|
||||
) %>
|
||||
</dt>
|
||||
|
||||
<dd class="attribute-data attribute-data--<%=attribute.html_class%>"
|
||||
><%= render_field attribute, page: page %></dd>
|
||||
<% end %>
|
||||
</fieldset>
|
||||
<% end %>
|
||||
</dl>
|
||||
|
||||
<% if accessible_action?(page.resource, :destroy) %>
|
||||
<%= link_to(
|
||||
t("administrate.actions.destroy"),
|
||||
[namespace, page.resource],
|
||||
class: "inline-flex items-center justify-center h-8 px-3 text-sm font-medium text-ruby-500 bg-ruby-100 rounded-lg hover:bg-ruby-200 transition-colors",
|
||||
method: :delete,
|
||||
data: { confirm: t("administrate.actions.confirm") }
|
||||
) %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="flex-1 px-6 overflow-y-auto">
|
||||
<div class="w-full mx-auto max-w-[60rem] py-6 space-y-6">
|
||||
<% page.attributes.each do |title, attributes| %>
|
||||
<div class="rounded-xl bg-n-solid-2 shadow outline-1 outline outline-n-container overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-n-weak">
|
||||
<% if title.present? %>
|
||||
<h2 class="text-base font-medium text-n-slate-12">
|
||||
<%= t "helpers.label.#{page.resource_name}.#{title}", default: title.to_s.titleize %>
|
||||
</h2>
|
||||
<% else %>
|
||||
<h2 class="text-base font-medium text-n-slate-12">Details</h2>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="divide-y divide-n-weak">
|
||||
<% attributes.each do |attribute| %>
|
||||
<div class="grid grid-cols-[180px,1fr] items-center min-h-12 px-6">
|
||||
<span class="text-sm text-n-slate-11">
|
||||
<%= t(
|
||||
"helpers.label.#{resource_name}.#{attribute.name}",
|
||||
default: page.resource.class.human_attribute_name(attribute.name),
|
||||
) %>
|
||||
</span>
|
||||
<span class="text-sm text-n-slate-12 py-3">
|
||||
<%= render_field attribute, page: page %>
|
||||
</span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</main>
|
||||
</section>
|
||||
|
||||
@@ -1,23 +1,49 @@
|
||||
<% content_for(:title) do %>
|
||||
Instance Status
|
||||
<% end %>
|
||||
<header class="main-content__header" role="banner">
|
||||
<h1 class="main-content__page-title" id="page-title">
|
||||
<%= content_for(:title) %>
|
||||
</h1>
|
||||
</header>
|
||||
<section class="main-content__body">
|
||||
<table>
|
||||
<tr>
|
||||
<th>Metric</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
<% @metrics.each do |key,value| %>
|
||||
<tr>
|
||||
<td> <%= key %> </td>
|
||||
<td> <%= value %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
|
||||
<section class="flex flex-col w-full h-full">
|
||||
<header class="sticky top-0 z-10 bg-n-background border-b border-n-weak" role="banner">
|
||||
<div class="flex items-center justify-between w-full h-20 mx-auto max-w-[60rem]">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="border border-n-weak p-2 rounded-full text-n-slate-11">
|
||||
<svg width="24" height="24"><use xlink:href="#icon-health-book-line" /></svg>
|
||||
</div>
|
||||
<div class="flex flex-col justify-center">
|
||||
<h1 class="text-base font-medium tracking-tight text-n-slate-12" id="page-title">
|
||||
<%= content_for(:title) %>
|
||||
</h1>
|
||||
<p class="text-xs text-n-slate-11 m-0">Chatwoot version and system information</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="flex-1 px-6 overflow-y-auto">
|
||||
<div class="w-full mx-auto max-w-[60rem] py-6">
|
||||
<div class="rounded-xl bg-n-solid-2 shadow outline-1 outline outline-n-container overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-n-weak">
|
||||
<h2 class="text-base font-medium text-n-slate-12">System Metrics</h2>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead class="bg-n-slate-1">
|
||||
<tr>
|
||||
<th class="text-left py-3 px-6 text-sm font-medium text-n-slate-11">Metric</th>
|
||||
<th class="text-left py-3 px-6 text-sm font-medium text-n-slate-11">Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-n-weak">
|
||||
<% @metrics.each do |key, value| %>
|
||||
<tr class="hover:bg-n-alpha-1">
|
||||
<td class="py-3 px-6 text-sm text-n-slate-12 font-medium"><%= key %></td>
|
||||
<td class="py-3 px-6 text-sm text-n-slate-11 font-mono"><%= value %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</section>
|
||||
|
||||
@@ -1,129 +1,136 @@
|
||||
<% content_for(:title) do %>
|
||||
Settings
|
||||
<% end %>
|
||||
<header class="flex px-8 py-4 items-center border-b border-n-weak" role="banner">
|
||||
<div class="border border-n-weak mr-4 p-2 rounded-full">
|
||||
<svg width="24" height="24"><use xlink:href="#icon-settings-2-line" /></svg>
|
||||
</div>
|
||||
<div class="flex flex-col h-14 justify-center">
|
||||
<h1 class="text-base font-medium leading-6 text-n-slate-12" id="page-title">
|
||||
<%= content_for(:title) %>
|
||||
</h1>
|
||||
<p class="text-sm font-normal leading-5 text-slate-500 m-0">Update your instance settings, access billing portal</p>
|
||||
</div>
|
||||
</header>
|
||||
<section class="main-content__body px-8">
|
||||
<% if Redis::Alfred.get(Redis::Alfred::CHATWOOT_INSTALLATION_CONFIG_RESET_WARNING) %>
|
||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-5" role="alert">
|
||||
<strong class="font-bold">Alert!</strong>
|
||||
<span class="block sm:inline">Unauthorized premium changes detected in Chatwoot. To keep using them, please upgrade your plan.
|
||||
Contact for help :</span><span class="inline rounded-full bg-red-200 px-2 text-white ml-2">sales@chatwoot.com</span>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="bg-white py-2 px-4 xl:px-0">
|
||||
<%= javascript_include_tag "secretField" %>
|
||||
<div class="flex items-center mb-6">
|
||||
<h4 class="text-sm font-medium leading-5 h-5 text-n-slate-12 mr-4">Installation Identifier</h4>
|
||||
<div data-secret-text="<%= ChatwootHub.installation_identifier %>" class="cell-data__secret-field text-sm leading-5 h-5 text-n-slate-11">
|
||||
<span data-secret-masked="true" class="select-none"></span>
|
||||
<button type="button" onclick="toggleSecretField(event)" data-secret-toggler class="inline-flex items-center justify-center h-5 w-5 p-0 rounded text-n-slate-11 hover:text-n-slate-12 transition-colors focus:outline-none">
|
||||
<svg width="20" height="20" class="w-5 h-5 fill-current">
|
||||
<use xlink:href="#eye-show" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" onclick="copySecretField(event)" data-secret-copier class="inline-flex items-center justify-center h-5 w-5 p-0 rounded text-n-slate-11 hover:text-n-slate-12 transition-colors focus:outline-none">
|
||||
<svg width="20" height="20" class="w-5 h-5 fill-current">
|
||||
<use xlink:href="#icon-copy" />
|
||||
</svg>
|
||||
</button>
|
||||
<section class="flex flex-col w-full h-full">
|
||||
<header class="sticky top-0 z-10 bg-n-background border-b border-n-weak" role="banner">
|
||||
<div class="flex items-center w-full h-20 mx-auto max-w-[60rem] gap-3">
|
||||
<div class="border border-n-weak p-2 rounded-full text-n-slate-11">
|
||||
<svg width="24" height="24"><use xlink:href="#icon-settings-2-line" /></svg>
|
||||
</div>
|
||||
<div class="flex flex-col justify-center">
|
||||
<h1 class="text-base font-medium tracking-tight text-n-slate-12" id="page-title">
|
||||
<%= content_for(:title) %>
|
||||
</h1>
|
||||
<p class="text-xs text-n-slate-11 m-0">Update your instance settings, access billing portal</p>
|
||||
</div>
|
||||
</div>
|
||||
<% if ChatwootApp.enterprise? %>
|
||||
<div class="flex p-4 outline outline-1 outline-n-container rounded-lg items-start md:items-center shadow-sm flex-col md:flex-row">
|
||||
<div class="flex flex-col flex-grow gap-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<h2 class="h-5 leading-5 text-n-slate-12 text-sm font-medium">Current plan</h2>
|
||||
<a href="<%= refresh_super_admin_settings_url %>" class="inline-flex gap-1 text-xs font-medium items-center text-woot-500 hover:text-woot-700">
|
||||
<svg width="16" height="16"><use xlink:href="#icon-refresh-line" /></svg>
|
||||
<span>Refresh</span>
|
||||
</a>
|
||||
</div>
|
||||
<p class="text-n-slate-11 m-0 text-sm"><%= SuperAdmin::FeaturesHelper.plan_details.html_safe %></p>
|
||||
</header>
|
||||
|
||||
<main class="flex-1 px-6 overflow-y-auto">
|
||||
<div class="w-full mx-auto max-w-[60rem] py-4">
|
||||
<% if Redis::Alfred.get(Redis::Alfred::CHATWOOT_INSTALLATION_CONFIG_RESET_WARNING) %>
|
||||
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative mb-5" role="alert">
|
||||
<strong class="font-bold">Alert!</strong>
|
||||
<span class="block sm:inline">Unauthorized premium changes detected in Chatwoot. To keep using them, please upgrade your plan.
|
||||
Contact for help :</span><span class="inline rounded-full bg-red-200 px-2 text-white ml-2">sales@chatwoot.com</span>
|
||||
</div>
|
||||
<a href="<%= ChatwootHub.billing_url %>" target="_blank" rel="noopener noreferrer">
|
||||
<button class="mt-4 md:mt-0 flex gap-1 items-center bg-transparent shadow-sm h-9 hover:text-n-slate-12 hover:bg-slate-50 outline outline-1 outline-n-container rounded text-n-slate-11 font-medium p-2 focus:outline-none">
|
||||
<svg width="16" height="16"><use xlink:href="#icon-settings-2-line" /></svg>
|
||||
<span class="px-1">Manage</span>
|
||||
<% end %>
|
||||
|
||||
<%= javascript_include_tag "secretField" %>
|
||||
<div class="flex items-center mb-6">
|
||||
<h4 class="text-sm font-medium leading-5 h-5 text-n-slate-12 mr-4">Installation Identifier</h4>
|
||||
<div data-secret-text="<%= ChatwootHub.installation_identifier %>" class="cell-data__secret-field text-sm leading-5 h-5 text-n-slate-11">
|
||||
<span data-secret-masked="true" class="select-none"></span>
|
||||
<button type="button" onclick="toggleSecretField(event)" data-secret-toggler class="inline-flex items-center justify-center h-5 w-5 p-0 rounded text-n-slate-11 hover:text-n-slate-12 transition-colors focus:outline-none">
|
||||
<svg width="20" height="20" class="w-5 h-5 fill-current">
|
||||
<use xlink:href="#eye-show" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" onclick="copySecretField(event)" data-secret-copier class="inline-flex items-center justify-center h-5 w-5 p-0 rounded text-n-slate-11 hover:text-n-slate-12 transition-colors focus:outline-none">
|
||||
<svg width="20" height="20" class="w-5 h-5 fill-current">
|
||||
<use xlink:href="#icon-copy" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if ChatwootApp.enterprise? %>
|
||||
<div class="flex p-4 outline outline-1 outline-n-container rounded-xl items-start md:items-center shadow-sm flex-col md:flex-row bg-n-solid-2">
|
||||
<div class="flex flex-col flex-grow gap-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<h2 class="h-5 leading-5 text-n-slate-12 text-sm font-medium">Current plan</h2>
|
||||
<a href="<%= refresh_super_admin_settings_url %>" class="inline-flex gap-1 text-xs font-medium items-center text-woot-500 hover:text-woot-700">
|
||||
<svg width="16" height="16"><use xlink:href="#icon-refresh-line" /></svg>
|
||||
<span>Refresh</span>
|
||||
</a>
|
||||
</div>
|
||||
<p class="text-n-slate-11 m-0 text-sm"><%= SuperAdmin::FeaturesHelper.plan_details.html_safe %></p>
|
||||
</div>
|
||||
<a href="<%= ChatwootHub.billing_url %>" target="_blank" rel="noopener noreferrer">
|
||||
<button class="mt-4 md:mt-0 flex gap-1 items-center bg-transparent shadow-sm h-9 hover:text-n-slate-12 hover:bg-n-alpha-2 outline outline-1 outline-n-container rounded text-n-slate-11 font-medium p-2 focus:outline-none">
|
||||
<svg width="16" height="16"><use xlink:href="#icon-settings-2-line" /></svg>
|
||||
<span class="px-1">Manage</span>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if ChatwootHub.pricing_plan != 'community' && User.count > ChatwootHub.pricing_plan_quantity %>
|
||||
<div role="alert" class="mt-4">
|
||||
<div class="border border-t-0 border-red-400 rounded-b bg-red-100 px-4 py-3 text-red-700">
|
||||
<p>You have <%= User.count %> agents. Please add more licenses to add more users.</p>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="flex p-4 outline outline-1 outline-n-container rounded-xl mt-4 items-start md:items-center shadow-sm flex-col md:flex-row bg-n-solid-2">
|
||||
<div class="flex flex-col flex-grow gap-1">
|
||||
<h2 class="h-5 leading-5 text-n-slate-12 text-sm font-medium">Need help?</h2>
|
||||
<p class="text-n-slate-11 m-0 text-sm">Do you face any issues? We are here to help.</p>
|
||||
</div>
|
||||
<a href="https://discord.gg/cJXdrwS" target="_blank">
|
||||
<button class="flex mt-4 md:mt-0 gap-1 items-center bg-transparent shadow-sm h-9 bg-violet-500 hover:bg-violet-600 text-white border border-solid border-violet-600 rounded font-medium p-2 focus:outline-none">
|
||||
<svg class="h-4 w-4" width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.209 3.103c-.495-1.004-1.926-1.004-2.421 0L8.43 7.88l-5.273.766c-1.107.161-1.55 1.522-.748 2.303l3.815 3.72-.9 5.25c-.19 1.103.968 1.944 1.959 1.424l4.715-2.48 4.716 2.48c.99.52 2.148-.32 1.96-1.424l-.902-5.25 3.816-3.72c.8-.78.359-2.142-.748-2.303l-5.273-.766-2.358-4.777ZM9.74 8.615l2.258-4.576 2.259 4.576a1.35 1.35 0 0 0 1.016.738l5.05.734-3.654 3.562a1.35 1.35 0 0 0-.388 1.195l.862 5.03-4.516-2.375a1.35 1.35 0 0 0-1.257 0l-4.516 2.374.862-5.029a1.35 1.35 0 0 0-.388-1.195l-3.654-3.562 5.05-.734c.44-.063.82-.34 1.016-.738ZM1.164 3.782a.75.75 0 0 0 .118 1.054l2.5 2a.75.75 0 1 0 .937-1.172l-2.5-2a.75.75 0 0 0-1.055.118Z" fill="currentColor"/><path d="M22.836 18.218a.75.75 0 0 0-.117-1.054l-2.5-2a.75.75 0 0 0-.938 1.172l2.5 2a.75.75 0 0 0 1.055-.117ZM1.282 17.164a.75.75 0 1 0 .937 1.172l2.5-2a.75.75 0 0 0-.937-1.172l-2.5 2ZM22.836 3.782a.75.75 0 0 1-.117 1.054l-2.5 2a.75.75 0 0 1-.938-1.172l2.5-2a.75.75 0 0 1 1.055.118Z" fill="currentColor"/></svg>
|
||||
<span class="px-1">Community Support</span>
|
||||
</button>
|
||||
</a>
|
||||
<% if ChatwootHub.pricing_plan != 'community' %>
|
||||
<button class="ml-4 flex gap-1 items-center bg-transparent h-9 hover:text-n-slate-12 hover:bg-n-alpha-2 border border-solid border-n-weak rounded text-n-slate-11 font-medium p-2 focus:outline-none" onclick="window.$chatwoot.toggle('open')">
|
||||
<svg class="h-4 w-4" width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.209 3.103c-.495-1.004-1.926-1.004-2.421 0L8.43 7.88l-5.273.766c-1.107.161-1.55 1.522-.748 2.303l3.815 3.72-.9 5.25c-.19 1.103.968 1.944 1.959 1.424l4.715-2.48 4.716 2.48c.99.52 2.148-.32 1.96-1.424l-.902-5.25 3.816-3.72c.8-.78.359-2.142-.748-2.303l-5.273-.766-2.358-4.777ZM9.74 8.615l2.258-4.576 2.259 4.576a1.35 1.35 0 0 0 1.016.738l5.05.734-3.654 3.562a1.35 1.35 0 0 0-.388 1.195l.862 5.03-4.516-2.375a1.35 1.35 0 0 0-1.257 0l-4.516 2.374.862-5.029a1.35 1.35 0 0 0-.388-1.195l-3.654-3.562 5.05-.734c.44-.063.82-.34 1.016-.738ZM1.164 3.782a.75.75 0 0 0 .118 1.054l2.5 2a.75.75 0 1 0 .937-1.172l-2.5-2a.75.75 0 0 0-1.055.118Z" fill="currentColor"/><path d="M22.836 18.218a.75.75 0 0 0-.117-1.054l-2.5-2a.75.75 0 0 0-.938 1.172l2.5 2a.75.75 0 0 0 1.055-.117ZM1.282 17.164a.75.75 0 1 0 .937 1.172l2.5-2a.75.75 0 0 0-.937-1.172l-2.5 2ZM22.836 3.782a.75.75 0 0 1-.117 1.054l-2.5 2a.75.75 0 0 1-.938-1.172l2.5-2a.75.75 0 0 1 1.055.118Z" fill="currentColor"/></svg>
|
||||
<span class="px-1">Chat Support</span>
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if ChatwootHub.pricing_plan != 'community' && User.count > ChatwootHub.pricing_plan_quantity %>
|
||||
<div role="alert">
|
||||
<div class="border border-t-0 border-red-400 rounded-b bg-red-100 px-4 py-3 text-red-700">
|
||||
<p>You have <%= User.count %> agents. Please add more licenses to add more users.</p>
|
||||
<div class="mt-10 py-4">
|
||||
<h3 class="h-5 leading-5 text-n-slate-12 font-medium text-base">Features</h3>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
|
||||
<div class="flex p-4 outline outline-1 outline-n-container rounded-lg mt-4 items-start md:items-center shadow-sm flex-col md:flex-row">
|
||||
<div class="flex flex-col flex-grow gap-1">
|
||||
<h2 class="h-5 leading-5 text-n-slate-12 text-sm font-medium">Need help?</h2>
|
||||
<p class="text-n-slate-11 m-0 text-sm">Do you face any issues? We are here to help.</p>
|
||||
</div>
|
||||
<a href="https://discord.gg/cJXdrwS" target="_blank">
|
||||
<button class="flex mt-4 md:mt-0 gap-1 items-center bg-transparent shadow-sm h-9 bg-violet-500 hover:bg-violet-600 text-white border border-solid border-violet-600 rounded font-medium p-2 focus:outline-none">
|
||||
<svg class="h-4 w-4" width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.209 3.103c-.495-1.004-1.926-1.004-2.421 0L8.43 7.88l-5.273.766c-1.107.161-1.55 1.522-.748 2.303l3.815 3.72-.9 5.25c-.19 1.103.968 1.944 1.959 1.424l4.715-2.48 4.716 2.48c.99.52 2.148-.32 1.96-1.424l-.902-5.25 3.816-3.72c.8-.78.359-2.142-.748-2.303l-5.273-.766-2.358-4.777ZM9.74 8.615l2.258-4.576 2.259 4.576a1.35 1.35 0 0 0 1.016.738l5.05.734-3.654 3.562a1.35 1.35 0 0 0-.388 1.195l.862 5.03-4.516-2.375a1.35 1.35 0 0 0-1.257 0l-4.516 2.374.862-5.029a1.35 1.35 0 0 0-.388-1.195l-3.654-3.562 5.05-.734c.44-.063.82-.34 1.016-.738ZM1.164 3.782a.75.75 0 0 0 .118 1.054l2.5 2a.75.75 0 1 0 .937-1.172l-2.5-2a.75.75 0 0 0-1.055.118Z" fill="currentColor"/><path d="M22.836 18.218a.75.75 0 0 0-.117-1.054l-2.5-2a.75.75 0 0 0-.938 1.172l2.5 2a.75.75 0 0 0 1.055-.117ZM1.282 17.164a.75.75 0 1 0 .937 1.172l2.5-2a.75.75 0 0 0-.937-1.172l-2.5 2ZM22.836 3.782a.75.75 0 0 1-.117 1.054l-2.5 2a.75.75 0 0 1-.938-1.172l2.5-2a.75.75 0 0 1 1.055.118Z" fill="currentColor"/></svg>
|
||||
<span class="px-1">Community Support</span>
|
||||
</button>
|
||||
</a>
|
||||
<% if ChatwootHub.pricing_plan !='community' %>
|
||||
<button class="ml-4 flex gap-1 items-center bg-transparent h-9 hover:text-n-slate-12 hover:bg-slate-50 border border-solid border-slate-100 rounded text-n-slate-11 font-medium p-2 focus:outline-none" onclick="window.$chatwoot.toggle('open')">
|
||||
<svg class="h-4 w-4" width="24" height="24" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13.209 3.103c-.495-1.004-1.926-1.004-2.421 0L8.43 7.88l-5.273.766c-1.107.161-1.55 1.522-.748 2.303l3.815 3.72-.9 5.25c-.19 1.103.968 1.944 1.959 1.424l4.715-2.48 4.716 2.48c.99.52 2.148-.32 1.96-1.424l-.902-5.25 3.816-3.72c.8-.78.359-2.142-.748-2.303l-5.273-.766-2.358-4.777ZM9.74 8.615l2.258-4.576 2.259 4.576a1.35 1.35 0 0 0 1.016.738l5.05.734-3.654 3.562a1.35 1.35 0 0 0-.388 1.195l.862 5.03-4.516-2.375a1.35 1.35 0 0 0-1.257 0l-4.516 2.374.862-5.029a1.35 1.35 0 0 0-.388-1.195l-3.654-3.562 5.05-.734c.44-.063.82-.34 1.016-.738ZM1.164 3.782a.75.75 0 0 0 .118 1.054l2.5 2a.75.75 0 1 0 .937-1.172l-2.5-2a.75.75 0 0 0-1.055.118Z" fill="currentColor"/><path d="M22.836 18.218a.75.75 0 0 0-.117-1.054l-2.5-2a.75.75 0 0 0-.938 1.172l2.5 2a.75.75 0 0 0 1.055-.117ZM1.282 17.164a.75.75 0 1 0 .937 1.172l2.5-2a.75.75 0 0 0-.937-1.172l-2.5 2ZM22.836 3.782a.75.75 0 0 1-.117 1.054l-2.5 2a.75.75 0 0 1-.938-1.172l2.5-2a.75.75 0 0 1 1.055.118Z" fill="currentColor"/></svg>
|
||||
<span class="px-1">Chat Support</span>
|
||||
</button>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="mt-10 py-4">
|
||||
<h3 class="h-5 leading-5 text-n-slate-12 font-medium text-base">Features</h3>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-x-2 gap-y-3">
|
||||
<% SuperAdmin::FeaturesHelper.available_features.each do |feature, attrs| %>
|
||||
<div class="group outline outline-1 outline-n-container p-4 rounded-lg relative shadow-sm">
|
||||
<span class="<%= attrs[:enabled] ? 'bg-green-400 text-white': 'bg-slate-300 text-slate-800' %> absolute rounded-full p-1 inline-flex right-4 top-5">
|
||||
<svg width="14" height="14"><use xlink:href="<%= attrs[:enabled] ? '#icon-tick-line': '#icon-lock-line' %>" /></svg>
|
||||
</span>
|
||||
<div class="visible <% if !attrs[:enabled] %> group-hover:invisible <% end %> flex items-center justify-center w-10 h-10 mb-2 border border-slate-100 border-solid text-slate-800 rounded-full">
|
||||
<svg width="20" height="20"><use xlink:href="#<%= attrs[:icon] %>" /></svg>
|
||||
</div>
|
||||
<% if !attrs[:enabled] %>
|
||||
<div class="flex h-9 absolute top-5 items-center invisible group-hover:visible">
|
||||
<% if ChatwootApp.enterprise? %>
|
||||
<%= render 'upgrade_button_enterprise' %>
|
||||
<% else %>
|
||||
<%= render 'upgrade_button_community' %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="flex items-center justify-between mb-1.5 mt-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<h3 class="text-n-slate-12 font-medium"><%= attrs[:name] %></h3>
|
||||
<% if attrs[:enterprise] %>
|
||||
<span class="px-2 h-4 leading-4 rounded-xl text-green-800 font-medium bg-green-100/70 text-xxs">EE</span>
|
||||
<% end %>
|
||||
<% if attrs[:config_key].present? && attrs[:enabled] %>
|
||||
<a class="inline-flex items-center justify-center h-5 w-5 hover:bg-slate-50 rounded-sm" href="/super_admin/app_config?config=<%= attrs[:config_key] %>">
|
||||
<svg width="18" height="18"><use xlink:href="#icon-gear" /></svg>
|
||||
</a>
|
||||
<% end %>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||
<% SuperAdmin::FeaturesHelper.available_features.each do |feature, attrs| %>
|
||||
<div class="group outline outline-1 outline-n-container p-4 rounded-xl relative shadow-sm bg-n-solid-2">
|
||||
<span class="<%= attrs[:enabled] ? 'bg-green-400 text-white': 'bg-slate-300 text-slate-800' %> absolute rounded-full p-1 inline-flex right-4 top-5">
|
||||
<svg width="14" height="14"><use xlink:href="<%= attrs[:enabled] ? '#icon-tick-line': '#icon-lock-line' %>" /></svg>
|
||||
</span>
|
||||
<div class="visible <% if !attrs[:enabled] %> group-hover:invisible <% end %> flex items-center justify-center w-10 h-10 mb-2 border border-n-weak border-solid text-n-slate-11 rounded-full">
|
||||
<svg width="20" height="20"><use xlink:href="#<%= attrs[:icon] %>" /></svg>
|
||||
</div>
|
||||
<% if !attrs[:enabled] %>
|
||||
<div class="flex h-9 absolute top-5 items-center invisible group-hover:visible">
|
||||
<% if ChatwootApp.enterprise? %>
|
||||
<%= render 'upgrade_button_enterprise' %>
|
||||
<% else %>
|
||||
<%= render 'upgrade_button_community' %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="flex items-center justify-between mb-1.5 mt-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<h3 class="text-n-slate-12 font-medium"><%= attrs[:name] %></h3>
|
||||
<% if attrs[:enterprise] %>
|
||||
<span class="px-2 h-4 leading-4 rounded-xl text-green-800 font-medium bg-green-100/70 text-xxs">EE</span>
|
||||
<% end %>
|
||||
<% if attrs[:config_key].present? && attrs[:enabled] %>
|
||||
<a class="inline-flex items-center justify-center h-5 w-5 hover:bg-n-alpha-2 rounded-sm" href="/super_admin/app_config?config=<%= attrs[:config_key] %>">
|
||||
<svg width="18" height="18"><use xlink:href="#icon-gear" /></svg>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-n-slate-11 mb-0 text-sm"><%= attrs[:description] %></p>
|
||||
</div>
|
||||
<p class="text-n-slate-11 mb-0"><%= attrs[:description] %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</section>
|
||||
|
||||
@@ -11,44 +11,18 @@ Used by both account and user show pages.
|
||||
- `resource_type`: Either 'account' or 'user' to determine hidden field
|
||||
%>
|
||||
|
||||
<section class="main-content__body">
|
||||
<% account_user_page = Administrate::Page::Form.new(AccountUserDashboard.new, AccountUser.new) %>
|
||||
<%= form_for([namespace, account_user_page.resource], html: { class: "form" }) do |f| %>
|
||||
<% if account_user_page.resource.errors.any? %>
|
||||
<div id="error_explanation">
|
||||
<h2>
|
||||
<%= t(
|
||||
"administrate.form.errors",
|
||||
pluralized_errors: pluralize(
|
||||
account_user_page.resource.errors.count,
|
||||
t("administrate.form.error")
|
||||
),
|
||||
resource_name: display_resource_name(account_user_page.resource_name)
|
||||
) %>
|
||||
</h2>
|
||||
|
||||
<ul>
|
||||
<% account_user_page.resource.errors.full_messages.each do |message| %>
|
||||
<li class="flash-error"><%= message %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% account_user_page = Administrate::Page::Form.new(AccountUserDashboard.new, AccountUser.new) %>
|
||||
<%= form_for([namespace, account_user_page.resource]) do |f| %>
|
||||
<div class="flex items-end gap-4 flex-wrap">
|
||||
<% account_user_page.attributes.each do |title, attributes| -%>
|
||||
<% attributes.each do |attribute| %>
|
||||
<% if attribute.name == resource_type %>
|
||||
<% if attribute.name.to_s == resource_type %>
|
||||
<%= f.hidden_field("#{resource_type}_id", value: page.resource.id) %>
|
||||
<% else %>
|
||||
<div class="field-unit field-unit--<%= attribute.html_class %> field-unit--<%= requireness(attribute) %>">
|
||||
<%= render_field attribute, f: f %>
|
||||
</div>
|
||||
<%= render_field attribute, f: f %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end -%>
|
||||
|
||||
<div class="form-actions">
|
||||
<%= f.submit %>
|
||||
</div>
|
||||
<% end %>
|
||||
</section>
|
||||
<%= f.submit "Add User", class: "inline-flex items-center justify-center h-9 px-4 text-sm font-medium text-white bg-woot-500 rounded-lg hover:bg-woot-600 transition-colors cursor-pointer" %>
|
||||
</div>
|
||||
<% end %>
|
||||
@@ -18,78 +18,80 @@ to display a collection of resources in an HTML table.
|
||||
[1]: http://www.rubydoc.info/gems/administrate/Administrate/Page/Collection
|
||||
%>
|
||||
|
||||
<table aria-labelledby="<%= table_title %>">
|
||||
<thead>
|
||||
<tr>
|
||||
<% collection_presenter.attribute_types.each do |attr_name, attr_type| %>
|
||||
<th class="cell-label
|
||||
cell-label--<%= attr_type.html_class %>
|
||||
cell-label--<%= collection_presenter.ordered_html_class(attr_name) %>"
|
||||
scope="col"
|
||||
role="columnheader"
|
||||
aria-sort="<%= sort_order(collection_presenter.ordered_html_class(attr_name)) %>">
|
||||
<%= link_to(sanitized_order_params(page, collection_field_name).merge(
|
||||
collection_presenter.order_params_for(attr_name, key: collection_field_name)
|
||||
)) do %>
|
||||
<%= t(
|
||||
"helpers.label.#{collection_presenter.resource_name}.#{attr_name}",
|
||||
default: attr_name.to_s,
|
||||
).titleize %>
|
||||
<% if collection_presenter.ordered_by?(attr_name) %>
|
||||
<span class="cell-label__sort-indicator cell-label__sort-indicator--<%= collection_presenter.ordered_html_class(attr_name) %>">
|
||||
<svg aria-hidden="true">
|
||||
<use xlink:href="#icon-up-caret" />
|
||||
</svg>
|
||||
</span>
|
||||
<div class="rounded-xl bg-n-solid-2 shadow outline-1 outline outline-n-container overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full" aria-labelledby="<%= table_title %>">
|
||||
<thead class="bg-n-slate-1">
|
||||
<tr>
|
||||
<% collection_presenter.attribute_types.each do |attr_name, attr_type| %>
|
||||
<th class="text-left py-3 px-4 text-sm font-medium text-n-slate-11"
|
||||
scope="col"
|
||||
role="columnheader"
|
||||
aria-sort="<%= sort_order(collection_presenter.ordered_html_class(attr_name)) %>">
|
||||
<%= link_to(sanitized_order_params(page, collection_field_name).merge(
|
||||
collection_presenter.order_params_for(attr_name, key: collection_field_name)
|
||||
), class: "inline-flex items-center gap-1 hover:text-n-slate-12") do %>
|
||||
<%= t(
|
||||
"helpers.label.#{collection_presenter.resource_name}.#{attr_name}",
|
||||
default: attr_name.to_s,
|
||||
).titleize %>
|
||||
<% if collection_presenter.ordered_by?(attr_name) %>
|
||||
<span class="<%= collection_presenter.ordered_html_class(attr_name) == 'asc' ? '' : 'rotate-180' %>">
|
||||
<svg class="w-3 h-3" aria-hidden="true">
|
||||
<use xlink:href="#icon-up-caret" />
|
||||
</svg>
|
||||
</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</th>
|
||||
<% end %>
|
||||
<% [existing_action?(collection_presenter.resource_name, :edit),
|
||||
existing_action?(collection_presenter.resource_name, :destroy)].count(true).times do %>
|
||||
<th scope="col" class="py-3 px-4"></th>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody class="divide-y divide-n-weak">
|
||||
<% resources.each do |resource| %>
|
||||
<tr class="js-table-row hover:bg-n-alpha-1 transition-colors"
|
||||
tabindex="0"
|
||||
<% if existing_action? collection_presenter.resource_name, :show %>
|
||||
<%= %(role=link data-url=#{polymorphic_path([namespace, resource.becomes(User)])}) %>
|
||||
<% end %>
|
||||
>
|
||||
<% collection_presenter.attributes_for(resource).each do |attribute| %>
|
||||
<td class="py-3 px-4 text-sm text-n-slate-12">
|
||||
<% if authorized_action? resource, :show -%>
|
||||
<a href="<%= polymorphic_path([namespace, resource.becomes(User)]) -%>"
|
||||
class="hover:underline"
|
||||
>
|
||||
<%= render_field attribute %>
|
||||
</a>
|
||||
<% end -%>
|
||||
</td>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</th>
|
||||
<% end %>
|
||||
<% [existing_action?(collection_presenter.resource_name, :edit),
|
||||
existing_action?(collection_presenter.resource_name, :destroy)].count(true).times do %>
|
||||
<th scope="col"></th>
|
||||
<% end %>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<% resources.each do |resource| %>
|
||||
<tr class="js-table-row"
|
||||
tabindex="0"
|
||||
<% if existing_action? collection_presenter.resource_name, :show %>
|
||||
<%= %(role=link data-url=#{polymorphic_path([namespace, resource.becomes(User)])}) %>
|
||||
<% end %>
|
||||
>
|
||||
<% collection_presenter.attributes_for(resource).each do |attribute| %>
|
||||
<td class="cell-data cell-data--<%= attribute.html_class %>">
|
||||
<% if authorized_action? resource, :show -%>
|
||||
<a href="<%= polymorphic_path([namespace, resource.becomes(User)]) -%>"
|
||||
class="action-show"
|
||||
>
|
||||
<%= render_field attribute %>
|
||||
</a>
|
||||
<% end -%>
|
||||
</td>
|
||||
<% end %>
|
||||
<% if existing_action? collection_presenter.resource_name, :edit %>
|
||||
<td class="py-3 px-4 text-right"><%= link_to(
|
||||
t("administrate.actions.edit"),
|
||||
[:edit, namespace, resource.becomes(User)],
|
||||
class: "text-sm text-woot-500 hover:text-woot-600 hover:underline",
|
||||
) if authorized_action? resource, :edit%></td>
|
||||
<% end %>
|
||||
|
||||
<% if existing_action? collection_presenter.resource_name, :edit %>
|
||||
<td><%= link_to(
|
||||
t("administrate.actions.edit"),
|
||||
[:edit, namespace, resource.becomes(User)],
|
||||
class: "action-edit",
|
||||
) if authorized_action? resource, :edit%></td>
|
||||
<% if existing_action? collection_presenter.resource_name, :destroy %>
|
||||
<td class="py-3 px-4 text-right"><%= link_to(
|
||||
t("administrate.actions.destroy"),
|
||||
[namespace, collection_presenter.resource_name == 'account_user' ? resource : resource.becomes(User)],
|
||||
class: "text-sm text-ruby-500 hover:text-ruby-600 hover:underline",
|
||||
method: :delete,
|
||||
data: { confirm: t("administrate.actions.confirm") }
|
||||
) if authorized_action? resource, :destroy %></td>
|
||||
<% end %>
|
||||
</tr>
|
||||
<% end %>
|
||||
|
||||
<% if existing_action? collection_presenter.resource_name, :destroy %>
|
||||
<td><%= link_to(
|
||||
t("administrate.actions.destroy"),
|
||||
[namespace, collection_presenter.resource_name == 'account_user' ? resource : resource.becomes(User)],
|
||||
class: "text-color-red",
|
||||
method: :delete,
|
||||
data: { confirm: t("administrate.actions.confirm") }
|
||||
) if authorized_action? resource, :destroy %></td>
|
||||
<% end %>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<section class="main-content__body">
|
||||
<hr/>
|
||||
<div class="form-actions">
|
||||
<p class="text-color-red">Caution: Any actions executed after impersonate will appear as if performed by the impersonated user - [<%= page.resource.name %> ]</p>
|
||||
<a class='button' target='_blank' href='<%= page.resource.generate_sso_link_with_impersonation %>'>Impersonate user </a>
|
||||
</div>
|
||||
</section>
|
||||
<div class="px-6 py-4">
|
||||
<h3 class="text-sm font-medium text-n-slate-12 mb-2">Impersonate User</h3>
|
||||
<p class="text-xs text-ruby-500 mb-3">Caution: Any actions executed after impersonate will appear as if performed by the impersonated user - [<%= page.resource.name %>]</p>
|
||||
<a target="_blank" href="<%= page.resource.generate_sso_link_with_impersonation %>" class="inline-flex items-center justify-center h-8 px-3 text-sm font-medium text-n-slate-12 bg-n-alpha-2 rounded-lg hover:bg-n-alpha-3 transition-colors">
|
||||
Impersonate user
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -18,44 +18,151 @@ as well as a link to its edit page.
|
||||
|
||||
<% content_for(:title) { t("administrate.actions.show_resource", name: page.page_title) } %>
|
||||
|
||||
<header class="main-content__header" role="banner">
|
||||
<h1 class="main-content__page-title">
|
||||
<%= content_for(:title) %>
|
||||
</h1>
|
||||
<section class="flex flex-col w-full h-full">
|
||||
<header class="sticky top-0 z-10 bg-n-background border-b border-n-weak" role="banner">
|
||||
<div class="flex items-center justify-between w-full h-20 mx-auto max-w-[60rem]">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="border border-n-weak p-2 rounded-full text-n-slate-11">
|
||||
<svg width="24" height="24"><use xlink:href="#icon-user-follow-line" /></svg>
|
||||
</div>
|
||||
<div class="flex flex-col justify-center">
|
||||
<h1 class="text-base font-medium tracking-tight text-n-slate-12">
|
||||
<%= content_for(:title) %>
|
||||
</h1>
|
||||
<p class="text-xs text-n-slate-11 m-0">Profile, accounts, and access control</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<%= link_to(
|
||||
<div class="flex items-center gap-2">
|
||||
<% if authorized_action?(page.resource, :edit) %>
|
||||
<%= link_to(
|
||||
t("administrate.actions.edit"),
|
||||
[:edit, namespace, page.resource.becomes(User)],
|
||||
class: "button",
|
||||
) if authorized_action? page.resource, :edit %>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="main-content__body">
|
||||
<dl>
|
||||
<% page.attributes.each do |title, attributes| %>
|
||||
<fieldset class="<%= "field-unit--nested" if title.present? %>">
|
||||
<% if title.present? %>
|
||||
<legend><%= t "helpers.label.#{page.resource_name}.#{title}", default: title %></legend>
|
||||
<% end %>
|
||||
|
||||
<% attributes.each do |attribute| %>
|
||||
<dt class="attribute-label" id="<%= attribute.name %>">
|
||||
<%= t(
|
||||
"helpers.label.#{resource_name}.#{attribute.name}",
|
||||
default: page.resource.class.human_attribute_name(attribute.name),
|
||||
[:edit, namespace, page.resource.becomes(User)],
|
||||
class: "inline-flex items-center justify-center h-8 px-3 text-sm font-medium text-white bg-woot-500 rounded-lg hover:bg-woot-600 transition-colors"
|
||||
) %>
|
||||
</dt>
|
||||
|
||||
<dd class="attribute-data attribute-data--<%=attribute.html_class%>"
|
||||
><%= render_field attribute, page: page %></dd>
|
||||
<% end %>
|
||||
</fieldset>
|
||||
<% end %>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="flex-1 px-6 overflow-y-auto">
|
||||
<div class="w-full mx-auto max-w-[60rem] py-6 space-y-6">
|
||||
|
||||
<%# User Information Card %>
|
||||
<div class="rounded-xl bg-n-solid-2 shadow outline-1 outline outline-n-container overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-n-weak">
|
||||
<h2 class="text-base font-medium text-n-slate-12">User Information</h2>
|
||||
</div>
|
||||
<div class="divide-y divide-n-weak">
|
||||
<% page.attributes.each do |title, attributes| %>
|
||||
<% next if title.present? %>
|
||||
<% attributes.each do |attribute| %>
|
||||
<% next if ['account_users', 'custom_attributes'].include?(attribute.name.to_s) %>
|
||||
<div class="grid grid-cols-[180px,1fr] items-center min-h-12 px-6">
|
||||
<span class="text-sm text-n-slate-11">
|
||||
<%= t(
|
||||
"helpers.label.#{resource_name}.#{attribute.name}",
|
||||
default: page.resource.class.human_attribute_name(attribute.name),
|
||||
) %>
|
||||
</span>
|
||||
<span class="text-sm text-n-slate-12 py-3">
|
||||
<%= render_field attribute, page: page %>
|
||||
</span>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%# Account Users Card %>
|
||||
<% page.attributes.each do |title, attributes| %>
|
||||
<% next if title.present? %>
|
||||
<% attributes.each do |attribute| %>
|
||||
<% if attribute.name.to_s == 'account_users' && page.resource.account_users.any? %>
|
||||
<div class="rounded-xl bg-n-solid-2 shadow outline-1 outline outline-n-container overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-n-weak">
|
||||
<h2 class="text-base font-medium text-n-slate-12">Account Users</h2>
|
||||
</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead class="bg-n-slate-1">
|
||||
<tr>
|
||||
<th class="text-left py-3 px-6 text-sm font-medium text-n-slate-11">Account</th>
|
||||
<th class="text-left py-3 px-6 text-sm font-medium text-n-slate-11">User</th>
|
||||
<th class="text-left py-3 px-6 text-sm font-medium text-n-slate-11">Inviter</th>
|
||||
<th class="text-left py-3 px-6 text-sm font-medium text-n-slate-11">Role</th>
|
||||
<th class="text-left py-3 px-6 text-sm font-medium text-n-slate-11"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-n-weak">
|
||||
<% page.resource.account_users.each do |account_user| %>
|
||||
<tr class="hover:bg-n-alpha-1">
|
||||
<td class="py-3 px-6 text-sm text-n-slate-12">
|
||||
<%= link_to "##{account_user.account_id} #{account_user.account.name}", super_admin_account_path(account_user.account), class: "text-n-slate-12 hover:underline" %>
|
||||
</td>
|
||||
<td class="py-3 px-6 text-sm text-n-slate-12">
|
||||
<%= link_to "##{account_user.user_id} #{account_user.user.name}", super_admin_user_path(account_user.user), class: "text-n-slate-12 hover:underline" %>
|
||||
</td>
|
||||
<td class="py-3 px-6 text-sm text-n-slate-11">
|
||||
<% if account_user.inviter.present? %>
|
||||
<%= link_to account_user.inviter.name, super_admin_user_path(account_user.inviter), class: "text-n-slate-12 hover:underline" %>
|
||||
<% else %>
|
||||
<span class="text-n-slate-10">---</span>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="py-3 px-6">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-md text-xs font-medium bg-n-alpha-2 text-n-slate-12 capitalize">
|
||||
<%= account_user.role %>
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-3 px-6 text-right">
|
||||
<%= link_to "Destroy", super_admin_account_user_path(account_user), method: :delete, data: { confirm: "Are you sure?" }, class: "text-sm text-ruby-500 hover:text-ruby-600 hover:underline" %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<%# Custom Attributes Card %>
|
||||
<% if page.resource.try(:custom_attributes).present? %>
|
||||
<div class="rounded-xl bg-n-solid-2 shadow outline-1 outline outline-n-container overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-n-weak">
|
||||
<h2 class="text-base font-medium text-n-slate-12">Custom Attributes</h2>
|
||||
</div>
|
||||
<div class="divide-y divide-n-weak">
|
||||
<% page.resource.custom_attributes.each do |key, value| %>
|
||||
<div class="grid grid-cols-[180px,1fr] items-start min-h-12 px-6">
|
||||
<span class="text-sm text-n-slate-11 py-3">
|
||||
<%= key.to_s.titleize %>
|
||||
</span>
|
||||
<span class="text-sm text-n-slate-12 py-3 break-all font-mono">
|
||||
<%= value %>
|
||||
</span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%# Actions Card %>
|
||||
<div class="rounded-xl bg-n-solid-2 shadow outline-1 outline outline-n-container overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-n-weak">
|
||||
<h2 class="text-base font-medium text-n-slate-12">Actions</h2>
|
||||
</div>
|
||||
<div class="divide-y divide-n-weak">
|
||||
<div class="px-6 py-4">
|
||||
<h3 class="text-sm font-medium text-n-slate-12 mb-3">Add User to Account</h3>
|
||||
<%= render 'super_admin/shared/account_user_form', page: page, namespace: namespace, resource_type: 'user' %>
|
||||
</div>
|
||||
<%= render partial: "impersonate", locals: {page: page} %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
</section>
|
||||
|
||||
<%= render 'super_admin/shared/account_user_form', page: page, namespace: namespace, resource_type: 'user' %>
|
||||
|
||||
<%= render partial: "impersonate", locals: {page: page} %>
|
||||
|
||||
Reference in New Issue
Block a user