mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-04 05:10:19 +08:00
feat: support copying token to clipboard for casdoor-app (#3345)
* feat: support copy token to clipboard for casdoor-app auth * feat: abstract casdoor-app related code
This commit is contained in:
121
web/src/common/CasdoorAppConnector.js
Normal file
121
web/src/common/CasdoorAppConnector.js
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {Alert, Button, QRCode} from "antd";
|
||||||
|
import * as Setting from "../Setting";
|
||||||
|
import i18next from "i18next";
|
||||||
|
|
||||||
|
export const generateCasdoorAppUrl = (accessToken, forQrCode = true) => {
|
||||||
|
let qrUrl = "";
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
if (!accessToken) {
|
||||||
|
error = i18next.t("general:Access token is empty");
|
||||||
|
return {qrUrl, error};
|
||||||
|
}
|
||||||
|
|
||||||
|
qrUrl = `casdoor-app://login?serverUrl=${window.location.origin}&accessToken=${accessToken}`;
|
||||||
|
|
||||||
|
if (forQrCode && qrUrl.length >= 2000) {
|
||||||
|
qrUrl = "";
|
||||||
|
error = i18next.t("general:QR code is too large");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {qrUrl, error};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CasdoorAppQrCode = ({accessToken, icon}) => {
|
||||||
|
const {qrUrl, error} = generateCasdoorAppUrl(accessToken, true);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <Alert message={error} type="error" showIcon />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<QRCode
|
||||||
|
value={qrUrl}
|
||||||
|
icon={icon}
|
||||||
|
errorLevel="M"
|
||||||
|
size={230}
|
||||||
|
bordered={false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CasdoorAppUrl = ({accessToken}) => {
|
||||||
|
const {qrUrl, error} = generateCasdoorAppUrl(accessToken, false);
|
||||||
|
|
||||||
|
const handleCopyUrl = async() => {
|
||||||
|
if (!window.isSecureContext) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(qrUrl);
|
||||||
|
Setting.showMessage("success", i18next.t("general:Copied to clipboard"));
|
||||||
|
} catch (err) {
|
||||||
|
Setting.showMessage("error", i18next.t("general:Failed to copy"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <Alert message={error} type="error" showIcon />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: "10px",
|
||||||
|
}}>
|
||||||
|
<span>{i18next.t("general:URL String")}</span>
|
||||||
|
{window.isSecureContext && (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={handleCopyUrl}
|
||||||
|
style={{marginLeft: "10px"}}
|
||||||
|
>
|
||||||
|
{i18next.t("general:Copy URL")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: "10px",
|
||||||
|
maxWidth: "400px",
|
||||||
|
maxHeight: "100px",
|
||||||
|
overflow: "auto",
|
||||||
|
wordBreak: "break-all",
|
||||||
|
whiteSpace: "pre-wrap",
|
||||||
|
cursor: "pointer",
|
||||||
|
userSelect: "all",
|
||||||
|
backgroundColor: "#f5f5f5",
|
||||||
|
borderRadius: "4px",
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
const selection = window.getSelection();
|
||||||
|
const range = document.createRange();
|
||||||
|
range.selectNodeContents(e.target);
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(range);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{qrUrl}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -14,9 +14,10 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
|
import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
|
||||||
import {Alert, Button, Col, Image, Input, Popover, QRCode, Row, Table, Tooltip} from "antd";
|
import {Button, Col, Image, Input, Popover, Row, Table, Tooltip} from "antd";
|
||||||
import * as Setting from "../Setting";
|
import * as Setting from "../Setting";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
|
import {CasdoorAppQrCode, CasdoorAppUrl} from "../common/CasdoorAppConnector";
|
||||||
|
|
||||||
class MfaAccountTable extends React.Component {
|
class MfaAccountTable extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -76,42 +77,6 @@ class MfaAccountTable extends React.Component {
|
|||||||
this.updateTable(table);
|
this.updateTable(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
getQrUrl() {
|
|
||||||
const {accessToken} = this.props;
|
|
||||||
let qrUrl = `casdoor-app://login?serverUrl=${window.location.origin}&accessToken=${accessToken}`;
|
|
||||||
let error = null;
|
|
||||||
|
|
||||||
if (!accessToken) {
|
|
||||||
qrUrl = "";
|
|
||||||
error = i18next.t("general:Access token is empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (qrUrl.length >= 2000) {
|
|
||||||
qrUrl = "";
|
|
||||||
error = i18next.t("general:QR code is too large");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {qrUrl, error};
|
|
||||||
}
|
|
||||||
|
|
||||||
renderQrCode() {
|
|
||||||
const {qrUrl, error} = this.getQrUrl();
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return <Alert message={error} type="error" showIcon />;
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<QRCode
|
|
||||||
value={qrUrl}
|
|
||||||
icon={this.state.icon}
|
|
||||||
errorLevel="M"
|
|
||||||
size={230}
|
|
||||||
bordered={false}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderTable(table) {
|
renderTable(table) {
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@ -194,10 +159,25 @@ class MfaAccountTable extends React.Component {
|
|||||||
title={() => (
|
title={() => (
|
||||||
<div>
|
<div>
|
||||||
{this.props.title}
|
{this.props.title}
|
||||||
<Button style={{marginRight: "10px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
|
<Button style={{marginRight: "10px"}} type="primary" size="small" onClick={() => this.addRow(table)}>
|
||||||
<Popover trigger="focus" overlayInnerStyle={{padding: 0}}
|
{i18next.t("general:Add")}
|
||||||
content={this.renderQrCode()}>
|
</Button>
|
||||||
<Button style={{marginLeft: "5px"}} type="primary" size="small">{i18next.t("general:QR Code")}</Button>
|
<Popover
|
||||||
|
trigger="focus"
|
||||||
|
overlayInnerStyle={{padding: 0}}
|
||||||
|
content={<CasdoorAppQrCode accessToken={this.props.accessToken} icon={this.state.icon} />}
|
||||||
|
>
|
||||||
|
<Button style={{marginRight: "10px"}} type="primary" size="small">
|
||||||
|
{i18next.t("general:QR Code")}
|
||||||
|
</Button>
|
||||||
|
</Popover>
|
||||||
|
<Popover
|
||||||
|
trigger="click"
|
||||||
|
content={<CasdoorAppUrl accessToken={this.props.accessToken} />}
|
||||||
|
>
|
||||||
|
<Button type="primary" size="small">
|
||||||
|
{i18next.t("general:Show URL")}
|
||||||
|
</Button>
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
Reference in New Issue
Block a user