Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ui: Add UI for Roles in TpStreams #118

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/_includes/layouts/tpstreams/full_page_layout.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{% extends "layouts/tpstreams/base.html" %}

{% block html_class %}h-full bg-white{% endblock html_class %}

{% block content %}
<div x-data="{open: false}" @keydown.window.escape="open = false">
{% include "partials/tpstreams/sidebar.html" %}
<div class="lg:pl-64 lg:px-8 bg-white">
<div class="mx-auto flex flex-col max-w-5xl 2xl:max-w-screen-2xl">
<main class="py-5 sm:py-10 relative">
<div class="mx-auto max-w-7xl 2xl:max-w-screen-2xl px-4 sm:px-6 lg:px-8">
{% block main_content %}
{% endblock main_content %}
</div>
</main>
</div>
</div>
</div>
{% endblock content %}
35 changes: 35 additions & 0 deletions src/tpstreams/roles/detail.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
title: Staff User - Roles
permalink: "tpstreams/roles/staff-user/"
date: 2024-12-10
---

{% extends "layouts/tpstreams/full_page_layout.html" %}

{% block html_class %}h-full bg-gray-100{% endblock html_class %}

{% block body_class %}overflow-y-scroll min-h-screen bg-white{% endblock %}

{% block main_content %}
<div x-data="roleDetailData">
{% include "./includes/alert.html" %}
<div class="max-w-7xl mx-auto">
{% include './includes/detail_header.html' %}
<main class="pt-8">
<div class="px-4 sm:px-6 lg:px-8 pb-6 bg-white border-b border-t border-gray-200 shadow sm:rounded-lg">
<div class="xl:grid xl:grid-cols-3">
{% include "./includes/permission_table.html" %}
{% include "./includes/assign_role_users.html" %}
</div>
</div>
</main>

{% include './includes/modals/remove_user_modal.html' %}
</div>
</div>
{% endblock %}

{% block script %}
{{block.super}}
{% include "./includes/role_detail_scripts.html" %}
{% endblock script %}
24 changes: 24 additions & 0 deletions src/tpstreams/roles/includes/alert.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<div class="w-full absolute flex flex-col top-20 items-center gap-2 z-20">
<div x-cloak x-show="showAlert" class="rounded bg-green-50 border border-green-300 p-3">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-green-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
</svg>
</div>
<div class="ml-3">
<p class="text-sm font-medium text-green-800" x-text="alertText"></p>
</div>
<div class="ml-auto pl-3">
<div class="h-5 w-5">
<button type="button" @click="showAlert=false" class="inline-flex bg-green-50 rounded-sm text-green-500 hover:bg-green-100 focus:outline-none focus:ring-2 focus:ring-offset-green-50 focus:ring-green-600">
<span class="sr-only">Dismiss</span>
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
97 changes: 97 additions & 0 deletions src/tpstreams/roles/includes/assign_role_users.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<aside class="xl:pl-8">
<h2 class="sr-only">Users</h2>
<div class="space-y-8 border-gray-200 py-6">
<div>
<div class="flex justify-between">
<div class="sm:flex-auto">
<h1 class="text-base font-semibold leading-6 text-gray-900">Assign Users</h1>
<p class="mt-2 text-sm text-gray-700">Users who inherit this role and its permissions.</p>
</div>
<div>
<button type="button"
class="-my-2 p-2 rounded-full bg-white flex items-center text-gray-400 hover:text-gray-600"
:class="{'outline-none ring-1 ring-gray-300': showAddAssigneeInputBox}"
id="menu-3-button" x-ref="button" @click="showAddAssigneeInputBox = !showAddAssigneeInputBox"
aria-expanded="false" aria-haspopup="true" x-bind:aria-expanded="open.toString()">
<span class="sr-only">Add Assignee</span>
<svg class="h-5 w-5" :class="{'rotate-45': showAddAssigneeInputBox}"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
</button>
</div>
</div>

<div class="relative">
<div>
<input
x-cloak x-show="assignedUsers.length"
x-model="assignedUserSearchText"
type="text" name="name" id="name"
class="mt-3 rounded block w-full border-0 border-transparent focus:ring-0 bg-gray-100 text-sm"
placeholder="Search assigned users">
<ul role="list" class="divide-y divide-gray-100">
<template x-for="user in getFilteredAssignedUsers">
<li class="flex items-center justify-between gap-x-6 py-2">
<div class="flex min-w-0 gap-x-4">
<div class="min-w-0 flex-auto">
<p class="text-sm font-semibold leading-6 text-gray-900" x-text="user.display_name"></p>
<p class="truncate text-xs leading-5 text-gray-500" x-text="user.email"></p>
</div>
</div>
<a href="#" @click.prevent="selectUserToRemove(user.id)" class="rounded-full bg-white px-1 py-1 text-xs font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.0" stroke="currentColor" class="font-semibold w-3 h-3">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</a>
</li>
</template>
</ul>
</div>

