Merge branch 'develop' into feat-url-validation-option
This commit is contained in:
commit
465708050a
134 changed files with 1040 additions and 114213 deletions
|
|
@ -136,7 +136,6 @@
|
|||
"PhotoSwipeUI_Default": true,
|
||||
"fluxify": true,
|
||||
"io": true,
|
||||
"QUnit": true,
|
||||
"JsBarcode": true,
|
||||
"L": true,
|
||||
"Chart": true,
|
||||
|
|
|
|||
4
.github/workflows/ci-tests.yml
vendored
4
.github/workflows/ci-tests.yml
vendored
|
|
@ -149,8 +149,8 @@ jobs:
|
|||
run: |
|
||||
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
|
||||
cd ${GITHUB_WORKSPACE}
|
||||
pip install coveralls==3.0.1
|
||||
pip install coverage==5.5
|
||||
pip install coveralls==2.2.0
|
||||
pip install coverage==4.5.4
|
||||
coveralls --service=github
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ Full-stack web application framework that uses Python and MariaDB on the server
|
|||
|
||||
### Installation
|
||||
|
||||
[Install via Frappe Bench](https://github.com/frappe/bench)
|
||||
* [Install via Docker](https://github.com/frappe/frappe_docker)
|
||||
* [Install via Frappe Bench](https://github.com/frappe/bench)
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ if PY2:
|
|||
reload(sys)
|
||||
sys.setdefaultencoding("utf-8")
|
||||
|
||||
__version__ = '13.1.0'
|
||||
__version__ = '14.0.0-dev'
|
||||
|
||||
__title__ = "Frappe Framework"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
import json
|
||||
|
||||
from six.moves.urllib.parse import urlencode, urlparse
|
||||
from urllib.parse import urlencode, urlparse
|
||||
|
||||
import frappe
|
||||
import frappe.client
|
||||
|
|
@ -14,6 +12,7 @@ import frappe.handler
|
|||
from frappe import _
|
||||
from frappe.utils.response import build_response
|
||||
|
||||
|
||||
def handle():
|
||||
"""
|
||||
Handler for `/api` methods
|
||||
|
|
@ -38,9 +37,6 @@ def handle():
|
|||
`/api/resource/{doctype}/{name}?run_method={method}` will run a whitelisted controller method
|
||||
"""
|
||||
|
||||
|
||||
validate_auth()
|
||||
|
||||
parts = frappe.request.path[1:].split("/",3)
|
||||
call = doctype = name = None
|
||||
|
||||
|
|
@ -140,6 +136,7 @@ def handle():
|
|||
|
||||
return build_response("json")
|
||||
|
||||
|
||||
def get_request_form_data():
|
||||
if frappe.local.form_dict.data is None:
|
||||
data = frappe.safe_decode(frappe.local.request.get_data())
|
||||
|
|
@ -148,25 +145,18 @@ def get_request_form_data():
|
|||
|
||||
return frappe.parse_json(data)
|
||||
|
||||
|
||||
def validate_auth():
|
||||
if frappe.get_request_header("Authorization") is None:
|
||||
return
|
||||
|
||||
VALID_AUTH_PREFIX_TYPES = ['basic', 'bearer', 'token']
|
||||
VALID_AUTH_PREFIX_STRING = ", ".join(VALID_AUTH_PREFIX_TYPES).title()
|
||||
|
||||
"""
|
||||
Authenticate and sets user for the request.
|
||||
"""
|
||||
authorization_header = frappe.get_request_header("Authorization", str()).split(" ")
|
||||
authorization_type = authorization_header[0].lower()
|
||||
|
||||
if len(authorization_header) == 1:
|
||||
frappe.throw(_('Invalid Authorization headers, add a token with a prefix from one of the following: {0}.').format(VALID_AUTH_PREFIX_STRING), frappe.InvalidAuthorizationHeader)
|
||||
|
||||
if authorization_type == "bearer":
|
||||
if len(authorization_header) == 2:
|
||||
validate_oauth(authorization_header)
|
||||
elif authorization_type in VALID_AUTH_PREFIX_TYPES:
|
||||
validate_auth_via_api_keys(authorization_header)
|
||||
else:
|
||||
frappe.throw(_('Invalid Authorization Type {0}, must be one of {1}.').format(authorization_type, VALID_AUTH_PREFIX_STRING), frappe.InvalidAuthorizationPrefix)
|
||||
|
||||
validate_auth_via_hooks()
|
||||
|
||||
|
||||
def validate_oauth(authorization_header):
|
||||
|
|
@ -177,8 +167,8 @@ def validate_oauth(authorization_header):
|
|||
authorization_header (list of str): The 'Authorization' header containing the prefix and token
|
||||
"""
|
||||
|
||||
from frappe.oauth import get_url_delimiter
|
||||
from frappe.integrations.oauth2 import get_oauth_server
|
||||
from frappe.oauth import get_url_delimiter
|
||||
|
||||
form_dict = frappe.local.form_dict
|
||||
token = authorization_header[1]
|
||||
|
|
@ -187,19 +177,20 @@ def validate_oauth(authorization_header):
|
|||
access_token = {"access_token": token}
|
||||
uri = parsed_url.scheme + "://" + parsed_url.netloc + parsed_url.path + "?" + urlencode(access_token)
|
||||
http_method = req.method
|
||||
body = req.get_data()
|
||||
headers = req.headers
|
||||
body = req.get_data()
|
||||
if req.content_type and "multipart/form-data" in req.content_type:
|
||||
body = None
|
||||
|
||||
try:
|
||||
required_scopes = frappe.db.get_value("OAuth Bearer Token", token, "scopes").split(get_url_delimiter())
|
||||
except AttributeError:
|
||||
frappe.throw(_("Invalid Bearer token, please provide a valid access token with prefix 'Bearer'."), frappe.InvalidAuthorizationToken)
|
||||
|
||||
valid, oauthlib_request = get_oauth_server().verify_request(uri, http_method, body, headers, required_scopes)
|
||||
|
||||
if valid:
|
||||
frappe.set_user(frappe.db.get_value("OAuth Bearer Token", token, "user"))
|
||||
frappe.local.form_dict = form_dict
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
def validate_auth_via_api_keys(authorization_header):
|
||||
|
|
@ -222,8 +213,7 @@ def validate_auth_via_api_keys(authorization_header):
|
|||
except binascii.Error:
|
||||
frappe.throw(_("Failed to decode token, please provide a valid base64-encoded token."), frappe.InvalidAuthorizationToken)
|
||||
except (AttributeError, TypeError, ValueError):
|
||||
frappe.throw(_("Invalid token, please provide a valid token with prefix 'Basic' or 'Token'."), frappe.InvalidAuthorizationToken)
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def validate_api_key_secret(api_key, api_secret, frappe_authorization_source=None):
|
||||
|
|
@ -248,3 +238,8 @@ def validate_api_key_secret(api_key, api_secret, frappe_authorization_source=Non
|
|||
if frappe.local.login_manager.user in ('', 'Guest'):
|
||||
frappe.set_user(user)
|
||||
frappe.local.form_dict = form_dict
|
||||
|
||||
|
||||
def validate_auth_via_hooks():
|
||||
for auth_hook in frappe.get_hooks('auth_hooks', []):
|
||||
frappe.get_attr(auth_hook)()
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ def application(request):
|
|||
frappe.recorder.record()
|
||||
frappe.monitor.start()
|
||||
frappe.rate_limiter.apply()
|
||||
frappe.api.validate_auth()
|
||||
|
||||
if request.method == "OPTIONS":
|
||||
response = Response()
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Auto Repeat", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Auto Repeat
|
||||
() => frappe.tests.make('Auto Repeat', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Contact", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Contact
|
||||
() => frappe.tests.make('Contact', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Communication", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Communication
|
||||
() => frappe.tests.make('Communication', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: DocType", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new DocType
|
||||
() => frappe.tests.make('DocType', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Domain", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially('Domain', [
|
||||
// insert a new Domain
|
||||
() => frappe.tests.make([
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: File", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new File
|
||||
() => frappe.tests.make('File', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Page", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Page
|
||||
() => frappe.tests.make('Page', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Payment Gateway", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Payment Gateway
|
||||
() => frappe.tests.make('Payment Gateway', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Prepared Report", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Prepared Report
|
||||
() => frappe.tests.make('Prepared Report', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
// Test for creating query report
|
||||
QUnit.test("Test Query Report", function(assert){
|
||||
assert.expect(2);
|
||||
let done = assert.async();
|
||||
let random = frappe.utils.get_random(10);
|
||||
frappe.run_serially([
|
||||
() => frappe.set_route('List', 'ToDo'),
|
||||
() => frappe.new_doc('ToDo'),
|
||||
() => frappe.quick_entry.dialog.set_value('description', random),
|
||||
() => frappe.quick_entry.insert(),
|
||||
() => {
|
||||
return frappe.tests.make('Report', [
|
||||
{report_name: 'ToDo List Report'},
|
||||
{report_type: 'Query Report'},
|
||||
{ref_doctype: 'ToDo'}
|
||||
]);
|
||||
},
|
||||
() => frappe.set_route('Form','Report', 'ToDo List Report'),
|
||||
|
||||
//Query
|
||||
() => cur_frm.set_value('query','select description,owner,status from `tabToDo`'),
|
||||
() => cur_frm.save(),
|
||||
() => frappe.set_route('query-report','ToDo List Report'),
|
||||
() => frappe.timeout(5),
|
||||
() => {
|
||||
assert.ok($('div.slick-header-column').length == 4,'Correct numbers of columns visible');
|
||||
//To check if the result is present
|
||||
assert.ok($('div.r1:contains('+random+')').is(':visible'),'Result is visible in report');
|
||||
frappe.timeout(3);
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Report", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Report
|
||||
() => frappe.tests.make('Report', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
QUnit.module('Core');
|
||||
|
||||
QUnit.test("test: Role Profile", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
assert.expect(3);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new user
|
||||
() => frappe.tests.make('Role Profile', [
|
||||
{role_profile: 'Test 2'}
|
||||
]),
|
||||
|
||||
() => {
|
||||
$('input.box')[0].checked = true;
|
||||
$('input.box')[2].checked = true;
|
||||
$('input.box')[4].checked = true;
|
||||
cur_frm.save();
|
||||
},
|
||||
|
||||
() => frappe.timeout(1),
|
||||
() => cur_frm.refresh(),
|
||||
() => frappe.timeout(2),
|
||||
() => {
|
||||
assert.equal($('input.box')[0].checked, true);
|
||||
assert.equal($('input.box')[2].checked, true);
|
||||
assert.equal($('input.box')[4].checked, true);
|
||||
},
|
||||
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: SMS Settings", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially('SMS Settings', [
|
||||
// insert a new SMS Settings
|
||||
() => frappe.tests.make([
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: System Settings", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially('System Settings', [
|
||||
// insert a new System Settings
|
||||
() => frappe.tests.make([
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Transaction Log", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Transaction Log
|
||||
() => frappe.tests.make('Transaction Log', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: User", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new User
|
||||
() => frappe.tests.make('User', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
QUnit.module('Core');
|
||||
|
||||
QUnit.test("test: Set role profile in user", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
assert.expect(3);
|
||||
|
||||
frappe.run_serially([
|
||||
|
||||
// Insert a new user
|
||||
() => frappe.tests.make('User', [
|
||||
{email: 'test@test2.com'},
|
||||
{first_name: 'Test 2'},
|
||||
{send_welcome_email: 0}
|
||||
]),
|
||||
|
||||
() => frappe.timeout(2),
|
||||
() => {
|
||||
return frappe.tests.set_form_values(cur_frm, [
|
||||
{role_profile_name:'Test 2'}
|
||||
]);
|
||||
},
|
||||
|
||||
() => cur_frm.save(),
|
||||
() => frappe.timeout(2),
|
||||
|
||||
() => {
|
||||
assert.equal($('input.box')[0].checked, true);
|
||||
assert.equal($('input.box')[2].checked, true);
|
||||
assert.equal($('input.box')[4].checked, true);
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: User Permission", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially('User Permission', [
|
||||
// insert a new User Permission
|
||||
() => frappe.tests.make([
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: View Log", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new View Log
|
||||
() => frappe.tests.make('View Log', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Custom Field", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Custom Field
|
||||
() => frappe.tests.make('Custom Field', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
// try and delete a standard row, it should fail
|
||||
|
||||
QUnit.module('Customize Form');
|
||||
|
||||
QUnit.test("test customize form", function(assert) {
|
||||
assert.expect(2);
|
||||
let done = assert.async();
|
||||
frappe.run_serially([
|
||||
() => frappe.set_route('Form', 'Customize Form'),
|
||||
() => frappe.timeout(1),
|
||||
() => cur_frm.set_value('doc_type', 'ToDo'),
|
||||
() => frappe.timeout(2),
|
||||
() => {
|
||||
// find the status column as there may be other custom fields like
|
||||
// kanban etc.
|
||||
frappe.row_idx = 0;
|
||||
cur_frm.doc.fields.every((d, i) => {
|
||||
if(d.fieldname==='status') {
|
||||
frappe.row_idx = i;
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
assert.equal(cur_frm.doc.fields[frappe.row_idx].fieldname, 'status',
|
||||
'check if selected field is "status"');
|
||||
},
|
||||
// open "status" row
|
||||
() => cur_frm.fields_dict.fields.grid.grid_rows[frappe.row_idx].toggle_view(),
|
||||
() => frappe.timeout(0.5),
|
||||
|
||||
// try deleting it
|
||||
() => $('.grid-delete-row:visible').click(),
|
||||
|
||||
() => frappe.timeout(0.5),
|
||||
() => frappe.hide_msgprint(),
|
||||
() => frappe.timeout(0.5),
|
||||
|
||||
// status still exists
|
||||
() => assert.equal(cur_frm.doc.fields[frappe.row_idx].fieldname, 'status',
|
||||
'check if selected field is still "status"'),
|
||||
() => done()
|
||||
]);
|
||||
});
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
// Copyright (c) 2021, Frappe Technologies and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Test rename new', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Data Migration Connector", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Data Migration Connector
|
||||
() => frappe.tests.make('Data Migration Connector', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Data Migration Mapping", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Data Migration Mapping
|
||||
() => frappe.tests.make('Data Migration Mapping', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Data Migration Plan", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Data Migration Plan
|
||||
() => frappe.tests.make('Data Migration Plan', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Data Migration Run", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Data Migration Run
|
||||
() => frappe.tests.make('Data Migration Run', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
|
||||
QUnit.test("test: Event", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(4);
|
||||
|
||||
const subject = '_Test Event 1';
|
||||
const datetime = frappe.datetime.now_datetime();
|
||||
const hex = '#6be273';
|
||||
const rgb = 'rgb(107, 226, 115)';
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Event
|
||||
() => frappe.tests.make('Event', [
|
||||
// values to be set
|
||||
{subject: subject},
|
||||
{starts_on: datetime},
|
||||
{color: hex},
|
||||
{event_type: 'Private'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.subject, subject, 'Subject correctly set');
|
||||
assert.equal(cur_frm.doc.starts_on, datetime, 'Date correctly set');
|
||||
assert.equal(cur_frm.doc.color, hex, 'Color correctly set');
|
||||
|
||||
// set filters explicitly for list view
|
||||
frappe.route_options = {
|
||||
event_type: 'Private'
|
||||
};
|
||||
},
|
||||
() => frappe.set_route('List', 'Event', 'Calendar'),
|
||||
() => frappe.timeout(2),
|
||||
() => {
|
||||
const bg_color = $(`.result:visible .fc-day-grid-event:contains("${subject}")`)
|
||||
.css('background-color');
|
||||
assert.equal(bg_color, rgb, 'Event background color is set correctly');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
QUnit.test("test: Note", function (assert) {
|
||||
let done = assert.async();
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
frappe.run_serially([
|
||||
// insert a new Note
|
||||
() => frappe.tests.make('Note', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: ToDo", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new ToDo
|
||||
() => frappe.tests.make('ToDo', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -109,8 +109,9 @@ class FormMeta(Meta):
|
|||
def _add_code(self, path, fieldname):
|
||||
js = get_js(path)
|
||||
if js:
|
||||
self.set(fieldname, (self.get(fieldname) or "")
|
||||
+ "\n\n/* Adding {0} */\n\n".format(path) + js)
|
||||
comment = f"\n\n/* Adding {path} */\n\n"
|
||||
sourceURL = f"\n\n//# sourceURL={scrub(self.name) + fieldname}"
|
||||
self.set(fieldname, (self.get(fieldname) or "") + comment + js + sourceURL)
|
||||
|
||||
def add_html_templates(self, path):
|
||||
if self.custom:
|
||||
|
|
@ -145,6 +146,10 @@ class FormMeta(Meta):
|
|||
if script.view == 'Form':
|
||||
form_script += script.script
|
||||
|
||||
file = scrub(self.name)
|
||||
form_script += f"\n\n//# sourceURL={file}__custom_js"
|
||||
list_script += f"\n\n//# sourceURL={file}__custom_list_js"
|
||||
|
||||
self.set("__custom_js", form_script)
|
||||
self.set("__custom_list_js", list_script)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Document Follow", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Document Follow
|
||||
() => frappe.tests.make('Document Follow', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Email Queue", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Email Queue
|
||||
() => frappe.tests.make('Email Queue', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Email Template", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Email Template
|
||||
() => frappe.tests.make('Email Template', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -24,6 +24,7 @@ class Newsletter(WebsiteGenerator):
|
|||
if self.send_from:
|
||||
validate_email_address(self.send_from, True)
|
||||
|
||||
@frappe.whitelist()
|
||||
def test_send(self, doctype="Lead"):
|
||||
self.recipients = frappe.utils.split_emails(self.test_email_id)
|
||||
self.queue_all(test_email=True)
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Newsletter", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Newsletter
|
||||
() => frappe.tests.make('Newsletter', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Notification", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Notification
|
||||
() => frappe.tests.make('Notification', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -2,20 +2,19 @@
|
|||
# Copyright (c) 2019, Frappe Technologies and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
import json
|
||||
import time
|
||||
|
||||
import requests
|
||||
from six import iteritems
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.frappeclient import FrappeClient
|
||||
from frappe.utils.background_jobs import get_jobs
|
||||
from frappe.utils.data import get_url, get_link_to_form
|
||||
from frappe.utils.password import get_decrypted_password
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
from frappe.integrations.oauth2 import validate_url
|
||||
from frappe.frappeclient import FrappeClient
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.background_jobs import get_jobs
|
||||
from frappe.utils.data import get_link_to_form, get_url
|
||||
from frappe.utils.password import get_decrypted_password
|
||||
|
||||
|
||||
class EventProducer(Document):
|
||||
|
|
@ -56,7 +55,7 @@ class EventProducer(Document):
|
|||
self.reload()
|
||||
|
||||
def check_url(self):
|
||||
if not validate_url(self.producer_url):
|
||||
if not frappe.utils.validate_url(self.producer_url):
|
||||
frappe.throw(_('Invalid URL'))
|
||||
|
||||
# remove '/' from the end of the url like http://test_site.com/
|
||||
|
|
@ -272,8 +271,8 @@ def set_insert(update, producer_site, event_producer):
|
|||
if update.mapping:
|
||||
if update.get('dependencies'):
|
||||
dependencies_created = sync_mapped_dependencies(update.dependencies, producer_site)
|
||||
for fieldname, value in iteritems(dependencies_created):
|
||||
doc.update({ fieldname : value })
|
||||
for fieldname, value in dependencies_created.items():
|
||||
doc.update({fieldname: value})
|
||||
else:
|
||||
sync_dependencies(doc, producer_site)
|
||||
|
||||
|
|
@ -304,8 +303,8 @@ def set_update(update, producer_site):
|
|||
if update.mapping:
|
||||
if update.get('dependencies'):
|
||||
dependencies_created = sync_mapped_dependencies(update.dependencies, producer_site)
|
||||
for fieldname, value in iteritems(dependencies_created):
|
||||
local_doc.update({ fieldname : value })
|
||||
for fieldname, value in dependencies_created.items():
|
||||
local_doc.update({fieldname: value})
|
||||
else:
|
||||
sync_dependencies(local_doc, producer_site)
|
||||
|
||||
|
|
@ -315,7 +314,7 @@ def set_update(update, producer_site):
|
|||
|
||||
def update_row_removed(local_doc, removed):
|
||||
"""Sync child table row deletion type update"""
|
||||
for tablename, rownames in iteritems(removed):
|
||||
for tablename, rownames in removed.items():
|
||||
table = local_doc.get_table_field_doctype(tablename)
|
||||
for row in rownames:
|
||||
table_rows = local_doc.get(tablename)
|
||||
|
|
@ -333,7 +332,7 @@ def get_child_table_row(table_rows, row):
|
|||
|
||||
def update_row_changed(local_doc, changed):
|
||||
"""Sync child table row updation type update"""
|
||||
for tablename, rows in iteritems(changed):
|
||||
for tablename, rows in changed.items():
|
||||
old = local_doc.get(tablename)
|
||||
for doc in old:
|
||||
for row in rows:
|
||||
|
|
@ -343,7 +342,7 @@ def update_row_changed(local_doc, changed):
|
|||
|
||||
def update_row_added(local_doc, added):
|
||||
"""Sync child table row addition type update"""
|
||||
for tablename, rows in iteritems(added):
|
||||
for tablename, rows in added.items():
|
||||
local_doc.extend(tablename, rows)
|
||||
for child in rows:
|
||||
child_doc = frappe.get_doc(child)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import frappe
|
|||
import frappe.utils
|
||||
import frappe.sessions
|
||||
from frappe.utils import cint
|
||||
from frappe.api import validate_auth
|
||||
from frappe import _, is_whitelisted
|
||||
from frappe.utils.response import build_response
|
||||
from frappe.utils.csvutils import build_csv_response
|
||||
|
|
@ -24,7 +23,7 @@ ALLOWED_MIMETYPES = ('image/png', 'image/jpeg', 'application/pdf', 'application/
|
|||
|
||||
def handle():
|
||||
"""handle request"""
|
||||
validate_auth()
|
||||
|
||||
cmd = frappe.local.form_dict.cmd
|
||||
data = None
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Braintree Settings", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Braintree Setting
|
||||
() => frappe.tests.make('Braintree Settings', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,256 +1,112 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"actions": [],
|
||||
"autoname": "field:authorization_code",
|
||||
"beta": 0,
|
||||
"creation": "2016-08-24 14:12:13.647159",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"client",
|
||||
"user",
|
||||
"scopes",
|
||||
"authorization_code",
|
||||
"expiration_time",
|
||||
"redirect_uri_bound_to_authorization_code",
|
||||
"validity",
|
||||
"nonce",
|
||||
"code_challenge",
|
||||
"code_challenge_method"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "client",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Client",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "OAuth Client",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "User",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "User",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "scopes",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Scopes",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "authorization_code",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Authorization Code",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"read_only": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "expiration_time",
|
||||
"fieldtype": "Datetime",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Expiration time",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "redirect_uri_bound_to_authorization_code",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Redirect URI Bound To Auth Code",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "validity",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Validity",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Valid\nInvalid",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "nonce",
|
||||
"fieldtype": "Data",
|
||||
"label": "nonce",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "code_challenge",
|
||||
"fieldtype": "Data",
|
||||
"label": "Code Challenge",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "code_challenge_method",
|
||||
"fieldtype": "Select",
|
||||
"label": "Code challenge method",
|
||||
"options": "\ns256\nplain",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-03-08 14:40:04.113884",
|
||||
"links": [],
|
||||
"modified": "2021-04-26 07:23:02.980612",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "OAuth Authorization Code",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
|
|
@ -1,283 +1,96 @@
|
|||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"actions": [],
|
||||
"autoname": "field:access_token",
|
||||
"beta": 0,
|
||||
"creation": "2016-08-24 14:10:17.471264",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"client",
|
||||
"user",
|
||||
"scopes",
|
||||
"access_token",
|
||||
"refresh_token",
|
||||
"expiration_time",
|
||||
"expires_in",
|
||||
"status"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "client",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Client",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "OAuth Client",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "User",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "User",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "scopes",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Scopes",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "access_token",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Access Token",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"read_only": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "refresh_token",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Refresh Token",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "expiration_time",
|
||||
"fieldtype": "Datetime",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Expiration time",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "expires_in",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Expires In",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Status",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Active\nRevoked",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2017-03-08 14:40:04.209039",
|
||||
"links": [],
|
||||
"modified": "2021-04-26 06:40:34.922441",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Integrations",
|
||||
"name": "OAuth Bearer Token",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: S3 Backup Settings", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new S3 Backup Settings
|
||||
() => frappe.tests.make('S3 Backup Settings', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Slack Webhook URL", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Slack Webhook URL
|
||||
() => frappe.tests.make('Slack Webhook URL', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Social Login Key", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Social Login Key
|
||||
() => frappe.tests.make('Social Login Key', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Stripe Settings", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Stripe Settings
|
||||
() => frappe.tests.make('Stripe Settings', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Webhook", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Webhook
|
||||
() => frappe.tests.make('Webhook', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,195 +1,39 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from urllib.parse import quote, urlencode, urlparse
|
||||
from urllib.parse import quote, urlencode
|
||||
|
||||
import jwt
|
||||
from oauthlib.oauth2 import FatalClientError, OAuth2Error
|
||||
from oauthlib.openid.connect.core.endpoints.pre_configured import (
|
||||
Server as WebApplicationServer,
|
||||
)
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.oauth import OAuthWebRequestValidator, WebApplicationServer
|
||||
from frappe.integrations.doctype.oauth_provider_settings.oauth_provider_settings import get_oauth_settings
|
||||
from frappe.integrations.doctype.oauth_provider_settings.oauth_provider_settings import (
|
||||
get_oauth_settings,
|
||||
)
|
||||
from frappe.oauth import (
|
||||
OAuthWebRequestValidator,
|
||||
generate_json_error_response,
|
||||
get_server_url,
|
||||
get_userinfo,
|
||||
)
|
||||
|
||||
|
||||
def get_oauth_server():
|
||||
if not getattr(frappe.local, 'oauth_server', None):
|
||||
if not getattr(frappe.local, "oauth_server", None):
|
||||
oauth_validator = OAuthWebRequestValidator()
|
||||
frappe.local.oauth_server = WebApplicationServer(oauth_validator)
|
||||
|
||||
return frappe.local.oauth_server
|
||||
|
||||
|
||||
def sanitize_kwargs(param_kwargs):
|
||||
"""Remove 'data' and 'cmd' keys, if present."""
|
||||
arguments = param_kwargs
|
||||
arguments.pop('data', None)
|
||||
arguments.pop('cmd', None)
|
||||
arguments.pop("data", None)
|
||||
arguments.pop("cmd", None)
|
||||
|
||||
return arguments
|
||||
|
||||
@frappe.whitelist()
|
||||
def approve(*args, **kwargs):
|
||||
r = frappe.request
|
||||
|
||||
try:
|
||||
scopes, frappe.flags.oauth_credentials = get_oauth_server().validate_authorization_request(
|
||||
r.url,
|
||||
r.method,
|
||||
r.get_data(),
|
||||
r.headers
|
||||
)
|
||||
|
||||
headers, body, status = get_oauth_server().create_authorization_response(
|
||||
uri=frappe.flags.oauth_credentials['redirect_uri'],
|
||||
body=r.get_data(),
|
||||
headers=r.headers,
|
||||
scopes=scopes,
|
||||
credentials=frappe.flags.oauth_credentials
|
||||
)
|
||||
uri = headers.get('Location', None)
|
||||
|
||||
frappe.local.response["type"] = "redirect"
|
||||
frappe.local.response["location"] = uri
|
||||
|
||||
except FatalClientError as e:
|
||||
return e
|
||||
except OAuth2Error as e:
|
||||
return e
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def authorize(**kwargs):
|
||||
success_url = "/api/method/frappe.integrations.oauth2.approve?" + encode_params(sanitize_kwargs(kwargs))
|
||||
failure_url = frappe.form_dict["redirect_uri"] + "?error=access_denied"
|
||||
|
||||
if frappe.session.user == 'Guest':
|
||||
#Force login, redirect to preauth again.
|
||||
frappe.local.response["type"] = "redirect"
|
||||
frappe.local.response["location"] = "/login?" + encode_params({'redirect-to': frappe.request.url})
|
||||
else:
|
||||
try:
|
||||
r = frappe.request
|
||||
scopes, frappe.flags.oauth_credentials = get_oauth_server().validate_authorization_request(
|
||||
r.url,
|
||||
r.method,
|
||||
r.get_data(),
|
||||
r.headers
|
||||
)
|
||||
|
||||
skip_auth = frappe.db.get_value("OAuth Client", frappe.flags.oauth_credentials['client_id'], "skip_authorization")
|
||||
unrevoked_tokens = frappe.get_all("OAuth Bearer Token", filters={"status":"Active"})
|
||||
|
||||
if skip_auth or (get_oauth_settings().skip_authorization == "Auto" and unrevoked_tokens):
|
||||
frappe.local.response["type"] = "redirect"
|
||||
frappe.local.response["location"] = success_url
|
||||
else:
|
||||
#Show Allow/Deny screen.
|
||||
response_html_params = frappe._dict({
|
||||
"client_id": frappe.db.get_value("OAuth Client", kwargs['client_id'], "app_name"),
|
||||
"success_url": success_url,
|
||||
"failure_url": failure_url,
|
||||
"details": scopes
|
||||
})
|
||||
resp_html = frappe.render_template("templates/includes/oauth_confirmation.html", response_html_params)
|
||||
frappe.respond_as_web_page("Confirm Access", resp_html)
|
||||
except FatalClientError as e:
|
||||
return e
|
||||
except OAuth2Error as e:
|
||||
return e
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_token(*args, **kwargs):
|
||||
#Check whether frappe server URL is set
|
||||
frappe_server_url = frappe.db.get_value("Social Login Key", "frappe", "base_url") or None
|
||||
if not frappe_server_url:
|
||||
frappe.throw(_("Please set Base URL in Social Login Key for Frappe"))
|
||||
|
||||
try:
|
||||
r = frappe.request
|
||||
headers, body, status = get_oauth_server().create_token_response(
|
||||
r.url,
|
||||
r.method,
|
||||
r.form,
|
||||
r.headers,
|
||||
frappe.flags.oauth_credentials
|
||||
)
|
||||
out = frappe._dict(json.loads(body))
|
||||
if not out.error and "openid" in out.scope:
|
||||
token_user = frappe.db.get_value("OAuth Bearer Token", out.access_token, "user")
|
||||
token_client = frappe.db.get_value("OAuth Bearer Token", out.access_token, "client")
|
||||
client_secret = frappe.db.get_value("OAuth Client", token_client, "client_secret")
|
||||
if token_user in ["Guest", "Administrator"]:
|
||||
frappe.throw(_("Logged in as Guest or Administrator"))
|
||||
|
||||
id_token_header = {
|
||||
"typ":"jwt",
|
||||
"alg":"HS256"
|
||||
}
|
||||
id_token = {
|
||||
"aud": token_client,
|
||||
"exp": int((frappe.db.get_value("OAuth Bearer Token", out.access_token, "expiration_time") - frappe.utils.datetime.datetime(1970, 1, 1)).total_seconds()),
|
||||
"sub": frappe.db.get_value("User Social Login", {"parent":token_user, "provider": "frappe"}, "userid"),
|
||||
"iss": frappe_server_url,
|
||||
"at_hash": frappe.oauth.calculate_at_hash(out.access_token, hashlib.sha256)
|
||||
}
|
||||
|
||||
id_token_encoded = jwt.encode(id_token, client_secret, algorithm='HS256', headers=id_token_header)
|
||||
out.update({"id_token": frappe.safe_decode(id_token_encoded)})
|
||||
|
||||
frappe.local.response = out
|
||||
|
||||
except FatalClientError as e:
|
||||
return e
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def revoke_token(*args, **kwargs):
|
||||
r = frappe.request
|
||||
headers, body, status = get_oauth_server().create_revocation_response(
|
||||
r.url,
|
||||
headers=r.headers,
|
||||
body=r.form,
|
||||
http_method=r.method
|
||||
)
|
||||
|
||||
frappe.local.response['http_status_code'] = status
|
||||
if status == 200:
|
||||
return "success"
|
||||
else:
|
||||
return "bad request"
|
||||
|
||||
@frappe.whitelist()
|
||||
def openid_profile(*args, **kwargs):
|
||||
picture = None
|
||||
first_name, last_name, avatar, name = frappe.db.get_value("User", frappe.session.user, ["first_name", "last_name", "user_image", "name"])
|
||||
frappe_userid = frappe.db.get_value("User Social Login", {"parent":frappe.session.user, "provider": "frappe"}, "userid")
|
||||
request_url = urlparse(frappe.request.url)
|
||||
base_url = frappe.db.get_value("Social Login Key", "frappe", "base_url") or None
|
||||
|
||||
if avatar:
|
||||
if validate_url(avatar):
|
||||
picture = avatar
|
||||
elif base_url:
|
||||
picture = base_url + '/' + avatar
|
||||
else:
|
||||
picture = request_url.scheme + "://" + request_url.netloc + avatar
|
||||
|
||||
user_profile = frappe._dict({
|
||||
"sub": frappe_userid,
|
||||
"name": " ".join(filter(None, [first_name, last_name])),
|
||||
"given_name": first_name,
|
||||
"family_name": last_name,
|
||||
"email": name,
|
||||
"picture": picture
|
||||
})
|
||||
|
||||
frappe.local.response = user_profile
|
||||
|
||||
def validate_url(url_string):
|
||||
try:
|
||||
result = urlparse(url_string)
|
||||
return result.scheme and result.scheme in ["http", "https", "ftp", "ftps"]
|
||||
except:
|
||||
return False
|
||||
|
||||
def encode_params(params):
|
||||
"""
|
||||
|
|
@ -200,3 +44,215 @@ def encode_params(params):
|
|||
as a whitespace.
|
||||
"""
|
||||
return urlencode(params, quote_via=quote)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def approve(*args, **kwargs):
|
||||
r = frappe.request
|
||||
|
||||
try:
|
||||
(
|
||||
scopes,
|
||||
frappe.flags.oauth_credentials,
|
||||
) = get_oauth_server().validate_authorization_request(
|
||||
r.url, r.method, r.get_data(), r.headers
|
||||
)
|
||||
|
||||
headers, body, status = get_oauth_server().create_authorization_response(
|
||||
uri=frappe.flags.oauth_credentials["redirect_uri"],
|
||||
body=r.get_data(),
|
||||
headers=r.headers,
|
||||
scopes=scopes,
|
||||
credentials=frappe.flags.oauth_credentials,
|
||||
)
|
||||
uri = headers.get("Location", None)
|
||||
|
||||
frappe.local.response["type"] = "redirect"
|
||||
frappe.local.response["location"] = uri
|
||||
return
|
||||
|
||||
except (FatalClientError, OAuth2Error) as e:
|
||||
return generate_json_error_response(e)
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def authorize(**kwargs):
|
||||
success_url = "/api/method/frappe.integrations.oauth2.approve?" + encode_params(
|
||||
sanitize_kwargs(kwargs)
|
||||
)
|
||||
failure_url = frappe.form_dict["redirect_uri"] + "?error=access_denied"
|
||||
|
||||
if frappe.session.user == "Guest":
|
||||
# Force login, redirect to preauth again.
|
||||
frappe.local.response["type"] = "redirect"
|
||||
frappe.local.response["location"] = "/login?" + encode_params(
|
||||
{"redirect-to": frappe.request.url}
|
||||
)
|
||||
else:
|
||||
try:
|
||||
r = frappe.request
|
||||
(
|
||||
scopes,
|
||||
frappe.flags.oauth_credentials,
|
||||
) = get_oauth_server().validate_authorization_request(
|
||||
r.url, r.method, r.get_data(), r.headers
|
||||
)
|
||||
|
||||
skip_auth = frappe.db.get_value(
|
||||
"OAuth Client",
|
||||
frappe.flags.oauth_credentials["client_id"],
|
||||
"skip_authorization",
|
||||
)
|
||||
unrevoked_tokens = frappe.get_all(
|
||||
"OAuth Bearer Token", filters={"status": "Active"}
|
||||
)
|
||||
|
||||
if skip_auth or (
|
||||
get_oauth_settings().skip_authorization == "Auto" and unrevoked_tokens
|
||||
):
|
||||
frappe.local.response["type"] = "redirect"
|
||||
frappe.local.response["location"] = success_url
|
||||
else:
|
||||
# Show Allow/Deny screen.
|
||||
response_html_params = frappe._dict(
|
||||
{
|
||||
"client_id": frappe.db.get_value(
|
||||
"OAuth Client", kwargs["client_id"], "app_name"
|
||||
),
|
||||
"success_url": success_url,
|
||||
"failure_url": failure_url,
|
||||
"details": scopes,
|
||||
}
|
||||
)
|
||||
resp_html = frappe.render_template(
|
||||
"templates/includes/oauth_confirmation.html", response_html_params
|
||||
)
|
||||
frappe.respond_as_web_page("Confirm Access", resp_html)
|
||||
except (FatalClientError, OAuth2Error) as e:
|
||||
return generate_json_error_response(e)
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def get_token(*args, **kwargs):
|
||||
try:
|
||||
r = frappe.request
|
||||
headers, body, status = get_oauth_server().create_token_response(
|
||||
r.url, r.method, r.form, r.headers, frappe.flags.oauth_credentials
|
||||
)
|
||||
body = frappe._dict(json.loads(body))
|
||||
|
||||
if body.error:
|
||||
frappe.local.response = body
|
||||
frappe.local.response["http_status_code"] = 400
|
||||
return
|
||||
|
||||
frappe.local.response = body
|
||||
return
|
||||
|
||||
except (FatalClientError, OAuth2Error) as e:
|
||||
return generate_json_error_response(e)
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def revoke_token(*args, **kwargs):
|
||||
try:
|
||||
r = frappe.request
|
||||
headers, body, status = get_oauth_server().create_revocation_response(
|
||||
r.url,
|
||||
headers=r.headers,
|
||||
body=r.form,
|
||||
http_method=r.method,
|
||||
)
|
||||
except (FatalClientError, OAuth2Error):
|
||||
pass
|
||||
|
||||
# status_code must be 200
|
||||
frappe.local.response = frappe._dict({})
|
||||
frappe.local.response["http_status_code"] = status or 200
|
||||
return
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def openid_profile(*args, **kwargs):
|
||||
try:
|
||||
r = frappe.request
|
||||
headers, body, status = get_oauth_server().create_userinfo_response(
|
||||
r.url,
|
||||
headers=r.headers,
|
||||
body=r.form,
|
||||
)
|
||||
body = frappe._dict(json.loads(body))
|
||||
frappe.local.response = body
|
||||
return
|
||||
|
||||
except (FatalClientError, OAuth2Error) as e:
|
||||
return generate_json_error_response(e)
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def openid_configuration():
|
||||
frappe_server_url = get_server_url()
|
||||
frappe.local.response = frappe._dict(
|
||||
{
|
||||
"issuer": frappe_server_url,
|
||||
"authorization_endpoint": f"{frappe_server_url}/api/method/frappe.integrations.oauth2.authorize",
|
||||
"token_endpoint": f"{frappe_server_url}/api/method/frappe.integrations.oauth2.get_token",
|
||||
"userinfo_endpoint": f"{frappe_server_url}/api/method/frappe.integrations.oauth2.openid_profile",
|
||||
"revocation_endpoint": f"{frappe_server_url}/api/method/frappe.integrations.oauth2.revoke_token",
|
||||
"introspection_endpoint": f"{frappe_server_url}/api/method/frappe.integrations.oauth2.introspect_token",
|
||||
"response_types_supported": [
|
||||
"code",
|
||||
"token",
|
||||
"code id_token",
|
||||
"code token id_token",
|
||||
"id_token",
|
||||
"id_token token",
|
||||
],
|
||||
"subject_types_supported": ["public"],
|
||||
"id_token_signing_alg_values_supported": ["HS256"],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def introspect_token(token=None, token_type_hint=None):
|
||||
if token_type_hint not in ["access_token", "refresh_token"]:
|
||||
token_type_hint = "access_token"
|
||||
try:
|
||||
bearer_token = None
|
||||
if token_type_hint == "access_token":
|
||||
bearer_token = frappe.get_doc("OAuth Bearer Token", {"access_token": token})
|
||||
elif token_type_hint == "refresh_token":
|
||||
bearer_token = frappe.get_doc(
|
||||
"OAuth Bearer Token", {"refresh_token": token}
|
||||
)
|
||||
|
||||
client = frappe.get_doc("OAuth Client", bearer_token.client)
|
||||
|
||||
token_response = frappe._dict(
|
||||
{
|
||||
"client_id": client.client_id,
|
||||
"trusted_client": client.skip_authorization,
|
||||
"active": bearer_token.status == "Active",
|
||||
"exp": round(bearer_token.expiration_time.timestamp()),
|
||||
"scope": bearer_token.scopes,
|
||||
}
|
||||
)
|
||||
|
||||
if "openid" in bearer_token.scopes:
|
||||
sub = frappe.get_value(
|
||||
"User Social Login",
|
||||
{"provider": "frappe", "parent": bearer_token.user},
|
||||
"userid",
|
||||
)
|
||||
|
||||
if sub:
|
||||
token_response.update({"sub": sub})
|
||||
user = frappe.get_doc("User", bearer_token.user)
|
||||
userinfo = get_userinfo(user)
|
||||
token_response.update(userinfo)
|
||||
|
||||
frappe.local.response = token_response
|
||||
|
||||
except Exception:
|
||||
frappe.local.response = frappe._dict({"active": False})
|
||||
|
|
|
|||
518
frappe/oauth.py
518
frappe/oauth.py
|
|
@ -1,65 +1,16 @@
|
|||
from __future__ import print_function, unicode_literals
|
||||
import frappe
|
||||
import pytz
|
||||
|
||||
from frappe import _
|
||||
from frappe.auth import LoginManager
|
||||
import base64
|
||||
import datetime
|
||||
import hashlib
|
||||
import re
|
||||
from http import cookies
|
||||
from oauthlib.oauth2.rfc6749.tokens import BearerToken
|
||||
from oauthlib.oauth2.rfc6749.grant_types import AuthorizationCodeGrant, ImplicitGrant, ResourceOwnerPasswordCredentialsGrant, ClientCredentialsGrant, RefreshTokenGrant
|
||||
from oauthlib.oauth2 import RequestValidator
|
||||
from oauthlib.oauth2.rfc6749.endpoints.authorization import AuthorizationEndpoint
|
||||
from oauthlib.oauth2.rfc6749.endpoints.token import TokenEndpoint
|
||||
from oauthlib.oauth2.rfc6749.endpoints.resource import ResourceEndpoint
|
||||
from oauthlib.oauth2.rfc6749.endpoints.revocation import RevocationEndpoint
|
||||
from oauthlib.common import Request
|
||||
from six.moves.urllib.parse import unquote
|
||||
from urllib.parse import unquote, urlparse
|
||||
|
||||
def get_url_delimiter(separator_character=" "):
|
||||
return separator_character
|
||||
import jwt
|
||||
import pytz
|
||||
from oauthlib.openid import RequestValidator
|
||||
|
||||
class WebApplicationServer(AuthorizationEndpoint, TokenEndpoint, ResourceEndpoint,
|
||||
RevocationEndpoint):
|
||||
|
||||
"""An all-in-one endpoint featuring Authorization code grant and Bearer tokens."""
|
||||
|
||||
def __init__(self, request_validator, token_generator=None,
|
||||
token_expires_in=None, refresh_token_generator=None, **kwargs):
|
||||
"""Construct a new web application server.
|
||||
|
||||
:param request_validator: An implementation of
|
||||
oauthlib.oauth2.RequestValidator.
|
||||
:param token_expires_in: An int or a function to generate a token
|
||||
expiration offset (in seconds) given a
|
||||
oauthlib.common.Request object.
|
||||
:param token_generator: A function to generate a token from a request.
|
||||
:param refresh_token_generator: A function to generate a token from a
|
||||
request for the refresh token.
|
||||
:param kwargs: Extra parameters to pass to authorization-,
|
||||
token-, resource-, and revocation-endpoint constructors.
|
||||
"""
|
||||
implicit_grant = ImplicitGrant(request_validator)
|
||||
auth_grant = AuthorizationCodeGrant(request_validator)
|
||||
refresh_grant = RefreshTokenGrant(request_validator)
|
||||
resource_owner_password_credentials_grant = ResourceOwnerPasswordCredentialsGrant(request_validator)
|
||||
bearer = BearerToken(request_validator, token_generator,
|
||||
token_expires_in, refresh_token_generator)
|
||||
AuthorizationEndpoint.__init__(self, default_response_type='code',
|
||||
response_types={
|
||||
'code': auth_grant,
|
||||
'token': implicit_grant
|
||||
},
|
||||
default_token_type=bearer)
|
||||
TokenEndpoint.__init__(self, default_grant_type='authorization_code',
|
||||
grant_types={
|
||||
'authorization_code': auth_grant,
|
||||
'refresh_token': refresh_grant,
|
||||
'password': resource_owner_password_credentials_grant
|
||||
},
|
||||
default_token_type=bearer)
|
||||
ResourceEndpoint.__init__(self, default_token='Bearer',
|
||||
token_types={'Bearer': bearer})
|
||||
RevocationEndpoint.__init__(self, request_validator)
|
||||
import frappe
|
||||
from frappe.auth import LoginManager
|
||||
|
||||
|
||||
class OAuthWebRequestValidator(RequestValidator):
|
||||
|
|
@ -67,7 +18,7 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
# Pre- and post-authorization.
|
||||
def validate_client_id(self, client_id, request, *args, **kwargs):
|
||||
# Simple validity check, does client exist? Not banned?
|
||||
cli_id = frappe.db.get_value("OAuth Client",{ "name":client_id })
|
||||
cli_id = frappe.db.get_value("OAuth Client", {"name": client_id})
|
||||
if cli_id:
|
||||
request.client = frappe.get_doc("OAuth Client", client_id).as_dict()
|
||||
return True
|
||||
|
|
@ -78,7 +29,9 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
# Is the client allowed to use the supplied redirect_uri? i.e. has
|
||||
# the client previously registered this EXACT redirect uri.
|
||||
|
||||
redirect_uris = frappe.db.get_value("OAuth Client", client_id, 'redirect_uris').split(get_url_delimiter())
|
||||
redirect_uris = frappe.db.get_value(
|
||||
"OAuth Client", client_id, "redirect_uris"
|
||||
).split(get_url_delimiter())
|
||||
|
||||
if redirect_uri in redirect_uris:
|
||||
return True
|
||||
|
|
@ -89,7 +42,9 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
# The redirect used if none has been supplied.
|
||||
# Prefer your clients to pre register a redirect uri rather than
|
||||
# supplying one on each authorization request.
|
||||
redirect_uri = frappe.db.get_value("OAuth Client", client_id, 'default_redirect_uri')
|
||||
redirect_uri = frappe.db.get_value(
|
||||
"OAuth Client", client_id, "default_redirect_uri"
|
||||
)
|
||||
return redirect_uri
|
||||
|
||||
def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs):
|
||||
|
|
@ -101,19 +56,23 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
# Scopes a client will authorize for if none are supplied in the
|
||||
# authorization request.
|
||||
scopes = get_client_scopes(client_id)
|
||||
request.scopes = scopes #Apparently this is possible.
|
||||
request.scopes = scopes # Apparently this is possible.
|
||||
return scopes
|
||||
|
||||
def validate_response_type(self, client_id, response_type, client, request, *args, **kwargs):
|
||||
# Clients should only be allowed to use one type of response type, the
|
||||
# one associated with their one allowed grant type.
|
||||
# In this case it must be "code".
|
||||
allowed_response_types = [client.response_type.lower(),
|
||||
"code token", "code id_token", "code token id_token",
|
||||
"code+token", "code+id_token", "code+token id_token"]
|
||||
|
||||
return (response_type in allowed_response_types)
|
||||
def validate_response_type(
|
||||
self, client_id, response_type, client, request, *args, **kwargs
|
||||
):
|
||||
allowed_response_types = [
|
||||
# From OAuth Client response_type field
|
||||
client.response_type.lower(),
|
||||
# OIDC
|
||||
"id_token",
|
||||
"id_token token",
|
||||
"code id_token",
|
||||
"code token id_token",
|
||||
]
|
||||
|
||||
return response_type in allowed_response_types
|
||||
|
||||
# Post-authorization
|
||||
|
||||
|
|
@ -121,38 +80,69 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
|
||||
cookie_dict = get_cookie_dict_from_headers(request)
|
||||
|
||||
oac = frappe.new_doc('OAuth Authorization Code')
|
||||
oac = frappe.new_doc("OAuth Authorization Code")
|
||||
oac.scopes = get_url_delimiter().join(request.scopes)
|
||||
oac.redirect_uri_bound_to_authorization_code = request.redirect_uri
|
||||
oac.client = client_id
|
||||
oac.user = unquote(cookie_dict['user_id'].value)
|
||||
oac.authorization_code = code['code']
|
||||
oac.user = unquote(cookie_dict["user_id"].value)
|
||||
oac.authorization_code = code["code"]
|
||||
|
||||
if request.nonce:
|
||||
oac.nonce = request.nonce
|
||||
|
||||
if request.code_challenge and request.code_challenge_method:
|
||||
oac.code_challenge = request.code_challenge
|
||||
oac.code_challenge_method = request.code_challenge_method.lower()
|
||||
|
||||
oac.save(ignore_permissions=True)
|
||||
frappe.db.commit()
|
||||
|
||||
def authenticate_client(self, request, *args, **kwargs):
|
||||
#Get ClientID in URL
|
||||
# Get ClientID in URL
|
||||
if request.client_id:
|
||||
oc = frappe.get_doc("OAuth Client", request.client_id)
|
||||
else:
|
||||
#Extract token, instantiate OAuth Bearer Token and use clientid from there.
|
||||
# Extract token, instantiate OAuth Bearer Token and use clientid from there.
|
||||
if "refresh_token" in frappe.form_dict:
|
||||
oc = frappe.get_doc("OAuth Client", frappe.db.get_value("OAuth Bearer Token", {"refresh_token": frappe.form_dict["refresh_token"]}, 'client'))
|
||||
oc = frappe.get_doc(
|
||||
"OAuth Client",
|
||||
frappe.db.get_value(
|
||||
"OAuth Bearer Token",
|
||||
{"refresh_token": frappe.form_dict["refresh_token"]},
|
||||
"client",
|
||||
),
|
||||
)
|
||||
elif "token" in frappe.form_dict:
|
||||
oc = frappe.get_doc("OAuth Client", frappe.db.get_value("OAuth Bearer Token", frappe.form_dict["token"], 'client'))
|
||||
oc = frappe.get_doc(
|
||||
"OAuth Client",
|
||||
frappe.db.get_value(
|
||||
"OAuth Bearer Token", frappe.form_dict["token"], "client"
|
||||
),
|
||||
)
|
||||
else:
|
||||
oc = frappe.get_doc("OAuth Client", frappe.db.get_value("OAuth Bearer Token", frappe.get_request_header("Authorization").split(" ")[1], 'client'))
|
||||
oc = frappe.get_doc(
|
||||
"OAuth Client",
|
||||
frappe.db.get_value(
|
||||
"OAuth Bearer Token",
|
||||
frappe.get_request_header("Authorization").split(" ")[1],
|
||||
"client",
|
||||
),
|
||||
)
|
||||
try:
|
||||
request.client = request.client or oc.as_dict()
|
||||
except Exception as e:
|
||||
print("Failed body authentication: Application %s does not exist".format(cid=request.client_id))
|
||||
return generate_json_error_response(e)
|
||||
|
||||
cookie_dict = get_cookie_dict_from_headers(request)
|
||||
user_id = unquote(cookie_dict.get('user_id').value) if 'user_id' in cookie_dict else "Guest"
|
||||
user_id = (
|
||||
unquote(cookie_dict.get("user_id").value)
|
||||
if "user_id" in cookie_dict
|
||||
else "Guest"
|
||||
)
|
||||
return frappe.session.user == user_id
|
||||
|
||||
def authenticate_client_id(self, client_id, request, *args, **kwargs):
|
||||
cli_id = frappe.db.get_value('OAuth Client', client_id, 'name')
|
||||
cli_id = frappe.db.get_value("OAuth Client", client_id, "name")
|
||||
if not cli_id:
|
||||
# Don't allow public (non-authenticated) clients
|
||||
return False
|
||||
|
|
@ -164,28 +154,72 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
# Validate the code belongs to the client. Add associated scopes,
|
||||
# state and user to request.scopes and request.user.
|
||||
|
||||
validcodes = frappe.get_all("OAuth Authorization Code", filters={"client": client_id, "validity": "Valid"})
|
||||
validcodes = frappe.get_all(
|
||||
"OAuth Authorization Code",
|
||||
filters={"client": client_id, "validity": "Valid"},
|
||||
)
|
||||
|
||||
checkcodes = []
|
||||
for vcode in validcodes:
|
||||
checkcodes.append(vcode["name"])
|
||||
|
||||
if code in checkcodes:
|
||||
request.scopes = frappe.db.get_value("OAuth Authorization Code", code, 'scopes').split(get_url_delimiter())
|
||||
request.user = frappe.db.get_value("OAuth Authorization Code", code, 'user')
|
||||
return True
|
||||
else:
|
||||
request.scopes = frappe.db.get_value(
|
||||
"OAuth Authorization Code", code, "scopes"
|
||||
).split(get_url_delimiter())
|
||||
request.user = frappe.db.get_value("OAuth Authorization Code", code, "user")
|
||||
code_challenge_method = frappe.db.get_value(
|
||||
"OAuth Authorization Code", code, "code_challenge_method"
|
||||
)
|
||||
code_challenge = frappe.db.get_value(
|
||||
"OAuth Authorization Code", code, "code_challenge"
|
||||
)
|
||||
|
||||
if code_challenge and not request.code_verifier:
|
||||
if frappe.db.exists("OAuth Authorization Code", code):
|
||||
frappe.delete_doc(
|
||||
"OAuth Authorization Code", code, ignore_permissions=True
|
||||
)
|
||||
frappe.db.commit()
|
||||
return False
|
||||
|
||||
def confirm_redirect_uri(self, client_id, code, redirect_uri, client, *args, **kwargs):
|
||||
saved_redirect_uri = frappe.db.get_value('OAuth Client', client_id, 'default_redirect_uri')
|
||||
if code_challenge_method == "s256":
|
||||
m = hashlib.sha256()
|
||||
m.update(bytes(request.code_verifier, "utf-8"))
|
||||
code_verifier = base64.b64encode(m.digest()).decode("utf-8")
|
||||
code_verifier = re.sub(r"\+", "-", code_verifier)
|
||||
code_verifier = re.sub(r"\/", "_", code_verifier)
|
||||
code_verifier = re.sub(r"=", "", code_verifier)
|
||||
return code_challenge == code_verifier
|
||||
|
||||
elif code_challenge_method == "plain":
|
||||
return code_challenge == request.code_verifier
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def confirm_redirect_uri(
|
||||
self, client_id, code, redirect_uri, client, *args, **kwargs
|
||||
):
|
||||
saved_redirect_uri = frappe.db.get_value(
|
||||
"OAuth Client", client_id, "default_redirect_uri"
|
||||
)
|
||||
|
||||
redirect_uris = frappe.db.get_value("OAuth Client", client_id, "redirect_uris")
|
||||
|
||||
if redirect_uris:
|
||||
redirect_uris = redirect_uris.split(get_url_delimiter())
|
||||
return redirect_uri in redirect_uris
|
||||
|
||||
return saved_redirect_uri == redirect_uri
|
||||
|
||||
def validate_grant_type(self, client_id, grant_type, client, request, *args, **kwargs):
|
||||
def validate_grant_type(
|
||||
self, client_id, grant_type, client, request, *args, **kwargs
|
||||
):
|
||||
# Clients should only be allowed to use one type of grant.
|
||||
# In this case, it must be "authorization_code" or "refresh_token"
|
||||
return (grant_type in ["authorization_code", "refresh_token", "password"])
|
||||
return grant_type in ["authorization_code", "refresh_token", "password"]
|
||||
|
||||
def save_bearer_token(self, token, request, *args, **kwargs):
|
||||
# Remember to associate it with request.scopes, request.user and
|
||||
|
|
@ -195,19 +229,30 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
# access_token to now + expires_in seconds.
|
||||
|
||||
otoken = frappe.new_doc("OAuth Bearer Token")
|
||||
otoken.client = request.client['name']
|
||||
otoken.client = request.client["name"]
|
||||
try:
|
||||
otoken.user = request.user if request.user else frappe.db.get_value("OAuth Bearer Token", {"refresh_token":request.body.get("refresh_token")}, "user")
|
||||
except Exception as e:
|
||||
otoken.user = (
|
||||
request.user
|
||||
if request.user
|
||||
else frappe.db.get_value(
|
||||
"OAuth Bearer Token",
|
||||
{"refresh_token": request.body.get("refresh_token")},
|
||||
"user",
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
otoken.user = frappe.session.user
|
||||
|
||||
otoken.scopes = get_url_delimiter().join(request.scopes)
|
||||
otoken.access_token = token['access_token']
|
||||
otoken.refresh_token = token.get('refresh_token')
|
||||
otoken.expires_in = token['expires_in']
|
||||
otoken.access_token = token["access_token"]
|
||||
otoken.refresh_token = token.get("refresh_token")
|
||||
otoken.expires_in = token["expires_in"]
|
||||
otoken.save(ignore_permissions=True)
|
||||
frappe.db.commit()
|
||||
|
||||
default_redirect_uri = frappe.db.get_value("OAuth Client", request.client['name'], "default_redirect_uri")
|
||||
default_redirect_uri = frappe.db.get_value(
|
||||
"OAuth Client", request.client["name"], "default_redirect_uri"
|
||||
)
|
||||
return default_redirect_uri
|
||||
|
||||
def invalidate_authorization_code(self, client_id, code, request, *args, **kwargs):
|
||||
|
|
@ -222,24 +267,35 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
def validate_bearer_token(self, token, scopes, request):
|
||||
# Remember to check expiration and scope membership
|
||||
otoken = frappe.get_doc("OAuth Bearer Token", token)
|
||||
token_expiration_local = otoken.expiration_time.replace(tzinfo=pytz.timezone(frappe.utils.get_time_zone()))
|
||||
token_expiration_local = otoken.expiration_time.replace(
|
||||
tzinfo=pytz.timezone(frappe.utils.get_time_zone())
|
||||
)
|
||||
token_expiration_utc = token_expiration_local.astimezone(pytz.utc)
|
||||
is_token_valid = (frappe.utils.datetime.datetime.utcnow().replace(tzinfo=pytz.utc) < token_expiration_utc) \
|
||||
and otoken.status != "Revoked"
|
||||
client_scopes = frappe.db.get_value("OAuth Client", otoken.client, 'scopes').split(get_url_delimiter())
|
||||
is_token_valid = (
|
||||
frappe.utils.datetime.datetime.utcnow().replace(tzinfo=pytz.utc)
|
||||
< token_expiration_utc
|
||||
) and otoken.status != "Revoked"
|
||||
client_scopes = frappe.db.get_value(
|
||||
"OAuth Client", otoken.client, "scopes"
|
||||
).split(get_url_delimiter())
|
||||
are_scopes_valid = True
|
||||
for scp in scopes:
|
||||
are_scopes_valid = are_scopes_valid and True if scp in client_scopes else False
|
||||
are_scopes_valid = (
|
||||
are_scopes_valid and True if scp in client_scopes else False
|
||||
)
|
||||
|
||||
return is_token_valid and are_scopes_valid
|
||||
|
||||
# Token refresh request
|
||||
|
||||
def get_original_scopes(self, refresh_token, request, *args, **kwargs):
|
||||
# Obtain the token associated with the given refresh_token and
|
||||
# return its scopes, these will be passed on to the refreshed
|
||||
# access token if the client did not specify a scope during the
|
||||
# request.
|
||||
obearer_token = frappe.get_doc("OAuth Bearer Token", {"refresh_token": refresh_token})
|
||||
obearer_token = frappe.get_doc(
|
||||
"OAuth Bearer Token", {"refresh_token": refresh_token}
|
||||
)
|
||||
return obearer_token.scopes
|
||||
|
||||
def revoke_token(self, token, token_type_hint, request, *args, **kwargs):
|
||||
|
|
@ -252,34 +308,36 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
Method is used by:
|
||||
- Revocation Endpoint
|
||||
"""
|
||||
otoken = None
|
||||
|
||||
if token_type_hint == "access_token":
|
||||
otoken = frappe.db.set_value("OAuth Bearer Token", token, 'status', 'Revoked')
|
||||
frappe.db.set_value("OAuth Bearer Token", token, "status", "Revoked")
|
||||
elif token_type_hint == "refresh_token":
|
||||
otoken = frappe.db.set_value("OAuth Bearer Token", {"refresh_token": token}, 'status', 'Revoked')
|
||||
frappe.db.set_value(
|
||||
"OAuth Bearer Token", {"refresh_token": token}, "status", "Revoked"
|
||||
)
|
||||
else:
|
||||
otoken = frappe.db.set_value("OAuth Bearer Token", token, 'status', 'Revoked')
|
||||
frappe.db.set_value("OAuth Bearer Token", token, "status", "Revoked")
|
||||
frappe.db.commit()
|
||||
|
||||
def validate_refresh_token(self, refresh_token, client, request, *args, **kwargs):
|
||||
# """Ensure the Bearer token is valid and authorized access to scopes.
|
||||
"""Ensure the Bearer token is valid and authorized access to scopes.
|
||||
|
||||
# OBS! The request.user attribute should be set to the resource owner
|
||||
# associated with this refresh token.
|
||||
OBS! The request.user attribute should be set to the resource owner
|
||||
associated with this refresh token.
|
||||
|
||||
# :param refresh_token: Unicode refresh token
|
||||
# :param client: Client object set by you, see authenticate_client.
|
||||
# :param request: The HTTP Request (oauthlib.common.Request)
|
||||
# :rtype: True or False
|
||||
:param refresh_token: Unicode refresh token
|
||||
:param client: Client object set by you, see authenticate_client.
|
||||
:param request: The HTTP Request (oauthlib.common.Request)
|
||||
:rtype: True or False
|
||||
|
||||
# Method is used by:
|
||||
# - Authorization Code Grant (indirectly by issuing refresh tokens)
|
||||
# - Resource Owner Password Credentials Grant (also indirectly)
|
||||
# - Refresh Token Grant
|
||||
# """
|
||||
Method is used by:
|
||||
- Authorization Code Grant (indirectly by issuing refresh tokens)
|
||||
- Resource Owner Password Credentials Grant (also indirectly)
|
||||
- Refresh Token Grant
|
||||
"""
|
||||
|
||||
otoken = frappe.get_doc("OAuth Bearer Token", {"refresh_token": refresh_token, "status": "Active"})
|
||||
otoken = frappe.get_doc(
|
||||
"OAuth Bearer Token", {"refresh_token": refresh_token, "status": "Active"}
|
||||
)
|
||||
|
||||
if not otoken:
|
||||
return False
|
||||
|
|
@ -287,36 +345,84 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
return True
|
||||
|
||||
# OpenID Connect
|
||||
def get_id_token(self, token, token_handler, request):
|
||||
"""
|
||||
In the OpenID Connect workflows when an ID Token is requested this method is called.
|
||||
Subclasses should implement the construction, signing and optional encryption of the
|
||||
ID Token as described in the OpenID Connect spec.
|
||||
|
||||
In addition to the standard OAuth2 request properties, the request may also contain
|
||||
these OIDC specific properties which are useful to this method:
|
||||
def finalize_id_token(self, id_token, token, token_handler, request):
|
||||
# Check whether frappe server URL is set
|
||||
id_token_header = {"typ": "jwt", "alg": "HS256"}
|
||||
|
||||
- nonce, if workflow is implicit or hybrid and it was provided
|
||||
- claims, if provided to the original Authorization Code request
|
||||
user = frappe.get_doc(
|
||||
"User",
|
||||
frappe.session.user,
|
||||
)
|
||||
|
||||
The token parameter is a dict which may contain an ``access_token`` entry, in which
|
||||
case the resulting ID Token *should* include a calculated ``at_hash`` claim.
|
||||
if request.nonce:
|
||||
id_token["nonce"] = request.nonce
|
||||
|
||||
Similarly, when the request parameter has a ``code`` property defined, the ID Token
|
||||
*should* include a calculated ``c_hash`` claim.
|
||||
userinfo = get_userinfo(user)
|
||||
|
||||
http://openid.net/specs/openid-connect-core-1_0.html (sections `3.1.3.6`_, `3.2.2.10`_, `3.3.2.11`_)
|
||||
if userinfo.get("iss"):
|
||||
id_token["iss"] = userinfo.get("iss")
|
||||
|
||||
.. _`3.1.3.6`: http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
|
||||
.. _`3.2.2.10`: http://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDToken
|
||||
.. _`3.3.2.11`: http://openid.net/specs/openid-connect-core-1_0.html#HybridIDToken
|
||||
if "openid" in request.scopes:
|
||||
id_token.update(userinfo)
|
||||
|
||||
:param token: A Bearer token dict
|
||||
:param token_handler: the token handler (BearerToken class)
|
||||
:param request: the HTTP Request (oauthlib.common.Request)
|
||||
:return: The ID Token (a JWS signed JWT)
|
||||
"""
|
||||
# the request.scope should be used by the get_id_token() method to determine which claims to include in the resulting id_token
|
||||
id_token_encoded = jwt.encode(
|
||||
payload=id_token,
|
||||
key=request.client.client_secret,
|
||||
algorithm="HS256",
|
||||
headers=id_token_header,
|
||||
)
|
||||
|
||||
return frappe.safe_decode(id_token_encoded)
|
||||
|
||||
def get_authorization_code_nonce(self, client_id, code, redirect_uri, request):
|
||||
if frappe.get_value("OAuth Authorization Code", code, "validity") == "Valid":
|
||||
return frappe.get_value("OAuth Authorization Code", code, "nonce")
|
||||
|
||||
return None
|
||||
|
||||
def get_authorization_code_scopes(self, client_id, code, redirect_uri, request):
|
||||
scope = frappe.get_value("OAuth Client", client_id, "scopes")
|
||||
if not scope:
|
||||
scope = []
|
||||
else:
|
||||
scope = scope.split(get_url_delimiter())
|
||||
|
||||
return scope
|
||||
|
||||
def get_jwt_bearer_token(self, token, token_handler, request):
|
||||
now = datetime.datetime.now()
|
||||
id_token = dict(
|
||||
aud=token.client_id,
|
||||
iat=round(now.timestamp()),
|
||||
at_hash=calculate_at_hash(token.access_token, hashlib.sha256),
|
||||
)
|
||||
return self.finalize_id_token(id_token, token, token_handler, request)
|
||||
|
||||
def get_userinfo_claims(self, request):
|
||||
user = frappe.get_doc("User", frappe.session.user)
|
||||
userinfo = get_userinfo(user)
|
||||
return userinfo
|
||||
|
||||
def validate_id_token(self, token, scopes, request):
|
||||
try:
|
||||
id_token = frappe.get_doc("OAuth Bearer Token", token)
|
||||
if id_token.status == "Active":
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
def validate_jwt_bearer_token(self, token, scopes, request):
|
||||
try:
|
||||
jwt = frappe.get_doc("OAuth Bearer Token", token)
|
||||
if jwt.status == "Active":
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
def validate_silent_authorization(self, request):
|
||||
"""Ensure the logged in user has authorized silent OpenID authorization.
|
||||
|
|
@ -377,9 +483,48 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
- OpenIDConnectImplicit
|
||||
- OpenIDConnectHybrid
|
||||
"""
|
||||
if id_token_hint and id_token_hint == frappe.db.get_value("User Social Login", {"parent":frappe.session.user, "provider": "frappe"}, "userid"):
|
||||
if id_token_hint:
|
||||
try:
|
||||
user = None
|
||||
payload = jwt.decode(
|
||||
id_token_hint,
|
||||
options={
|
||||
"verify_signature": False,
|
||||
"verify_aud": False,
|
||||
},
|
||||
)
|
||||
client_id, client_secret = frappe.get_value(
|
||||
"OAuth Client",
|
||||
payload.get("aud"),
|
||||
["client_id", "client_secret"],
|
||||
)
|
||||
|
||||
if payload.get("sub") and client_id and client_secret:
|
||||
user = frappe.db.get_value(
|
||||
"User Social Login",
|
||||
{"userid": payload.get("sub"), "provider": "frappe"},
|
||||
"parent",
|
||||
)
|
||||
user = frappe.get_doc("User", user)
|
||||
verified_payload = jwt.decode(
|
||||
id_token_hint,
|
||||
key=client_secret,
|
||||
audience=client_id,
|
||||
algorithm="HS256",
|
||||
options={
|
||||
"verify_exp": False,
|
||||
},
|
||||
)
|
||||
|
||||
if verified_payload:
|
||||
return user.name == frappe.session.user
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
elif frappe.session.user != "Guest":
|
||||
return True
|
||||
else:
|
||||
|
||||
return False
|
||||
|
||||
def validate_user(self, username, password, client, request, *args, **kwargs):
|
||||
|
|
@ -390,15 +535,21 @@ class OAuthWebRequestValidator(RequestValidator):
|
|||
"""
|
||||
login_manager = LoginManager()
|
||||
login_manager.authenticate(username, password)
|
||||
|
||||
if login_manager.user == "Guest":
|
||||
return False
|
||||
|
||||
request.user = login_manager.user
|
||||
return True
|
||||
|
||||
|
||||
def get_cookie_dict_from_headers(r):
|
||||
cookie = cookies.BaseCookie()
|
||||
if r.headers.get('Cookie'):
|
||||
cookie.load(r.headers.get('Cookie'))
|
||||
if r.headers.get("Cookie"):
|
||||
cookie.load(r.headers.get("Cookie"))
|
||||
return cookie
|
||||
|
||||
|
||||
def calculate_at_hash(access_token, hash_alg):
|
||||
"""Helper method for calculating an access token
|
||||
hash, as described in http://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
|
||||
|
|
@ -412,18 +563,22 @@ def calculate_at_hash(access_token, hash_alg):
|
|||
access_token (str): An access token string.
|
||||
hash_alg (callable): A callable returning a hash object, e.g. hashlib.sha256
|
||||
"""
|
||||
hash_digest = hash_alg(access_token.encode('utf-8')).digest()
|
||||
hash_digest = hash_alg(access_token.encode("utf-8")).digest()
|
||||
cut_at = int(len(hash_digest) / 2)
|
||||
truncated = hash_digest[:cut_at]
|
||||
from jwt.utils import base64url_encode
|
||||
|
||||
at_hash = base64url_encode(truncated)
|
||||
return at_hash.decode('utf-8')
|
||||
return at_hash.decode("utf-8")
|
||||
|
||||
|
||||
def delete_oauth2_data():
|
||||
# Delete Invalid Authorization Code and Revoked Token
|
||||
commit_code, commit_token = False, False
|
||||
code_list = frappe.get_all("OAuth Authorization Code", filters={"validity":"Invalid"})
|
||||
token_list = frappe.get_all("OAuth Bearer Token", filters={"status":"Revoked"})
|
||||
code_list = frappe.get_all(
|
||||
"OAuth Authorization Code", filters={"validity": "Invalid"}
|
||||
)
|
||||
token_list = frappe.get_all("OAuth Bearer Token", filters={"status": "Revoked"})
|
||||
if len(code_list) > 0:
|
||||
commit_code = True
|
||||
if len(token_list) > 0:
|
||||
|
|
@ -439,3 +594,58 @@ def delete_oauth2_data():
|
|||
def get_client_scopes(client_id):
|
||||
scopes_string = frappe.db.get_value("OAuth Client", client_id, "scopes")
|
||||
return scopes_string.split()
|
||||
|
||||
|
||||
def get_userinfo(user):
|
||||
picture = None
|
||||
frappe_server_url = get_server_url()
|
||||
|
||||
if user.user_image:
|
||||
if frappe.utils.validate_url(user.user_image):
|
||||
picture = user.user_image
|
||||
else:
|
||||
picture = frappe_server_url + "/" + user.user_image
|
||||
|
||||
userinfo = frappe._dict(
|
||||
{
|
||||
"sub": frappe.db.get_value(
|
||||
"User Social Login",
|
||||
{"parent": user.name, "provider": "frappe"},
|
||||
"userid",
|
||||
),
|
||||
"name": " ".join(filter(None, [user.first_name, user.last_name])),
|
||||
"given_name": user.first_name,
|
||||
"family_name": user.last_name,
|
||||
"email": user.email,
|
||||
"picture": picture,
|
||||
"roles": frappe.get_roles(user.name),
|
||||
"iss": frappe_server_url,
|
||||
}
|
||||
)
|
||||
|
||||
return userinfo
|
||||
|
||||
|
||||
def get_url_delimiter(separator_character=" "):
|
||||
return separator_character
|
||||
|
||||
|
||||
def generate_json_error_response(e):
|
||||
if not e:
|
||||
e = frappe._dict({})
|
||||
|
||||
frappe.local.response = frappe._dict(
|
||||
{
|
||||
"description": getattr(e, "description", "Internal Server Error"),
|
||||
"status_code": getattr(e, "status_code", 500),
|
||||
"error": getattr(e, "error", "internal_server_error"),
|
||||
}
|
||||
)
|
||||
frappe.local.response["http_status_code"] = getattr(e, "status_code", 500)
|
||||
return
|
||||
|
||||
|
||||
def get_server_url():
|
||||
request_url = urlparse(frappe.request.url)
|
||||
request_url = f"{request_url.scheme}://{request_url.netloc}"
|
||||
return frappe.get_value("Social Login Key", "frappe", "base_url") or request_url
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Print Settings", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Print Settings
|
||||
() => frappe.tests.make('Print Settings', [
|
||||
// values to be set
|
||||
{key: 'value'}
|
||||
]),
|
||||
() => {
|
||||
assert.equal(cur_frm.doc.key, 'value');
|
||||
},
|
||||
() => done()
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
/* eslint-disable */
|
||||
// rename this file from _test_[name] to test_[name] to activate
|
||||
// and remove above this line
|
||||
|
||||
QUnit.test("test: Print Style", function (assert) {
|
||||
let done = assert.async();
|
||||
|
||||
// number of asserts
|
||||
assert.expect(1);
|
||||
|
||||
frappe.run_serially([
|
||||
// insert a new Print Style
|
||||
() => frappe.tests.make('Print Style', [
|
||||
// values to be set
|
||||
{print_style_name: 'Test Print Style'},
|
||||
{css: '/* some css value */'}
|
||||
]),
|
||||
]);
|
||||
|
||||
});
|
||||
|
|
@ -162,7 +162,6 @@
|
|||
"public/js/frappe/utils/common.js",
|
||||
"public/js/frappe/utils/urllib.js",
|
||||
"public/js/frappe/utils/pretty_date.js",
|
||||
"public/js/frappe/utils/test_utils.js",
|
||||
"public/js/frappe/utils/tools.js",
|
||||
"public/js/frappe/utils/datetime.js",
|
||||
"public/js/frappe/utils/number_format.js",
|
||||
|
|
|
|||
6
frappe/public/css/animate.min.css
vendored
6
frappe/public/css/animate.min.css
vendored
File diff suppressed because one or more lines are too long
|
|
@ -1,91 +0,0 @@
|
|||
/* .avatar {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
.avatar-frame {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
padding: 50% 0px;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.avatar img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.avatar-empty {
|
||||
border: 1px dashed #d1d8dd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.avatar-small {
|
||||
margin-right: 5px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
.avatar-small .standard-image {
|
||||
font-size: 14px;
|
||||
}
|
||||
.avatar-small .avatar-frame {
|
||||
border-radius: 3px;
|
||||
}
|
||||
.avatar-medium {
|
||||
margin-right: 5px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
.avatar-medium .standard-image {
|
||||
font-size: 18px;
|
||||
}
|
||||
.avatar-large {
|
||||
margin-right: 10px;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
}
|
||||
.avatar-large .standard-image {
|
||||
font-size: 36px;
|
||||
}
|
||||
.avatar-xl {
|
||||
margin-right: 10px;
|
||||
width: 108px;
|
||||
height: 108px;
|
||||
}
|
||||
.avatar-xl .standard-image {
|
||||
font-size: 72px;
|
||||
}
|
||||
.avatar-xs {
|
||||
margin-right: 3px;
|
||||
margin-top: -2px;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.avatar-xs .standard-image {
|
||||
font-size: 9px;
|
||||
}
|
||||
.avatar-text {
|
||||
display: inline;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
padding-bottom: 100%;
|
||||
}
|
||||
.standard-image {
|
||||
width: 100%;
|
||||
height: 0;
|
||||
padding: 50% 0;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
line-height: 0px;
|
||||
color: #d1d8dd;
|
||||
border: 1px solid #d1d8dd;
|
||||
font-weight: normal;
|
||||
margin-top: -1px;
|
||||
} */
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
.fc-toolbar {
|
||||
padding: 15px;
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
.fc-view-container {
|
||||
margin-left: -1px;
|
||||
margin-right: -1px;
|
||||
}
|
||||
th.fc-widget-header {
|
||||
background-color: #F7FAFC;
|
||||
color: #8C99A5;
|
||||
}
|
||||
.fc-unthemed th,
|
||||
.fc-unthemed td,
|
||||
.fc-unthemed hr,
|
||||
.fc-unthemed thead,
|
||||
.fc-unthemed tbody,
|
||||
.fc-unthemed .fc-row,
|
||||
.fc-unthemed .fc-popover {
|
||||
border-color: #d1d8dd !important;
|
||||
}
|
||||
.fc-unthemed .fc-today {
|
||||
background-color: #FFF !important;
|
||||
}
|
||||
.fc-unthemed .fc-today .fc-day-number {
|
||||
background-color: #5E64FF;
|
||||
min-width: 20px;
|
||||
border-radius: 50%;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
}
|
||||
.fc-highlight {
|
||||
background-color: #fffce7 !important;
|
||||
}
|
||||
.fc-event {
|
||||
border: 1px solid #E8DDFF;
|
||||
/* default BORDER color */
|
||||
background-color: #E8DDFF;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.fc-scroller {
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
.fc-day-top {
|
||||
padding: 12px 12px 0 0 !important;
|
||||
}
|
||||
th.fc-day-header {
|
||||
text-align: right !important;
|
||||
padding: 10px 12px 10px 0 !important;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
}
|
||||
.fc.fc-unthemed .fc-toolbar {
|
||||
padding: 15px;
|
||||
}
|
||||
.fc-event-container .fc-content {
|
||||
padding: 3px;
|
||||
}
|
||||
.fc-left h2 {
|
||||
font-size: 14px;
|
||||
}
|
||||
.fc button {
|
||||
height: auto !important;
|
||||
font-size: 12px !important;
|
||||
outline: none !important;
|
||||
}
|
||||
.fc button .fc-icon {
|
||||
top: -1px !important;
|
||||
}
|
||||
.fc-state-active {
|
||||
box-shadow: none !important;
|
||||
background: #cfdce5 !important;
|
||||
}
|
||||
.fc-day-grid-event {
|
||||
border: none !important;
|
||||
margin: 5px 4px 0 !important;
|
||||
padding: 1px 5px !important;
|
||||
}
|
||||
.fc-bg-orange {
|
||||
background-color: #FDD2C2 !important;
|
||||
color: #A64F33 !important;
|
||||
}
|
||||
.fc-bg-orange.fc-start {
|
||||
border-left: 3px solid #FDA688 !important;
|
||||
}
|
||||
.fc-bg-red {
|
||||
background-color: #FEC3C5 !important;
|
||||
color: #A63336 !important;
|
||||
}
|
||||
.fc-bg-red.fc-start {
|
||||
border-left: 3px solid #FD8B8B !important;
|
||||
}
|
||||
.fc-bg-skyblue {
|
||||
background-color: #D4F1FF !important;
|
||||
color: #548DA8 !important;
|
||||
}
|
||||
.fc-bg-skyblue.fc-start {
|
||||
border-left: 3px solid #AAE3FE !important;
|
||||
}
|
||||
.fc-bg-green {
|
||||
background-color: #EBF7CF !important;
|
||||
color: #7C9142 !important;
|
||||
}
|
||||
.fc-bg-green.fc-start {
|
||||
border-left: 3px solid #D9F29E !important;
|
||||
}
|
||||
.fc-bg-blue {
|
||||
background-color: #D1D3FC !important;
|
||||
color: #4C51A2 !important;
|
||||
}
|
||||
.fc-bg-blue.fc-start {
|
||||
border-left: 3px solid #A3A5FC !important;
|
||||
}
|
||||
.fc-bg-yellow {
|
||||
background-color: #FEF9CF !important;
|
||||
color: #A99E4C !important;
|
||||
}
|
||||
.fc-bg-yellow.fc-start {
|
||||
border-left: 3px solid #FFF5A0 !important;
|
||||
}
|
||||
|
|
@ -1,471 +0,0 @@
|
|||
/* the element that this class is applied to, should have a max width for this to work*/
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||
}
|
||||
a {
|
||||
cursor: pointer;
|
||||
}
|
||||
a,
|
||||
a:hover,
|
||||
a:active,
|
||||
a:focus,
|
||||
.btn,
|
||||
.btn:hover,
|
||||
.btn:active,
|
||||
.btn:focus {
|
||||
outline: 0;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
p {
|
||||
margin: 10px 0px;
|
||||
}
|
||||
.text-color {
|
||||
color: #36414C !important;
|
||||
}
|
||||
.text-muted {
|
||||
color: #8D99A6 !important;
|
||||
}
|
||||
.text-extra-muted {
|
||||
color: #d1d8dd !important;
|
||||
}
|
||||
a,
|
||||
.badge {
|
||||
-webkit-transition: 0.2s;
|
||||
-o-transition: 0.2s;
|
||||
transition: 0.2s;
|
||||
}
|
||||
.btn {
|
||||
-webkit-transition: background-color 0.2s;
|
||||
-o-transition: background-color 0.2s;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
a.disabled,
|
||||
a.disabled:hover {
|
||||
color: #888;
|
||||
cursor: default;
|
||||
text-decoration: none;
|
||||
}
|
||||
a.grey,
|
||||
.sidebar-section a,
|
||||
.control-value a,
|
||||
.data-row a {
|
||||
text-decoration: none;
|
||||
}
|
||||
a.grey:hover,
|
||||
.sidebar-section a:hover,
|
||||
.control-value a:hover,
|
||||
.data-row a:hover,
|
||||
a.grey:focus,
|
||||
.sidebar-section a:focus,
|
||||
.control-value a:focus,
|
||||
.data-row a:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
a.text-muted,
|
||||
a.text-extra-muted {
|
||||
text-decoration: none;
|
||||
}
|
||||
.underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
.bold,
|
||||
.strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
kbd {
|
||||
color: inherit;
|
||||
background-color: #F0F4F7;
|
||||
}
|
||||
.btn [class^="fa fa-"],
|
||||
.nav [class^="fa fa-"],
|
||||
.btn [class*="fa fa-"],
|
||||
.nav [class*="fa fa-"] {
|
||||
display: inline-block;
|
||||
}
|
||||
.dropdown-menu > li > a {
|
||||
padding: 14px;
|
||||
white-space: normal;
|
||||
}
|
||||
.dropdown-menu {
|
||||
min-width: 200px;
|
||||
padding: 0px;
|
||||
font-size: 12px;
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
border-radius: 0px 0px 4px 4px;
|
||||
}
|
||||
.dropdown-menu .dropdown-header {
|
||||
padding: 3px 14px;
|
||||
font-size: 11px;
|
||||
font-weight: 200;
|
||||
padding-top: 12px;
|
||||
}
|
||||
.dropdown-menu .divider {
|
||||
margin: 0px;
|
||||
}
|
||||
a.badge-hover:hover .badge,
|
||||
a.badge-hover:focus .badge,
|
||||
a.badge-hover:active .badge {
|
||||
background-color: #D8DFE5;
|
||||
}
|
||||
.msgprint {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.msgprint pre {
|
||||
text-align: left;
|
||||
}
|
||||
.centered {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
}
|
||||
.border-top {
|
||||
border-top: 1px solid #d1d8dd;
|
||||
}
|
||||
.border-bottom {
|
||||
border-bottom: 1px solid #d1d8dd;
|
||||
}
|
||||
.border-left {
|
||||
border-left: 1px solid #d1d8dd;
|
||||
}
|
||||
.border-right {
|
||||
border-right: 1px solid #d1d8dd;
|
||||
}
|
||||
.border {
|
||||
border: 1px solid #d1d8dd;
|
||||
}
|
||||
.close-inline {
|
||||
font-size: 120%;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
display: inline-block;
|
||||
}
|
||||
.close-inline:hover,
|
||||
.close-inline:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
.middle {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.full-center-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
.full-center {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 100%;
|
||||
transform: translate(-50%, -50%);
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
}
|
||||
#freeze {
|
||||
z-index: 1020;
|
||||
bottom: 0px;
|
||||
opacity: 0;
|
||||
background-color: #fafbfc;
|
||||
}
|
||||
#freeze .freeze-message-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
#freeze .freeze-message {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 100%;
|
||||
transform: translate(-50%, -50%);
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
color: #36414C !important;
|
||||
}
|
||||
#freeze.dark {
|
||||
background-color: #334143;
|
||||
}
|
||||
#freeze.in {
|
||||
opacity: 0.5;
|
||||
}
|
||||
a.no-decoration {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
a.no-decoration:hover,
|
||||
a.no-decoration:focus,
|
||||
a.no-decoration:active {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
.padding {
|
||||
padding: 15px;
|
||||
}
|
||||
.margin {
|
||||
margin: 15px;
|
||||
}
|
||||
.margin-top {
|
||||
margin-top: 15px;
|
||||
}
|
||||
.margin-bottom {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.margin-left {
|
||||
margin-left: 15px;
|
||||
}
|
||||
.margin-right {
|
||||
margin-right: 15px;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.text-center-xs {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.grayscale {
|
||||
-webkit-filter: grayscale(100%);
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
.uppercase {
|
||||
padding-bottom: 4px;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.4px;
|
||||
color: #8D99A6;
|
||||
}
|
||||
.ellipsis {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
.font-heavy {
|
||||
font-weight: 900;
|
||||
}
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.avatar {
|
||||
padding: 2px;
|
||||
}
|
||||
.navbar .frappe-chat-toggle {
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
.navbar .octicon {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.frappe-chat > .frappe-chat-popper {
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
margin: 15px;
|
||||
z-index: 1035;
|
||||
}
|
||||
.frappe-chat > .frappe-chat-popper > .frappe-chat-popper-collapse > .panel {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 350px;
|
||||
height: 500px;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.frappe-chat > .frappe-chat-popper > .frappe-chat-popper-collapse > .panel .vcenter {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.frappe-chat > .frappe-chat-popper > .frappe-chat-popper-collapse > .panel .panel-heading .panel-title .media-heading {
|
||||
font-size: 12px;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
.frappe-chat > .frappe-chat-popper > .frappe-chat-popper-collapse > .panel .panel-heading .panel-title .media-subtitle {
|
||||
font-size: 12px;
|
||||
}
|
||||
.frappe-chat > .frappe-chat-popper > .frappe-chat-popper-collapse > .panel .panel-heading .frappe-chat-action-bar form {
|
||||
width: 100%;
|
||||
}
|
||||
.frappe-chat > .frappe-chat-popper > .frappe-chat-popper-collapse > .panel .panel-heading .frappe-chat-action-bar .btn-action {
|
||||
margin-left: 5px !important;
|
||||
}
|
||||
.frappe-chat > .frappe-chat-popper > .frappe-chat-popper-collapse > .panel .frappe-chat-room-list {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
padding: 0 1px 0 1px;
|
||||
}
|
||||
.frappe-chat > .frappe-chat-popper > .frappe-chat-popper-collapse > .panel .frappe-chat-room-list > li > a {
|
||||
border-radius: 0px !important;
|
||||
}
|
||||
.frappe-chat > .frappe-chat-popper > .frappe-chat-popper-collapse > .panel .frappe-chat-room-list .media .media-heading,
|
||||
.frappe-chat > .frappe-chat-popper > .frappe-chat-popper-collapse > .panel .frappe-chat-room-list .media .media-subtitle {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
vertical-align: middle;
|
||||
max-width: 180px;
|
||||
}
|
||||
.frappe-chat > .frappe-chat-popper > .frappe-chat-popper-collapse > .panel.panel-bg {
|
||||
background-size: 350px 500px;
|
||||
background-image: url(/assets/frappe/images/chat/wallpaper-default.jpg);
|
||||
}
|
||||
.frappe-chat > .frappe-chat-popper > .frappe-chat-popper-collapse > .panel.panel-span {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
overflow: auto;
|
||||
border-radius: 0px;
|
||||
}
|
||||
.frappe-chat > .frappe-chat-popper > .frappe-chat-popper-collapse > .panel.panel-span .panel-heading {
|
||||
border-radius: 0px;
|
||||
}
|
||||
.frappe-chat .panel {
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
.frappe-chat .panel .chat-form .form-control {
|
||||
font-size: 12px;
|
||||
}
|
||||
.frappe-chat .panel .chat-form .dropdown-menu {
|
||||
border-radius: 4px;
|
||||
}
|
||||
.frappe-chat .panel .chat-form .hint-list.list-group {
|
||||
margin: 0px;
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.frappe-chat .panel .chat-form .hint-list.list-group .hint-list-item.list-group-item:first-child,
|
||||
.frappe-chat .panel .chat-form .hint-list.list-group .hint-list-item.list-group-item:last-child {
|
||||
border-radius: 0px !important;
|
||||
}
|
||||
.frappe-chat .panel .chat-form .hint-list.list-group .hint-list-item.list-group-item:first-child a,
|
||||
.frappe-chat .panel .chat-form .hint-list.list-group .hint-list-item.list-group-item:last-child a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.frappe-chat-popper-collapse > .panel > .panel-heading {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
.frappe-chat-popper-collapse > .panel > .panel-heading .btn-back {
|
||||
margin-right: 5px;
|
||||
}
|
||||
.frappe-chat-popper-collapse > .panel > .panel-heading .avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
.chat-room-footer .chat-form {
|
||||
border-top: 1px solid #D1D8DD;
|
||||
}
|
||||
.chat-room-footer .chat-form .input-group-btn .btn {
|
||||
background: white;
|
||||
border-radius: 0px;
|
||||
}
|
||||
.chat-room-footer .chat-form .form-control {
|
||||
line-height: 27px;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
resize: none;
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.chat-room-footer .chat-form .fa {
|
||||
font-size: 14px;
|
||||
transition: color 0.5s;
|
||||
}
|
||||
.chat-list {
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.chat-list .chat-list-item {
|
||||
cursor: pointer;
|
||||
border: none !important;
|
||||
padding: 5px 10px;
|
||||
background: transparent;
|
||||
}
|
||||
.chat-list .chat-list-item .avatar {
|
||||
vertical-align: top;
|
||||
}
|
||||
.chat-list .chat-list-item .avatar .standard-image {
|
||||
background-color: white;
|
||||
}
|
||||
.chat-list .chat-list-item .chat-bubble {
|
||||
min-width: 20%;
|
||||
max-width: 75%;
|
||||
display: inline-block;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
-webkit-box-shadow: 0px 0.1px 0.5px 0px rgba(0, 0, 0, 0.5);
|
||||
-moz-box-shadow: 0px 0.1px 0.5px 0px rgba(0, 0, 0, 0.5);
|
||||
box-shadow: 0px 0.1px 0.5px 0px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.chat-list .chat-list-item .chat-bubble.chat-bubble-l {
|
||||
background-color: white;
|
||||
}
|
||||
.chat-list .chat-list-item .chat-bubble.chat-bubble-l.chat-groupable {
|
||||
margin-left: 40px;
|
||||
}
|
||||
.chat-list .chat-list-item .chat-bubble.chat-bubble-l .chat-bubble-meta > .chat-bubble-creation,
|
||||
.chat-list .chat-list-item .chat-bubble.chat-bubble-l .chat-bubble-meta > .chat-bubble-check i {
|
||||
color: #577287 !important;
|
||||
}
|
||||
.chat-list .chat-list-item .chat-bubble.chat-bubble-r {
|
||||
text-align: right;
|
||||
background-color: #EBF7CF;
|
||||
}
|
||||
.chat-list .chat-list-item .chat-bubble.chat-bubble-r .chat-bubble-meta > .chat-bubble-creation,
|
||||
.chat-list .chat-list-item .chat-bubble.chat-bubble-r .chat-bubble-meta > .chat-bubble-check i {
|
||||
color: #80ab1c !important;
|
||||
}
|
||||
.chat-list .chat-list-item .chat-bubble .chat-bubble-author {
|
||||
font-size: 12px;
|
||||
}
|
||||
.chat-list .chat-list-item .chat-bubble .chat-bubble-author a {
|
||||
font-weight: 700;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
.chat-list .chat-list-item .chat-bubble .chat-bubble-content {
|
||||
margin-bottom: 5px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.chat-list .chat-list-item .chat-bubble .chat-bubble-meta {
|
||||
font-size: 10px;
|
||||
}
|
||||
.chat-list .chat-list-item .chat-bubble .chat-bubble-meta > .chat-bubble-check {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.chat-list .chat-list-item .chat-bubble .chat-bubble-meta > .chat-bubble-check i {
|
||||
font-size: 12px;
|
||||
}
|
||||
.chat-list-notification {
|
||||
text-align: center;
|
||||
}
|
||||
.chat-list-notification-content {
|
||||
color: white;
|
||||
background-color: #8D99A6;
|
||||
display: inline-block;
|
||||
/* padding: 5px; */
|
||||
border-radius: 20px;
|
||||
opacity: 0.5;
|
||||
font-size: 10px;
|
||||
padding: 5px;
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,292 +0,0 @@
|
|||
body[data-route=""] .navbar-default,
|
||||
body[data-route="desktop"] .navbar-default {
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
border-color: rgba(54, 65, 76, 0.1);
|
||||
}
|
||||
#page-desktop {
|
||||
min-width: 100%;
|
||||
margin-top: 0px;
|
||||
border: 0px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
.case-wrapper {
|
||||
position: relative;
|
||||
margin: 0px;
|
||||
float: left;
|
||||
width: 138px;
|
||||
height: 140px;
|
||||
}
|
||||
.case-label {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 0.4px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
transition: 0.2s;
|
||||
-webkit-transition: 0.2s;
|
||||
text-shadow: 0px 1px 2px rgba(0, 0, 0, 0.5), 0px 1px 5px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.app-icon {
|
||||
padding: 20px;
|
||||
display: inline-block;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
border-radius: 16px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.app-icon .inner,
|
||||
.app-icon i {
|
||||
font-size: 32px;
|
||||
min-width: 32px;
|
||||
color: #fafbfc;
|
||||
display: inline-block;
|
||||
transition: 0.2s;
|
||||
-webkit-transition: 0.2s;
|
||||
text-shadow: -1px 1px 5px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.app-icon .inner {
|
||||
line-height: 32px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.app-icon svg,
|
||||
.app-icon img {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
}
|
||||
.app-icon path {
|
||||
transition: 0.2s;
|
||||
-webkit-transition: 0.2s;
|
||||
}
|
||||
@-webkit-keyframes wiggle {
|
||||
0% {
|
||||
-webkit-transform: rotate(3deg);
|
||||
}
|
||||
50% {
|
||||
-webkit-transform: rotate(-3deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(3deg);
|
||||
}
|
||||
}
|
||||
@-moz-keyframes wiggle {
|
||||
0% {
|
||||
-moz-transform: rotate(3deg);
|
||||
}
|
||||
50% {
|
||||
-moz-transform: rotate(-3deg);
|
||||
}
|
||||
100% {
|
||||
-moz-transform: rotate(3deg);
|
||||
}
|
||||
}
|
||||
@keyframes wiggle {
|
||||
0% {
|
||||
transform: rotate(3deg);
|
||||
}
|
||||
50% {
|
||||
transform: rotate(-3deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(3deg);
|
||||
}
|
||||
}
|
||||
.wiggle {
|
||||
-webkit-animation: wiggle 0.2s linear infinite;
|
||||
-moz-animation: wiggle 0.2s linear infinite;
|
||||
animation: wiggle 0.2s linear infinite;
|
||||
}
|
||||
.circle {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
top: -10px;
|
||||
color: #fff;
|
||||
background-color: #ff5858;
|
||||
padding: 6px;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
border-radius: 25px;
|
||||
min-width: 25px;
|
||||
height: 25px;
|
||||
text-align: center;
|
||||
text-shadow: none;
|
||||
letter-spacing: normal;
|
||||
cursor: pointer;
|
||||
}
|
||||
.app-icon:hover i,
|
||||
.app-icon:hover {
|
||||
color: #fff;
|
||||
}
|
||||
.app-icon-small {
|
||||
padding: 12px;
|
||||
}
|
||||
.app-icon-img.app-icon-small {
|
||||
padding: 0px;
|
||||
height: 54px;
|
||||
width: 54px;
|
||||
}
|
||||
.app-icon-img {
|
||||
padding: 0px;
|
||||
height: 70px;
|
||||
width: 70px;
|
||||
}
|
||||
.app-icon-img img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.rtl {
|
||||
direction: rtl;
|
||||
}
|
||||
#icon-grid {
|
||||
padding-top: 15px;
|
||||
padding-bottom: 30px;
|
||||
max-width: 970px;
|
||||
margin: auto;
|
||||
}
|
||||
@media (min-width: 768px) and (max-width: 991px) {
|
||||
#icon-grid {
|
||||
max-width: 690px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
#icon-grid {
|
||||
max-width: 320px;
|
||||
}
|
||||
.case-wrapper {
|
||||
width: 80px;
|
||||
height: 90px;
|
||||
}
|
||||
.case-label {
|
||||
font-size: 80%;
|
||||
font-weight: normal;
|
||||
margin-top: 7px;
|
||||
}
|
||||
.app-icon {
|
||||
padding: 10px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.app-icon i {
|
||||
font-size: 32px;
|
||||
min-width: 32px;
|
||||
}
|
||||
.app-icon svg,
|
||||
.app-icon img {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
}
|
||||
.circle {
|
||||
right: 0px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 320px) {
|
||||
#icon-grid {
|
||||
max-width: 280px;
|
||||
}
|
||||
.case-wrapper {
|
||||
width: 70px;
|
||||
height: 90px;
|
||||
}
|
||||
}
|
||||
.all-applications-dialog .desktop-app-search {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.all-applications-dialog hr {
|
||||
margin: 10px -15px;
|
||||
}
|
||||
.all-applications-dialog .checkbox {
|
||||
margin-top: 3px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.desktop-list-item {
|
||||
padding: 10px 15px;
|
||||
border-bottom: 1px solid #d1d8dd;
|
||||
cursor: pointer;
|
||||
}
|
||||
.desktop-list-item:hover,
|
||||
.desktop-list-item:focus {
|
||||
background-color: #F7FAFC;
|
||||
}
|
||||
.desktop-list-item h4 {
|
||||
display: inline-block;
|
||||
}
|
||||
.navbar-set-desktop-icons {
|
||||
display: none;
|
||||
}
|
||||
body[data-route=""] .navbar-set-desktop-icons,
|
||||
body[data-route="desktop"] .navbar-set-desktop-icons {
|
||||
display: block;
|
||||
}
|
||||
.help-message-wrapper {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
width: 100%;
|
||||
padding: 0px 10px;
|
||||
}
|
||||
.help-message-wrapper .help-message-container {
|
||||
position: relative;
|
||||
text-align: left;
|
||||
margin: auto;
|
||||
max-width: 500px;
|
||||
background-color: #fff;
|
||||
padding: 10px 15px 15px 15px;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.help-message-wrapper h5 {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.help-message-wrapper .help-message-item {
|
||||
font-size: 12px;
|
||||
}
|
||||
.help-message-wrapper .octicon {
|
||||
color: #8D99A6;
|
||||
cursor: pointer;
|
||||
width: 20px;
|
||||
}
|
||||
.help-message-wrapper .octicon.disabled {
|
||||
color: #d1d8dd;
|
||||
}
|
||||
.help-message-wrapper .octicon:hover {
|
||||
color: #36414C;
|
||||
text-decoration: none;
|
||||
}
|
||||
.help-message-wrapper .left-arrow {
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
bottom: 15px;
|
||||
text-align: left;
|
||||
}
|
||||
.help-message-wrapper .right-arrow {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
bottom: 15px;
|
||||
text-align: right;
|
||||
}
|
||||
.help-message-wrapper .indicator {
|
||||
color: #36414C;
|
||||
}
|
||||
.help-message-wrapper .help-progress {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
height: 4px;
|
||||
background-color: #fafbfc;
|
||||
}
|
||||
.help-message-wrapper .help-progress {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
height: 4px;
|
||||
width: 100px;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.help-message-wrapper .help-progress-bar {
|
||||
display: inline-block;
|
||||
height: 4px;
|
||||
background-color: #5E64FF;
|
||||
float: left;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
.align-center {
|
||||
align-items: center;
|
||||
}
|
||||
.level {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.level-left,
|
||||
.level-right {
|
||||
display: flex;
|
||||
flex-basis: auto;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
}
|
||||
.level-left.is-flexible,
|
||||
.level-right.is-flexible {
|
||||
flex-grow: initial;
|
||||
flex-shrink: initial;
|
||||
}
|
||||
.level-left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.level-right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.level-item {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-basis: auto;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load diff
|
Before Width: | Height: | Size: 326 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load diff
|
Before Width: | Height: | Size: 1 MiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load diff
|
Before Width: | Height: | Size: 1 MiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load diff
|
Before Width: | Height: | Size: 1 MiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load diff
|
Before Width: | Height: | Size: 1 MiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load diff
|
Before Width: | Height: | Size: 1 MiB |
Binary file not shown.
|
|
@ -1,64 +0,0 @@
|
|||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
src: url('/assets/frappe/css/font/open-sans/OpenSans-Regular-webfont.eot');
|
||||
src: local("Open Sans"),
|
||||
local("OpenSans"),
|
||||
url('/assets/frappe/css/font/open-sans/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'),
|
||||
url('/assets/frappe/css/font/open-sans/OpenSans-Regular-webfont.woff') format('woff'),
|
||||
url('/assets/frappe/css/font/open-sans/OpenSans-Regular-webfont.ttf') format('truetype'),
|
||||
url('/assets/frappe/css/font/open-sans/OpenSans-Regular-webfont.svg#open_sansregular') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
src: url('/assets/frappe/css/font/open-sans/OpenSans-Italic-webfont.eot');
|
||||
src: local("Open Sans Italic"),
|
||||
local("OpenSans-Italic"),
|
||||
url('/assets/frappe/css/font/open-sans/OpenSans-Italic-webfont.eot?#iefix') format('embedded-opentype'),
|
||||
url('/assets/frappe/css/font/open-sans/OpenSans-Italic-webfont.woff') format('woff'),
|
||||
url('/assets/frappe/css/font/open-sans/OpenSans-Italic-webfont.ttf') format('truetype'),
|
||||
url('/assets/frappe/css/font/open-sans/OpenSans-Italic-webfont.svg#open_sansitalic') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
src: url('/assets/frappe/css/font/open-sans/OpenSans-Light-webfont.eot');
|
||||
src: local("Open Sans Light"),
|
||||
local("OpenSans-Light"),
|
||||
url('/assets/frappe/css/font/open-sans/OpenSans-Light-webfont.eot?#iefix') format('embedded-opentype'),
|
||||
url('/assets/frappe/css/font/open-sans/OpenSans-Light-webfont.woff') format('woff'),
|
||||
url('/assets/frappe/css/font/open-sans/OpenSans-Light-webfont.ttf') format('truetype'),
|
||||
url('/assets/frappe/css/font/open-sans/OpenSans-Light-webfont.svg#open_sanslight') format('svg');
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
src: url('/assets/frappe/css/font/open-sans/OpenSans-Bold-webfont.eot');
|
||||
src: local("Open Sans Bold"),
|
||||
local("OpenSans-Bold"),
|
||||
url('/assets/frappe/css/font/open-sans/OpenSans-Bold-webfont.eot?#iefix') format('embedded-opentype'),
|
||||
url('/assets/frappe/css/font/open-sans/OpenSans-Bold-webfont.woff') format('woff'),
|
||||
url('/assets/frappe/css/font/open-sans/OpenSans-Bold-webfont.ttf') format('truetype'),
|
||||
url('/assets/frappe/css/font/open-sans/OpenSans-Bold-webfont.svg#open_sansbold') format('svg');
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
src: url('/assets/frappe/css/font/open-sans/OpenSans-Semibold-webfont.eot');
|
||||
src: local("Open Sans Semibold"),
|
||||
local("OpenSans-Semibold"),
|
||||
url('/assets/frappe/css/font/open-sans/OpenSans-Semibold-webfont.eot?#iefix') format('embedded-opentype'),
|
||||
url('/assets/frappe/css/font/open-sans/OpenSans-Semibold-webfont.woff') format('woff'),
|
||||
url('/assets/frappe/css/font/open-sans/OpenSans-Semibold-webfont.ttf') format('truetype'),
|
||||
url('/assets/frappe/css/font/open-sans/OpenSans-Semibold-webfont.svg#open_sanssemibold') format('svg');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
|
@ -1,219 +0,0 @@
|
|||
/* the element that this class is applied to, should have a max width for this to work*/
|
||||
.form-grid {
|
||||
border: 1px solid #d1d8dd;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.form-grid.error {
|
||||
border-color: #ff5858;
|
||||
}
|
||||
.grid-heading-row {
|
||||
border-bottom: 1px solid #d1d8dd;
|
||||
background-color: #F7FAFC;
|
||||
font-weight: bold;
|
||||
}
|
||||
.grid-row {
|
||||
padding: 0px 15px;
|
||||
border-bottom: 1px solid #d1d8dd;
|
||||
-webkit-transition: 0.2s;
|
||||
-o-transition: 0.2s;
|
||||
transition: 0.2s;
|
||||
}
|
||||
.grid-row:last-child {
|
||||
border: none;
|
||||
}
|
||||
.rows .grid-row .data-row,
|
||||
.rows .grid-row .grid-footer-toolbar,
|
||||
.grid-form-heading {
|
||||
cursor: pointer;
|
||||
}
|
||||
.data-row textarea {
|
||||
height: 40px;
|
||||
}
|
||||
.grid-body {
|
||||
background-color: #fff;
|
||||
}
|
||||
.form-grid .data-row.highlight {
|
||||
background-color: #fffdf4;
|
||||
}
|
||||
.form-grid .data-row.sortable-handle {
|
||||
cursor: move;
|
||||
}
|
||||
.form-column.col-sm-6 .form-grid .row-index > span {
|
||||
display: none;
|
||||
}
|
||||
.form-grid .template-row {
|
||||
padding: 8px 15px;
|
||||
}
|
||||
.grid-body .data-row {
|
||||
font-size: 12px;
|
||||
}
|
||||
.grid-empty,
|
||||
.list-loading {
|
||||
padding: 10px 15px;
|
||||
color: #d1d8dd;
|
||||
}
|
||||
.grid-static-col,
|
||||
.row-index {
|
||||
height: 39px;
|
||||
padding: 10px 15px;
|
||||
max-height: 200px;
|
||||
border-right: 1px solid #d1d8dd;
|
||||
}
|
||||
.editable-form .grid-static-col.bold {
|
||||
font-weight: bold;
|
||||
background-color: #fffdf4;
|
||||
}
|
||||
.validated-form .grid-static-col.error {
|
||||
background-color: #FFDCDC;
|
||||
}
|
||||
.grid-static-col input[type="checkbox"] {
|
||||
margin-left: -16px !important;
|
||||
}
|
||||
.row-index {
|
||||
text-align: right;
|
||||
}
|
||||
.grid-row > .row .col:last-child {
|
||||
margin-right: -10px;
|
||||
}
|
||||
.grid-row > .row .col {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
.grid-body .btn-open-row {
|
||||
padding-top: 5px;
|
||||
}
|
||||
.grid-body .editable-row .grid-static-col {
|
||||
padding: 0px !important;
|
||||
}
|
||||
.grid-body .editable-row .checkbox {
|
||||
margin: 0px;
|
||||
text-align: center;
|
||||
margin-top: 9px;
|
||||
}
|
||||
.grid-body .editable-row textarea {
|
||||
height: 38px !important;
|
||||
}
|
||||
.grid-body .editable-row .form-control {
|
||||
border-radius: 0px;
|
||||
border: 0px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 9px;
|
||||
height: 38px;
|
||||
}
|
||||
.grid-body .editable-row .link-btn {
|
||||
top: 8px;
|
||||
}
|
||||
.grid-body .editable-row .form-control:focus {
|
||||
border-color: #8D99A6;
|
||||
z-index: 2;
|
||||
}
|
||||
.grid-body .editable-row .has-error .form-control {
|
||||
z-index: 1;
|
||||
}
|
||||
.grid-body .editable-row .has-error .form-control:focus {
|
||||
border-color: #ff5858;
|
||||
}
|
||||
.grid-body .editable-row input[data-fieldtype="Int"],
|
||||
.grid-body .editable-row input[data-fieldtype="Float"],
|
||||
.grid-body .editable-row input[data-fieldtype="Currency"] {
|
||||
text-align: right;
|
||||
}
|
||||
.grid-body .grid-static-col[data-fieldtype="Button"] .field-area {
|
||||
margin-top: 5px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.grid-body .grid-static-col[data-fieldtype="Button"] .field-area button {
|
||||
height: 27px;
|
||||
}
|
||||
.grid-body .grid-static-col[data-fieldtype="Code"] {
|
||||
overflow: hidden;
|
||||
}
|
||||
.grid-body .grid-static-col[data-fieldtype="Code"] .static-area {
|
||||
margin-top: -5px;
|
||||
}
|
||||
.grid-body .grid-static-col[data-fieldtype="Code"] .static-area pre {
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
.grid-body .grid-static-col[data-fieldtype="Text Editor"] {
|
||||
overflow: hidden;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.grid-body .btn-open-row {
|
||||
margin-top: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
.editable-row .frappe-control {
|
||||
padding-top: 0px !important;
|
||||
padding-bottom: 0px !important;
|
||||
margin-left: -5px !important;
|
||||
margin-right: -5px !important;
|
||||
}
|
||||
}
|
||||
.row-data > .row {
|
||||
margin-left: 15px;
|
||||
}
|
||||
.grid-row td {
|
||||
vertical-align: top;
|
||||
}
|
||||
.grid-row p {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.grid-row .frappe-control {
|
||||
margin-bottom: 0px;
|
||||
position: relative;
|
||||
}
|
||||
.grid-row .col-sm-6 .editor-toolbar-text-group,
|
||||
.grid-row .col-sm-6 .editor-toolbar-align-group {
|
||||
display: none;
|
||||
}
|
||||
.grid-row .col-sm-6 .text-editor {
|
||||
height: 200px;
|
||||
}
|
||||
.grid-row .col-sm-6 .markdown-text-editor {
|
||||
height: 251px;
|
||||
}
|
||||
.form-in-grid {
|
||||
background-color: white;
|
||||
z-index: 1021;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 0.2s ease;
|
||||
-o-transition: opacity 0.2s ease;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
.grid-row-open .form-in-grid {
|
||||
opacity: 1;
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
margin: 0px -15px;
|
||||
}
|
||||
.grid-form-heading {
|
||||
padding: 10px 15px;
|
||||
font-size: 120%;
|
||||
border-bottom: 1px solid #d1d8dd;
|
||||
}
|
||||
.grid-footer {
|
||||
background-color: #fff;
|
||||
}
|
||||
.grid-footer-toolbar {
|
||||
padding: 10px 15px;
|
||||
border-top: 1px solid #d1d8dd;
|
||||
}
|
||||
.grid-overflow-no-ellipsis {
|
||||
word-wrap: break-word;
|
||||
overflow: hidden;
|
||||
padding-right: 0px;
|
||||
}
|
||||
.grid-overflow-ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding-right: 0px;
|
||||
}
|
||||
.grid-label {
|
||||
margin-right: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
.data-table {
|
||||
margin-left: -1px;
|
||||
margin-top: -1px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.data-table .data-table-col .edit-cell {
|
||||
padding: 0;
|
||||
}
|
||||
.data-table .data-table-col .edit-cell input {
|
||||
font-size: inherit;
|
||||
height: 34px;
|
||||
}
|
||||
.data-table .frappe-control {
|
||||
margin: 0;
|
||||
}
|
||||
.data-table .form-group {
|
||||
margin: 0;
|
||||
}
|
||||
.data-table .form-control {
|
||||
border-radius: 0px;
|
||||
border: none;
|
||||
}
|
||||
.data-table .link-btn {
|
||||
top: 6px;
|
||||
}
|
||||
.data-table select {
|
||||
height: 34px;
|
||||
}
|
||||
.data-table .checkbox {
|
||||
margin: 7px 0 7px 8px;
|
||||
}
|
||||
.data-table [data-fieldtype="Color"] .control-input {
|
||||
overflow: hidden;
|
||||
}
|
||||
.data-table .body-scrollable::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.data-table .data-table-header {
|
||||
background-color: #F7FAFC;
|
||||
color: #8D99A6;
|
||||
}
|
||||
.data-table .data-table-row.row-update {
|
||||
animation: 500ms breathe forwards;
|
||||
}
|
||||
.data-table .data-table-row.row-highlight {
|
||||
background-color: #fffdf4;
|
||||
}
|
||||
@keyframes breathe {
|
||||
0% {
|
||||
background-color: transparent;
|
||||
}
|
||||
50% {
|
||||
background-color: #fffdf4;
|
||||
}
|
||||
100% {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
.gantt .bar-milestone .bar {
|
||||
fill: #FD8B8B;
|
||||
}
|
||||
.gantt .bar-milestone .bar-progress {
|
||||
fill: #FC4F51;
|
||||
}
|
||||
.frappe-rtl .gantt {
|
||||
direction: ltr;
|
||||
}
|
||||
|
|
@ -1,277 +0,0 @@
|
|||
pre {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Original style from softwaremaniacs.org (c) Ivan Sagalaev <Maniac@SoftwareManiacs.Org>
|
||||
|
||||
*/
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 0.5em;
|
||||
background: #f0f0f0;
|
||||
-webkit-text-size-adjust: none;
|
||||
}
|
||||
|
||||
.hljs,
|
||||
.hljs-subst,
|
||||
/*.hljs-tag .hljs-title,*/
|
||||
.nginx .hljs-title {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-title,
|
||||
.hljs-constant,
|
||||
.hljs-parent,
|
||||
.hljs-tag .hljs-value,
|
||||
.hljs-rules .hljs-value,
|
||||
.hljs-preprocessor,
|
||||
.hljs-pragma,
|
||||
.haml .hljs-symbol,
|
||||
.ruby .hljs-symbol,
|
||||
.ruby .hljs-symbol .hljs-string,
|
||||
.hljs-template_tag,
|
||||
.django .hljs-variable,
|
||||
.smalltalk .hljs-class,
|
||||
.hljs-addition,
|
||||
.hljs-flow,
|
||||
.hljs-stream,
|
||||
.bash .hljs-variable,
|
||||
.apache .hljs-tag,
|
||||
.apache .hljs-cbracket,
|
||||
.tex .hljs-command,
|
||||
.tex .hljs-special,
|
||||
.erlang_repl .hljs-function_or_atom,
|
||||
.asciidoc .hljs-header,
|
||||
.markdown .hljs-header,
|
||||
.coffeescript .hljs-attribute {
|
||||
color: #800;
|
||||
}
|
||||
|
||||
.smartquote,
|
||||
.hljs-comment,
|
||||
.hljs-annotation,
|
||||
.hljs-template_comment,
|
||||
.diff .hljs-header,
|
||||
.hljs-chunk,
|
||||
.asciidoc .hljs-blockquote,
|
||||
.markdown .hljs-blockquote {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.hljs-number,
|
||||
.hljs-date,
|
||||
.hljs-regexp,
|
||||
.hljs-literal,
|
||||
.hljs-hexcolor,
|
||||
.smalltalk .hljs-symbol,
|
||||
.smalltalk .hljs-char,
|
||||
.go .hljs-constant,
|
||||
.hljs-change,
|
||||
.lasso .hljs-variable,
|
||||
.makefile .hljs-variable,
|
||||
.asciidoc .hljs-bullet,
|
||||
.markdown .hljs-bullet,
|
||||
.asciidoc .hljs-link_url,
|
||||
.markdown .hljs-link_url {
|
||||
color: #080;
|
||||
}
|
||||
|
||||
.hljs-label,
|
||||
.hljs-javadoc,
|
||||
.ruby .hljs-string,
|
||||
.hljs-decorator,
|
||||
.hljs-filter .hljs-argument,
|
||||
.hljs-localvars,
|
||||
.hljs-array,
|
||||
.hljs-attr_selector,
|
||||
.hljs-important,
|
||||
.hljs-pseudo,
|
||||
.hljs-pi,
|
||||
.haml .hljs-bullet,
|
||||
.hljs-doctype,
|
||||
.hljs-deletion,
|
||||
.hljs-envvar,
|
||||
.hljs-shebang,
|
||||
.apache .hljs-sqbracket,
|
||||
.nginx .hljs-built_in,
|
||||
.tex .hljs-formula,
|
||||
.erlang_repl .hljs-reserved,
|
||||
.hljs-prompt,
|
||||
.asciidoc .hljs-link_label,
|
||||
.markdown .hljs-link_label,
|
||||
.vhdl .hljs-attribute,
|
||||
.clojure .hljs-attribute,
|
||||
.asciidoc .hljs-attribute,
|
||||
.lasso .hljs-attribute,
|
||||
.coffeescript .hljs-property,
|
||||
.hljs-phony {
|
||||
color: #88f;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-id,
|
||||
.hljs-title,
|
||||
.hljs-built_in,
|
||||
.css .hljs-tag,
|
||||
.hljs-javadoctag,
|
||||
.hljs-phpdoc,
|
||||
.hljs-dartdoc,
|
||||
.hljs-yardoctag,
|
||||
.smalltalk .hljs-class,
|
||||
.hljs-winutils,
|
||||
.bash .hljs-variable,
|
||||
.apache .hljs-tag,
|
||||
.hljs-type,
|
||||
.hljs-typename,
|
||||
.tex .hljs-command,
|
||||
.asciidoc .hljs-strong,
|
||||
.markdown .hljs-strong,
|
||||
.hljs-request,
|
||||
.hljs-status {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.asciidoc .hljs-emphasis,
|
||||
.markdown .hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.nginx .hljs-built_in {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.coffeescript .javascript,
|
||||
.javascript .xml,
|
||||
.lasso .markup,
|
||||
.tex .hljs-formula,
|
||||
.xml .javascript,
|
||||
.xml .vbscript,
|
||||
.xml .css,
|
||||
.xml .hljs-cdata {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Zenburn style from voldmar.ru (c) Vladimir Epifanov <voldmar@voldmar.ru>
|
||||
based on dark.css by Ivan Sagalaev
|
||||
|
||||
*/
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding: 0.5em;
|
||||
background: #3f3f3f;
|
||||
color: #dcdcdc;
|
||||
-webkit-text-size-adjust: none;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-tag,
|
||||
.css .hljs-class,
|
||||
.css .hljs-id,
|
||||
.lisp .hljs-title,
|
||||
.nginx .hljs-title,
|
||||
.hljs-request,
|
||||
.hljs-status,
|
||||
.clojure .hljs-attribute {
|
||||
color: #e3ceab;
|
||||
}
|
||||
|
||||
.django .hljs-template_tag,
|
||||
.django .hljs-variable,
|
||||
.django .hljs-filter .hljs-argument {
|
||||
color: #dcdcdc;
|
||||
}
|
||||
|
||||
.hljs-number,
|
||||
.hljs-date {
|
||||
color: #8cd0d3;
|
||||
}
|
||||
|
||||
.dos .hljs-envvar,
|
||||
.dos .hljs-stream,
|
||||
.hljs-variable,
|
||||
.apache .hljs-sqbracket {
|
||||
color: #efdcbc;
|
||||
}
|
||||
|
||||
.dos .hljs-flow,
|
||||
.diff .hljs-change,
|
||||
.python .exception,
|
||||
.python .hljs-built_in,
|
||||
.hljs-literal,
|
||||
.tex .hljs-special {
|
||||
color: #efefaf;
|
||||
}
|
||||
|
||||
.diff .hljs-chunk,
|
||||
.hljs-subst {
|
||||
color: #8f8f8f;
|
||||
}
|
||||
|
||||
.dos .hljs-keyword,
|
||||
.hljs-decorator,
|
||||
.hljs-title,
|
||||
.hljs-type,
|
||||
.diff .hljs-header,
|
||||
.ruby .hljs-class .hljs-parent,
|
||||
.apache .hljs-tag,
|
||||
.nginx .hljs-built_in,
|
||||
.tex .hljs-command,
|
||||
.hljs-prompt {
|
||||
color: #efef8f;
|
||||
}
|
||||
|
||||
.dos .hljs-winutils,
|
||||
.ruby .hljs-symbol,
|
||||
.ruby .hljs-symbol .hljs-string,
|
||||
.ruby .hljs-string {
|
||||
color: #dca3a3;
|
||||
}
|
||||
|
||||
.diff .hljs-deletion,
|
||||
.hljs-string,
|
||||
.hljs-tag .hljs-value,
|
||||
.hljs-preprocessor,
|
||||
.hljs-pragma,
|
||||
.hljs-built_in,
|
||||
.hljs-javadoc,
|
||||
.smalltalk .hljs-class,
|
||||
.smalltalk .hljs-localvars,
|
||||
.smalltalk .hljs-array,
|
||||
.css .hljs-rules .hljs-value,
|
||||
.hljs-attr_selector,
|
||||
.hljs-pseudo,
|
||||
.apache .hljs-cbracket,
|
||||
.tex .hljs-formula,
|
||||
.coffeescript .hljs-attribute {
|
||||
color: #cc9393;
|
||||
}
|
||||
|
||||
.hljs-shebang,
|
||||
.diff .hljs-addition,
|
||||
.hljs-comment,
|
||||
.hljs-annotation,
|
||||
.hljs-template_comment,
|
||||
.hljs-pi,
|
||||
.hljs-doctype {
|
||||
color: #7f9f7f;
|
||||
}
|
||||
|
||||
.coffeescript .javascript,
|
||||
.javascript .xml,
|
||||
.tex .hljs-formula,
|
||||
.xml .javascript,
|
||||
.xml .vbscript,
|
||||
.xml .css,
|
||||
.xml .hljs-cdata {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
.indicator,
|
||||
.indicator-right {
|
||||
background: none;
|
||||
font-size: 12px;
|
||||
vertical-align: middle;
|
||||
font-weight: bold;
|
||||
color: #6c7680;
|
||||
}
|
||||
.indicator::before,
|
||||
.indicator-right::after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.indicator::before {
|
||||
margin: 0 4px 0 0px;
|
||||
}
|
||||
.indicator-right::after {
|
||||
margin: 0 0 0 4px;
|
||||
}
|
||||
.indicator.grey::before,
|
||||
.indicator-right.grey::after {
|
||||
background: #F0F4F7;
|
||||
}
|
||||
.indicator.blue::before,
|
||||
.indicator-right.blue::after {
|
||||
background: #5e64ff;
|
||||
}
|
||||
.indicator.red::before,
|
||||
.indicator-right.red::after {
|
||||
background: #ff5858;
|
||||
}
|
||||
.indicator.green::before,
|
||||
.indicator-right.green::after {
|
||||
background: #98d85b;
|
||||
}
|
||||
.indicator.orange::before,
|
||||
.indicator-right.orange::after {
|
||||
background: #ffa00a;
|
||||
}
|
||||
.indicator.purple::before,
|
||||
.indicator-right.purple::after {
|
||||
background: #743ee2;
|
||||
}
|
||||
.indicator.gray::before,
|
||||
.indicator-right.gray::after {
|
||||
background: #b8c2cc;
|
||||
}
|
||||
.indicator.black::before,
|
||||
.indicator-right.black::after {
|
||||
background: #36414C;
|
||||
}
|
||||
.indicator.yellow::before,
|
||||
.indicator-right.yellow::after {
|
||||
background: #FEEF72;
|
||||
}
|
||||
.indicator.light-blue::before,
|
||||
.indicator-right.light-blue::after {
|
||||
background: #7CD6FD;
|
||||
}
|
||||
.indicator.lightblue::before,
|
||||
.indicator-right.lightblue::after {
|
||||
background: #7CD6FD;
|
||||
}
|
||||
.modal-header .indicator {
|
||||
float: left;
|
||||
margin-top: 7.5px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
11
frappe/public/css/ionicons.min.css
vendored
11
frappe/public/css/ionicons.min.css
vendored
File diff suppressed because one or more lines are too long
|
|
@ -1,147 +0,0 @@
|
|||
.kanban {
|
||||
min-height: calc(100vh - 252px);
|
||||
background-color: #fafbfc;
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
}
|
||||
.kanban .kanban-column {
|
||||
flex: 0 0 230px;
|
||||
max-width: 230px;
|
||||
background-color: #fafbfc;
|
||||
border-right: 1px solid #d1d8dd;
|
||||
padding: 15px;
|
||||
}
|
||||
.kanban .kanban-column.add-new-column {
|
||||
order: 1;
|
||||
border-right: none;
|
||||
}
|
||||
.kanban .kanban-column-title {
|
||||
margin-top: 0;
|
||||
margin-bottom: 15px;
|
||||
position: relative;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
}
|
||||
.kanban .kanban-column-title .column-options .button-group {
|
||||
display: flex;
|
||||
padding: 12px 14px;
|
||||
}
|
||||
.kanban .kanban-column-title .column-options .button-group .btn.indicator {
|
||||
flex: 1;
|
||||
}
|
||||
.kanban .kanban-column-title .column-options .indicator::before {
|
||||
margin: 0;
|
||||
}
|
||||
.kanban .kanban-column-title:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.kanban .sortable-ghost > .kanban-card:not(.add-card) {
|
||||
background: #ccc !important;
|
||||
color: transparent;
|
||||
}
|
||||
.kanban .sortable-ghost > .kanban-card:not(.add-card) * {
|
||||
background: transparent !important;
|
||||
color: transparent !important;
|
||||
}
|
||||
.kanban .kanban-card {
|
||||
background-color: #fff;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
border-radius: 2px;
|
||||
padding: 6px 6px 6px 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.kanban .kanban-card.add-card {
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
color: #8D99A6;
|
||||
}
|
||||
.kanban .kanban-card.add-card:hover {
|
||||
box-shadow: none;
|
||||
color: #36414C;
|
||||
cursor: pointer;
|
||||
}
|
||||
.kanban .kanban-card.add-card .octicon-plus {
|
||||
top: -1px;
|
||||
font-size: 1em;
|
||||
margin-right: 5px;
|
||||
position: relative;
|
||||
}
|
||||
.kanban .kanban-card-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
.kanban .kanban-card:hover,
|
||||
.kanban .new-card-area,
|
||||
.kanban .edit-card-area {
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.kanban .kanban-card-wrapper:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
.kanban .kanban-card-wrapper:hover .kanban-card-edit {
|
||||
opacity: 1;
|
||||
}
|
||||
.kanban .kanban-card-title {
|
||||
max-width: 90%;
|
||||
font-size: 12px;
|
||||
}
|
||||
.kanban .kanban-card-edit {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
opacity: 0;
|
||||
transition: 0.2s ease;
|
||||
}
|
||||
.kanban .new-card-area,
|
||||
.kanban .edit-card-area {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.kanban .new-card-area textarea,
|
||||
.kanban .edit-card-area textarea {
|
||||
font-size: 12px;
|
||||
resize: none;
|
||||
border: none;
|
||||
background: none;
|
||||
overflow: hidden;
|
||||
word-wrap: break-word;
|
||||
width: 100%;
|
||||
}
|
||||
.kanban .new-card-area textarea:focus,
|
||||
.kanban .edit-card-area textarea:focus {
|
||||
outline: none;
|
||||
}
|
||||
.kanban .compose-column-form .new-column-title {
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
.kanban .add-new-column a:hover {
|
||||
color: #36414C !important;
|
||||
}
|
||||
.kanban .kanban-card-meta {
|
||||
margin-top: 8px;
|
||||
text-align: right;
|
||||
}
|
||||
.kanban .kanban-card-meta .avatar {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.kanban .kanban-empty-state {
|
||||
width: 100%;
|
||||
line-height: 400px;
|
||||
}
|
||||
body[data-route*="Kanban"] .modal .add-assignment:hover i {
|
||||
color: #36414C !important;
|
||||
}
|
||||
.edit-card-title .h4 {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.edit-card-title span:hover {
|
||||
background-color: #fffce7;
|
||||
cursor: pointer;
|
||||
}
|
||||
.edit-card-title input {
|
||||
border: none;
|
||||
outline: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
/* the element that this class is applied to, should have a max width for this to work*/
|
||||
|
|
@ -1,225 +0,0 @@
|
|||
/* the element that this class is applied to, should have a max width for this to work*/
|
||||
.navbar .dropdown-toggle {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
.navbar-fixed-top {
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
}
|
||||
.navbar a {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.navbar-icon-home {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.navbar-icon-home:hover,
|
||||
.navbar-icon-home:focus,
|
||||
.navbar-icon-home:active,
|
||||
.navbar-icon-home-hover {
|
||||
opacity: 1;
|
||||
Filter: alpha(opacity=100);
|
||||
/* For IE8 and earlier */
|
||||
}
|
||||
.navbar-user-image {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 3px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
@media (max-width: 991px) {
|
||||
.navbar-desk {
|
||||
width: 35% !important;
|
||||
}
|
||||
.navbar-desk ~ ul > li {
|
||||
float: left;
|
||||
}
|
||||
.navbar-desk ~ ul > li a {
|
||||
padding-left: 10px !important;
|
||||
padding-right: 10px !important;
|
||||
}
|
||||
.navbar-desk ~ ul > li a .avatar {
|
||||
margin-right: 0;
|
||||
}
|
||||
.dropdown-navbar-new-comments > a {
|
||||
padding: 8px 0 !important;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.navbar-desk {
|
||||
width: 50% !important;
|
||||
}
|
||||
}
|
||||
#search-modal .modal-dialog,
|
||||
#search-modal .modal-content {
|
||||
background: transparent;
|
||||
}
|
||||
#search-modal .modal-header {
|
||||
background: #fff;
|
||||
width: 100%;
|
||||
}
|
||||
#search-modal .modal-header form {
|
||||
vertical-align: middle;
|
||||
}
|
||||
#search-modal .modal-header button {
|
||||
line-height: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 9;
|
||||
padding: 9px;
|
||||
}
|
||||
.dropdown-navbar-new-comments > a {
|
||||
border: 0;
|
||||
}
|
||||
.dropdown-navbar-new-comments .dropdown-menu {
|
||||
margin-top: 0;
|
||||
}
|
||||
.dropdown-help .dropdown-menu {
|
||||
width: 350px !important;
|
||||
max-height: 440px;
|
||||
overflow: auto;
|
||||
}
|
||||
.dropdown-help .dropdown-menu .input-group {
|
||||
width: 100%;
|
||||
background-color: #f5f7fa;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
.dropdown-help .dropdown-menu input {
|
||||
width: 100%;
|
||||
padding: 5px 10px;
|
||||
outline: none;
|
||||
border-radius: 3px 0 0 3px;
|
||||
border: 1px solid #d1d8dd;
|
||||
opacity: 0.9;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.dropdown-help .dropdown-menu button {
|
||||
border: 1px solid #d1d8dd;
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.dropdown-help .dropdown-menu {
|
||||
position: fixed !important;
|
||||
top: 40px;
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
@media (max-width: 767px) {
|
||||
.dropdown-mobile.open .dropdown-menu {
|
||||
position: absolute;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.14902);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
background-color: #fff;
|
||||
right: 0;
|
||||
left: auto;
|
||||
}
|
||||
.dropdown-mobile.open .dropdown-menu > li > a {
|
||||
padding: 12px;
|
||||
}
|
||||
.dropdown-help {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
.navbar-new-comments {
|
||||
display: inline-block;
|
||||
min-width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
padding: 2px 5px;
|
||||
background-color: #b8c2cc;
|
||||
}
|
||||
.navbar-new-comments-true {
|
||||
background-color: #ff5858;
|
||||
}
|
||||
.navbar-form .awesomplete {
|
||||
margin-left: -15px;
|
||||
width: 300px;
|
||||
}
|
||||
@media (max-width: 1199px) {
|
||||
.navbar-form .awesomplete {
|
||||
width: 280px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 991px) {
|
||||
.navbar-form .awesomplete {
|
||||
width: 250px;
|
||||
}
|
||||
}
|
||||
#navbar-search {
|
||||
width: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
.navbar .navbar-search-icon {
|
||||
color: #6C7680;
|
||||
font-size: inherit;
|
||||
position: relative;
|
||||
right: 24px;
|
||||
top: 1px;
|
||||
}
|
||||
.navbar .badge {
|
||||
font-weight: normal;
|
||||
}
|
||||
#navbar-search-results {
|
||||
left: auto;
|
||||
right: inherit;
|
||||
margin-top: -1px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.navbar-center {
|
||||
float: left;
|
||||
color: #6C7680;
|
||||
}
|
||||
#navbar-breadcrumbs > li > a:before {
|
||||
font-family: FontAwesome;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
text-decoration: inherit;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
*margin-right: 0.3em;
|
||||
display: inline-block;
|
||||
speak: none;
|
||||
font-size: 24px;
|
||||
transition: 0.2s;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
content: "\f105";
|
||||
margin-right: 10px;
|
||||
color: #C0C9D2;
|
||||
}
|
||||
#navbar-breadcrumbs > li > a:hover:before,
|
||||
#navbar-breadcrumbs > li > a:focus:before,
|
||||
#navbar-breadcrumbs > li > a:active:before {
|
||||
color: #36414C;
|
||||
}
|
||||
#navbar-breadcrumbs > li > a {
|
||||
padding: 6px 15px 10px 0px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 170px;
|
||||
}
|
||||
@media (min-width: 991px) and (max-width: 1199px) {
|
||||
#navbar-breadcrumbs > li > a {
|
||||
max-width: 120px;
|
||||
}
|
||||
}
|
||||
.toolbar-user-fullname {
|
||||
max-width: 150px;
|
||||
display: inline-block;
|
||||
}
|
||||
.navbar-brand > img {
|
||||
display: inline-block;
|
||||
}
|
||||
.toggle-sidebar {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.navbar-default .navbar-nav > li > a,
|
||||
.navbar-default .navbar-brand {
|
||||
color: #8D99A6;
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
@media (max-width: 767px) {
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue