كيفية تكييف UX / UI تحت أذونات

العديد من المشاريع لديها عمليات المصادقة (إلى درجة أو أخرى). تمت كتابة العديد من أفضل الممارسات في جميع التقنيات المعروفة ، إلخ. الخ


لكن المستخدم قدم تسجيل الدخول و؟ بعد كل شيء ، لا يستطيع أن يفعل كل شيء. كيفية تحديد ما يمكن أن يرى وما لا. الأزرار التي لها الحق في الضغط عليها ، والتي يمكن أن تغير ما لإنشاء أو حذف.


في هذه المقالة ، أود التفكير في طريقة لحل هذه المشكلات على تطبيقات الويب.


الصورة


بادئ ذي بدء ، يمكن أن يحدث إذن حقيقي / فعال على الخادم فقط. في Front End ، يمكننا فقط تحسين واجهة المستخدم (هم ، اللغة الروسية قوية ، ولكن ليس دائمًا ...) ، بل هي أيضًا تجربة المستخدم خارج UX. يمكننا إخفاء الأزرار التي لا يتمتع المستخدم بحق النقر عليها أو منعه من الوصول إلى الصفحة أو إظهار رسالة بأنه لا يملك حقوقًا في هذا الإجراء أو ذاك.


وهنا السؤال الذي يطرح نفسه ، كيف نفعل ذلك بشكل صحيح قدر الإمكان.


لنبدأ بتحديد المشكلة.


أنشأنا تطبيق Todo ولديه أنواع مختلفة من المستخدمين:


USER - يمكنه رؤية جميع المهام وتغييرها (تعيين V وإزالتها) ، ولكن لا يمكن حذفها أو إنشاؤها ولا ترى إحصائيات.


ADMIN - يمكنه رؤية جميع المهام وإنشاء مهام جديدة ، لكن لا يرى إحصائيات.


SUPER_ADMIN - يمكنه رؤية جميع المهام ، وإنشاء جديد وحذف ، كما يمكنه مشاهدة الإحصاءات.


عرض المهمةإنشاء مهمةفحص / إلغاء مهمة (تحديث)حذف المهمةعرض احصائيات
المستخدمالخامساكسالخامساكساكس
المسؤولالخامسالخامسالخامساكساكس
SUPER_ADMINالخامسالخامسالخامسالخامسالخامس

في مثل هذه الحالة ، يمكننا بسهولة الحصول على الأدوار. ومع ذلك ، يمكن أن يتغير الوضع كثيرا. تخيل أن هناك مستخدمًا يجب أن يتمتع بنفس حقوق ADMIN بالإضافة إلى حذف المهام. أو مجرد مستخدم لديه القدرة على رؤية الإحصاءات.


حل بسيط هو خلق أدوار جديدة.
ولكن على الأنظمة الكبيرة ، مع وجود عدد كبير نسبيًا من المستخدمين ، سرعان ما نضيع في عدد كبير من الأدوار ... ونذكر هنا أذونات "حقوق المستخدم". لتسهيل الإدارة ، يمكننا إنشاء مجموعات من العديد من الأذونات وإرفاقها للمستخدم. هناك دائمًا فرصة لإضافة إذن محدد لمستخدم معين.


يمكن إيجاد حلول مماثلة في العديد من الخدمات الرائعة. AWS ، Google Cloud ، SalesForce ، إلخ.
أيضا ، يتم تنفيذ حلول مماثلة بالفعل في العديد من الأطر ، على سبيل المثال ، Django (الثعبان).


أريد أن أعطي مثالا على تطبيق للتطبيقات الزاوي. (على سبيل المثال نفس تطبيق تودو).


تحتاج أولاً إلى تحديد جميع الأذونات الممكنة.


