How to hide Create Workspace from certain user roles?

Have you found the solution?

Any update?

Removing the ‘Workspace Manager’ role from any particular user would remove the ‘Create workspace’ button as well.

Removing the ‘Workspace Manager’ role from any particular user would remove the ‘Create workspace’ button as well.

This doesn’t work any other solution ?

The only solution that worked for me is to write javascript code in my custom app and put it in hooks so it will be accessible in the desk.

Frappe version - 14

$(document).on(‘DOMNodeInserted’, “#body”, function(e){
$id = $(e.target).attr(“id”);
if($id){
if ($id.localeCompare(‘page-Workspaces’) == 0) {
if(frappe.user_roles.includes(“Sales Manager”)){
$(function() {
$(“#page-Workspaces .page-actions”).html(“”)
})
}
}
}
});

5 Likes

more optimum solution using frappe.router

frappe.router.on('change', () => {
    route = frappe.get_route()
    if(route.length == 2 && !frappe.user_roles.includes("System Manager")){
        if (route[0] == "Workspaces"){
            $("#page-Workspaces .page-actions").html("")
        }
    }
})

4 Likes

Where do you put this function?

1 Like
  1. create .js file inside the public folder in your custom app
  2. add the .js file in the build.json
  3. then run bench build --site [yoursitename]
  4. add the build file in your custom app hooks.py app_include_js
6 Likes

After searching for a lot of time and trying to inject custom css and js to hide those buttons for certain roles nothing really worked
so i changed the core code itself, nothing much only one function.
there is a function called as setup_actions in workspace.js
replace that code with the following code snippet

setup_actions(page) {
let pages = page.public ? this.public_pages : this.private_pages;
let current_page = pages.filter((p) => p.title == page.name)[0];

if (!this.is_read_only) {
    this.setup_customization_buttons(current_page);
    return;
}

this.clear_page_actions();

// Check if the user has the "System Manager" role
frappe.call({
    method: 'bytenba.custom_utilities.get_roles',
			args:{
				"session_user": frappe.session.user
			},
    callback: (r) => {
        if (r.message.includes("System Manager")) {
            this.page.set_secondary_action(
                __("Edit"),
                async () => {
			if (!this.editor || !this.editor.readOnly) return;
			this.is_read_only = false;
			this.toggle_hidden_workspaces(true);
			await this.editor.readOnly.toggle();
			this.editor.isReady.then(() => {
				this.body.addClass("edit-mode");
				this.initialize_editorjs_undo();
				this.setup_customization_buttons(current_page);
				this.show_sidebar_actions();
				this.make_blocks_sortable();
			});
                },
                "es-line-edit"
            );

            // Your "Create Workspace" button logic here
            this.page.add_inner_button(__("Create Workspace"), () => {
                this.initialize_new_page();
            });
        }
    }
});

}

u can also use frappe.session.user.has_role() instead of calling a server method

2 Likes

its generally not advisable to modify core code unless you’re maintaining your own fork. Although i do understand why you had to go this far. i’ve been trying to achieve this for quite a while without success. I hope an easier way to accomplish this without having to go to the extreme is implemented soon so admins can at least have the option to hide the workspace and edit buttons based on role.

2 Likes

@NCP , we appreciate your advice and recommed approach for hiding “CRAETE WORKSPCE and EDIT” buttons

Hi @Rebaz_Balisani:

Anyway, I think you can avoid workspace creations with permissions since Frappe v15.29.1

Anyway, you can create a custom app with this Javascript code on public/js/yourfile.js

You can adapt this to your requirements …

document.addEventListener('DOMContentLoaded', function () {
    function hideElements(selector) {
        var currentUser = frappe.boot.user.name;


        if (currentUser !== 'Administrator') {
            var intervalId = setInterval(function () {
                var elements = document.querySelectorAll(selector);
                if (elements.length > 0) {
                    elements.forEach(function (el) {
                        el.style.display = 'none';
                    });
                    clearInterval(intervalId); 
                }
            }, 5); 
        }
    }

    const selectors = [

        'button[data-label="Edit"]',
        'button[data-label="Create%20Workspace"]'
    ];

    selectors.forEach(hideElements);
});

On hooks.py

app_include_js = "/assets/yourcustomapp/js/yourfile.js"

Hope this helps.

1 Like

i copy pasted your code and changed its custom name to my app name but it was still showing the buttons (i use ERPNext: v15.28.2 (version-15)).

are you using the same version?

Hi @Rebaz_Balisani :

Yes, v15.
It works if the logged user is not “Administrator”.

1 Like

Hi @avc

when i login as normal user, i dont see the buttons but if i click on a button like sales invoice and on the browser click on return then the Create Workspace button becomes visible

This is one headache i have experienced with most custom js functions lately. i find that some functions perform inconsistently due to caching, this forces me to implement location.reload in most cases (which is terrible UX i know) to ensure that the js functions always refresh and reapply their conditional states. I am yet to find a way to prevent caching of states and would love to hear any suggestions on how to handle this.

As far as the actual thread topic goes - @avc is right. If you have the latest version leaving Workspace Manager un-checked will mean that create workspace is not visible for that user. This means no custom code is required

image

off topic (1) - How can I actually tell which version of erpnext I am using? If I do bench version I just get erpnext 14.x.x-develop which I actually don’t think is correct?

ERPNext: v15.30.0 (version-15)
Frappe Framework: v15.35.0 (version-15)

Yes, working fine.

Thanks

offtopic (2)

@flexy2ky From my somewhat limited experience I don’t believe there is an out of the box way to reliably execute javascript code when the user views a page in frappe framework. Once the onload event fires once then caching kicks in and the event won’t fire again, unless the user specifically refreshes the browser (or you do it for them using location.reload or whatever).

I would love to be proved wrong but I haven’t found a way

If you are say adding buttons its not a problem as the buttons just stay on the page (no idea why). But if you are hiding elements then it is a problem as they will simply re-appear with the next page draw

I am currently using a hack which detects if the url changes. I have found this to be reliable, with the caveats that

  1. It is not ‘exactly once’ execution, i.e. it may execute twice for a single page (it does seem to always execute though)
  2. You don’t have access to the frappe page model, so you have to do everything in the DOM directly using jquery or vanilla javascript :hammer: :pick: :carpentry_saw:

Anyway here is the code, I’d be interested to know if it works for you :slight_smile:

async function observeUrlChange() {

    let oldHref = document.location.href;
    const body = document.querySelector("body");
    const observer = new MutationObserver(async mutations => {

        for (const mutation of mutations) {

            if (oldHref !== document.location.href) {
                // user has navigated to a new page
                setupPage()
                oldHref = document.location.href
            }
        } // next mutation
    });

    observer.observe(body, { childList: true, subtree: true });
};

window.addEventListener("load", observeUrlChange)

window.addEventListener("load", setupPage)

async function setupPage() {

        // get and parse current URL
        let url = new URL(document.location.href);
        let lastPart = url.pathname.split('/').filter(segment => segment !== "").pop();
    
        console.log(`setting up page for ${url.pathname} page ${lastPart}`)

        // do various things to the DOM based on the page (lastPart)
}