<div x-cloak x-show="showAddAssigneeInputBox" x-transition:leave="transition ease-in duration-100"
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" class="absolute top-0 w-full z-10
bg-white overflow-hidden shadow rounded-lg
shadow-lg rounded-md ring-1 ring-black ring-opacity-5 opacity-100 transition ease-in duration-100
">
<div class="px-2">
<div class="py-3 text-sm text-gray-800 px-3">
Assign this role to a user
</div>
<input type="text" name="name" id="name"
class="rounded block w-full border-0 border-transparent focus:ring-0 bg-gray-100 text-sm"
placeholder="Search users">

<ul class="max-h-56 overflow-auto text-sm my-3" tabindex="-1" role="listbox"
aria-labelledby="listbox-label" aria-activedescendant="listbox-option-3">
<template hidden x-for="user in allUsers">
<li
class="rounded cursor-pointer hover:bg-gray-100 text-gray-900 cursor-default select-none relative py-2 pl-3 pr-9"
id="listbox-option-0" role="option" @click="onUserSelect(user)">
<div class="flex items-center">
<!-- Selected: "font-semibold", Not Selected: "font-normal" -->
<span class="block truncate" :class="{'font-medium': isUserSelected(user)}"
x-text="user.display_name">
</span>
</div>

<span x-show="isUserSelected(user)"
class="text-indigo-600 absolute inset-y-0 right-0 flex items-center pr-4">
<!-- Heroicon name: solid/check -->
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clip-rule="evenodd" />
</svg>
</span>
</li>
</template>
</ul>
</div>
</div>
</div>
</div>
</div>
</aside>
56 changes: 56 additions & 0 deletions src/tpstreams/roles/includes/detail_header.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<div x-data="{editRole: false, editedRoleName: roleName}" class="lg:flex lg:items-center lg:justify-between px-4 sm:px-0 lg:px-0">
<div class="min-w-0 flex-1">
<nav class="flex" aria-label="Breadcrumb">
<ol role="list" class="flex items-center space-x-4">
<li>
<div class="flex">
<a href="{{ '/tpstreams/settings'|url }}" class="text-sm font-medium text-gray-500 hover:text-gray-700">Settings</a>
</div>
</li>
<li>
<div class="flex items-center">
<svg class="h-5 w-5 flex-shrink-0 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" />
</svg>
<a href="{{ '/tpstreams/roles'|url }}" class="ml-4 text-sm font-medium text-gray-500 hover:text-gray-700">Roles</a>
</div>
</li>
</ol>
</nav>
<h2 class="mt-2 text-2xl font-bold leading-7 text-gray-900 sm:truncate sm:text-3xl sm:tracking-tight">
<input x-cloak x-show="editRole" type="text" name="role_name" id="roleNameInput" class="rounded block w-full border-0 border-transparent focus:ring-0 bg-gray-200" x-model="editedRoleName">
<span x-cloak x-show="!editRole" x-text="roleName"></span>
</h2>
<div class="mt-1 flex flex-col sm:mt-0 sm:flex-row sm:flex-wrap sm:space-x-6">
<div class="mt-2 flex items-center text-sm text-gray-500">
<svg class="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400" viewBox="0 0 20 20" fill="currentColor">
<path d="M10 8a3 3 0 100-6 3 3 0 000 6zM3.465 14.493a1.23 1.23 0 00.41 1.412A9.957 9.957 0 0010 18c2.31 0 4.438-.784 6.131-2.1.43-.333.604-.903.408-1.41a7.002 7.002 0 00-13.074.003z" />
</svg>
{{ roles_list[0]["number_of_users"] }} Users
</div>
<div class="mt-2 flex items-center text-sm text-gray-500">
<svg class="mr-1.5 h-5 w-5 flex-shrink-0 text-gray-400" viewBox="0 0 20 20" fill="currentColor">
<path d="M10 3.75a2 2 0 10-4 0 2 2 0 004 0zM17.25 4.5a.75.75 0 000-1.5h-5.5a.75.75 0 000 1.5h5.5zM5 3.75a.75.75 0 01-.75.75h-1.5a.75.75 0 010-1.5h1.5a.75.75 0 01.75.75zM4.25 17a.75.75 0 000-1.5h-1.5a.75.75 0 000 1.5h1.5zM17.25 17a.75.75 0 000-1.5h-5.5a.75.75 0 000 1.5h5.5zM9 10a.75.75 0 01-.75.75h-5.5a.75.75 0 010-1.5h5.5A.75.75 0 019 10zM17.25 10.75a.75.75 0 000-1.5h-1.5a.75.75 0 000 1.5h1.5zM14 10a2 2 0 10-4 0 2 2 0 004 0zM10 16.25a2 2 0 10-4 0 2 2 0 004 0z" />
</svg>
<span class="mr-0.5">
<a href="#" class="font-medium text-gray-900">{{ roles_list[0]["modified_by"] }}</a>
modified on {{ roles_list[0]["modified"] }}
</span>
</div>
</div>
</div>
<div class="mt-5 flex lg:ml-4 lg:mt-0">
<span class="" x-cloak x-show="!editRole">
<button @click="editRole = true" type="button" class="inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50">
<svg class="-ml-0.5 mr-1.5 h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path d="M2.695 14.763l-1.262 3.154a.5.5 0 00.65.65l3.155-1.262a4 4 0 001.343-.885L17.5 5.5a2.121 2.121 0 00-3-3L3.58 13.42a4 4 0 00-.885 1.343z" />
</svg>
Edit
</button>
</span>
<div class="flex items-center justify-end gap-x-4" x-cloak x-show="editRole">
<button @click="roleName = editedRoleName; editRole = false;" type="submit" class="rounded-md bg-blue-700 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600">Save</button>
<button type="button" class="text-sm font-semibold leading-6 text-gray-900" @click="editedRoleName=roleName; editRole = false;">Cancel</button>
</div>
</div>
</div>
32 changes: 32 additions & 0 deletions src/tpstreams/roles/includes/modals/create_role_modal.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<div x-cloak x-show="showCreateRoleModal" @keydown.window.escape="showCreateRoleModal = false" class="relative z-10" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"></div>