مقسمة إلى ميزات


  1. لدينا المهام والإحصاءات.
  2. نحدد الإجراءات الممكنة مع كل منهم.
    • المهمة: إنشاء ، قراءة ، تحديث ، حذف
    • إحصائيات: قراءة (في مثالنا ، العرض فقط)
  3. قم بإنشاء خريطة (MAP) للأدوار والأذونات

export const permissionsMap = { todos:{ create:'*', read:'*', update:'*', delete:'*' }, stats:'*' } 

في رأيي ، هذا هو الخيار الأفضل ، لكن في معظم الحالات سيعود الخادم بشيء من هذا القبيل:


 export permissions = [ 'todos_create', 'todos_read', 'todos_update', 'todos_delete', 'stas' ] 

أقل قراءة ، ولكن أيضا جيدة جدا.


هذا هو ما يبدو عليه تطبيقنا إذا كان لدى المستخدم جميع الأذونات الممكنة.


الصورة


لنبدأ باستخدام USER ، هذا ما تبدو عليه أذوناته:


 export const USERpermissionsMap = { todos:{ read:'*', update:'*', } } 

1) لا يمكن لـ USER الاطلاع على إحصاءات ، أي من حيث المبدأ ، لا يمكنه الانتقال إلى صفحة الإحصائيات.


لمثل هذه الحالات ، يحتوي Angular على حراس ، يتم تسجيلهم على مستوى الطرق ( الوثائق ).


في حالتنا ، يبدو كما يلي:


 const routes: Routes = [ // {...Other routes}, { path: 'stats', component: TodoStatisticsComponent, canActivate: [ PermissionsGuardService ], data: { permission: 'stats' }, } ]; 

يجب الانتباه إلى الكائن في البيانات.


peremissions = 'stats' ، هذه هي الأذونات التي يجب أن يتمتع بها المستخدم من أجل الوصول إلى هذه الصفحة.


استنادًا إلى data.permissions المطلوبة والأذونات التي منحها لنا خادم PermissionsGuardService ، ستقرر ما إذا كنت ستسمح لـ USER بالانتقال إلى صفحة "/ الإحصائيات" أم لا.


 @Injectable({ providedIn: 'root' }) export class PermissionsGuardService implements CanActivate { constructor( private store: Store<any>) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean { // Required permission to continue navigation const required = route.data.permission; // User permissions that we got from server const userPerms = this.getPermissions(); // verification const isPermitted = checkPermissions(required, userPerms); if (!isPermitted) { alert('ROUTE GUARD SAYS: \n You don\'t have permissions to see this page'); } return isPermitted; } getPermissions() { return localStorage.get('userPermissions') } } 

تحتوي هذه الوظيفة على منطق القرار - هل يوجد إذن غير قابل للاستبدال (مطلوب) بين جميع أذونات USER (userPerms).


 export function checkPermissions(required: string, userPerms) { // 1) Separate feature and action const [feature, action] = required.split('_'); // 2) Check if user have any type of access to the feature if (!userPerms.hasOwnProperty(feature)) { return false; } // 3) Check if user have permission for required action if (!userPerms[feature].hasOwnProperty(action)) { return false; } return true; } 

الصورة


و كذلك. لا يمكن للمستخدم الوصول إلى صفحة الإحصائيات. ومع ذلك ، لا يزال بإمكانه إنشاء المهام وحذفها.


لكي لا يتمكن USER من إزالة المهمة ، ستكون كافية لإزالة علامة (X) الحمراء من سطر المهمة.
لهذا الغرض سوف نستخدم التوجيه الهيكلي .


 <!-- Similar to other Structural Directives in angular (*ngIF, *ngFor..) we have '*' in directive name Input for directive is a required permission to see this btn. --> <button *appPermissions="'todos_delete'" class="delete-button" (click)="removeSingleTodo()"> X </button> 

 @Directive({ selector: '[appPermissions]' }) export class PermissionsDirective { private _required: string; private _viewRef: EmbeddedViewRef<any> | null = null; private _templateRef: TemplateRef<any> | null = null; @Input() set appPermissions(permission: string) { this._required = permission; this._viewRef = null; this.init(); } constructor(private templateRef: TemplateRef<any>, private viewContainerRef: ViewContainerRef) { this._templateRef = templateRef; } init() { const isPermitted = checkPermissions(this._required, this.getPermissions()); if (isPermitted) { this._viewRef = this.viewContainerRef.createEmbeddedView(this.templateRef); } else { console.log('PERMISSIONS DIRECTIVE says \n You don\'t have permissions to see it'); } } getPermissions() { localStorage.get('userPermissions') } } 

الصورة


الآن لا يرى USER زر DELETE ولكن لا يزال بإمكانه إضافة مهام جديدة.


لإزالة حقل الإدخال - سوف تفسد كل نوع من طلبنا. الحل الصحيح في هذه الحالة هو disable حقول الإدخال.


سوف نستخدم الأنابيب.


 <!-- as a value `permissions` pipe will get required permissions `permissions` pipe return true or false, that's why we have !('todos_create' | permissions) to set disable=true if pipe returns false --> <input class="centered-block" [disabled]="!('todos_create' | permissions)" placeholder="What needs to be done?" autofocus/> 

 @Pipe({ name: 'permissions' }) export class PermissionsPipe implements PipeTransform { constructor(private store: Store<any>) { } transform(required: any, args?: any): any { const isPermitted = checkPermissions(required, this.getPermissions()); if (isPermitted) { return true; } else { console.log('[PERMISSIONS PIPE] You don\'t have permissions'); return false; } } getPermissions() { return localStorage.get('userPermissions') } } 

والآن يستطيع المستخدم فقط رؤية المهام وتغييرها (V / X). ومع ذلك ، لا يزال لدينا زر واحد آخر يسمى "مسح مكتمل".


افترض أن لدينا متطلبات إدارة المنتج التالية:


  1. يجب أن يكون الزر "مسح مكتمل" مرئيًا للجميع ودائمًا.
  2. يجب أن يكون أيضًا قابل للنقر.
  3. إذا ضغط USER بدون الأذونات المقابلة على الزر ، فستظهر رسالة.

التوجيه البناء لن يساعدنا ، وكذلك الأنابيب.
كما أنها ليست مريحة للغاية لكتابة أذونات التدقيق في وظائف المكون.


كل ما نحتاج إليه هو إجراء فحص أذونات بين النقر وتنفيذ الوظيفة المنضمة.


في رأيي ، الأمر يستحق استخدام الديكور.


 export function Permissions(required) { return (classProto, propertyKey, descriptor) => { const originalFunction = descriptor.value; descriptor.value = function (...args: any[]) { const userPerms = localStorage.get('userPermissions') const isPermitted = checkPermissions(required, userPerms); if (isPermitted) { originalFunction.apply(this, args); } else { alert('you have no permissions \n [PERMISSIONS DECORATOR]'); } }; return descriptor; }; } 

 @Component({ selector: 'app-actions', templateUrl: './actions.component.html', styleUrls: ['./actions.component.css'] }) export class ActionsComponent implements OnInit { @Output() deleteCompleted = new EventEmitter(); constructor() { } @Permissions('todos_delete') public deleteCompleted() { this.deleteCompleted.emit(); } } 

المتحدثون يستحقون مقالا منفصلا.


النتيجة النهائية لدينا:


الصورة


النتيجة:


يسمح لنا هذا النهج بتكييف UX الخاص بنا بسهولة وبشكل حيوي وفقًا للأذونات التي يتمتع بها المستخدم.


والأهم من ذلك ، نحن لسنا بحاجة إلى تحويل الخدمات إلى جميع المكونات أو المطرقة في قوالب "* ngIf".


تطبيق كامل اكتمال .


سأكون سعيدا للتعليق.

Source: https://habr.com/ru/post/ar439100/


All Articles