<div class="fixed inset-0 z-10 w-screen overflow-y-auto">
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<div class="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
<form>
<div class="space-y-12">
<div class="">
<h2 class="text-base font-semibold leading-7 text-gray-900">Create a new role</h2>
<p class="mt-1 text-sm leading-6 text-gray-600">Upon successful submission of this form you will be able to manage permissions and assign users for the newly created role.</p>

<div class="mt-6 grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
<div class="col-span-full">
<label for="role-name" class="block text-sm font-medium leading-6 text-gray-900">Name</label>
<div class="mt-2">
<input type="text" name="role-name" id="role-name" autocomplete="given-name" class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6">
</div>
</div>
</div>
</div>
</div>

<div class="mt-6 flex items-center justify-end gap-x-6">
<button @click="showCreateRoleModal=false" type="button" class="text-sm font-semibold leading-6 text-gray-900">Cancel</button>
<a href="{{ '/tpstreams/roles/staff-user/'|url }}" type="submit" class="rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600">Submit</a>
</div>
</form>
</div>
</div>
</div>
</div>
52 changes: 52 additions & 0 deletions src/tpstreams/roles/includes/modals/delete_role_modal.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<div x-cloak x-show="showDeleteModal" @keydown.window.escape="showDeleteModal = false" class="relative z-10" aria-labelledby="modal-title" role="dialog"
x-init="$watch('confirmedRoleName', value => validateConfirmedRoleName())"
aria-modal="true">
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"></div>

<div class="fixed inset-0 z-10 w-screen overflow-y-auto">
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<div class="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6">
<div>
<div class="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-red-100">
<svg class="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
</svg>
</div>
<div class="mt-3 text-center sm:mt-5">
<h3 class="text-base font-semibold leading-6 text-gray-900" id="modal-title">Delete role "<span x-text="selectedRoleToDelete.name"></span>"?</h3>
<div class="mt-2">
<p class="text-sm text-gray-500">Assigned users will lose the permissions granted by this role, unless the same permissions are also granted by another existing role.</p>
</div>
<div class="mt-4">
<label for="email" class="block text-sm font-medium leading-6 text-gray-900">To confirm deletion, enter the name of the role in the text input field.</label>
<div class="relative mt-2 rounded-md shadow-sm">
<input type="text" name="delete_role_name" id="delete_role_name"
x-model="confirmedRoleName"
class="pr-10 block w-full rounded-md border-0 py-1.5 ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6" aria-invalid="true" aria-describedby="delete_role_name-error"
:placeholder="selectedRoleToDelete.name"
:class="{'text-gray-900 shadow-sm ring-gray-300 placeholder:text-gray-400 focus:ring-indigo-600': !hasDeleteError, 'pr-10 text-red-900 ring-red-300 placeholder:text-red-300 focus:ring-red-500': hasDeleteError}">
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
<svg class="h-5 w-5 text-red-500" :class="{'hidden': !hasDeleteError}" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd" />
</svg>
</div>
</div>
<p class="mt-2 text-sm text-red-600" :class="{'hidden': !hasDeleteError}" id="delete_role_name-error">The entered name does not match the role's name.</p>
</div>
</div>
</div>
<div class="mt-5 sm:mt-6 sm:grid sm:grid-flow-row-dense sm:grid-cols-2 sm:gap-3">
<button type="button" @click="deleteRole()"
:disabled="!canDeleteRole"
class="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600 sm:col-start-2 disabled:opacity-25 disabled:cursor-not-allowed">
Delete
</button>
<button type="button" @click="selectedRoleToDelete=null; showDeleteModal=false"
class="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:col-start-1 sm:mt-0">
Cancel
</button>
</div>
</div>
</div>
</div>
</div>
Loading
Loading