web>app
This commit is contained in:
11
.claude/settings.local.json
Normal file
11
.claude/settings.local.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"mcp__serena__activate_project",
|
||||
"mcp__serena__list_dir",
|
||||
"mcp__serena__get_symbols_overview",
|
||||
"mcp__serena__find_symbol",
|
||||
"Bash(python -c \"\nfrom app import generate_key\nr = generate_key\\('jwt_rs256'\\)\nprint\\('RS256 keypair:', r['keypair'], r['algorithm'], r['bits'], 'bit'\\)\nprint\\('private key starts with:', r['key'][:27]\\)\nprint\\('public key starts with:', r['public_key'][:26]\\)\n\ne = generate_key\\('jwt_es256'\\)\nprint\\('ES256 keypair:', e['keypair'], e['algorithm'], e['bits'], 'bit'\\)\nprint\\('private key starts with:', e['key'][:27]\\)\n\nh = generate_key\\('jwt_hs256'\\)\nprint\\('HS256:', h['keypair'], h['algorithm'], h['bits'], 'bit'\\)\n\")"
|
||||
]
|
||||
}
|
||||
}
|
||||
2
.serena/.gitignore
vendored
Normal file
2
.serena/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/cache
|
||||
/project.local.yml
|
||||
135
.serena/project.yml
Normal file
135
.serena/project.yml
Normal file
@@ -0,0 +1,135 @@
|
||||
# the name by which the project can be referenced within Serena
|
||||
project_name: "key-generator"
|
||||
|
||||
|
||||
# list of languages for which language servers are started; choose from:
|
||||
# al bash clojure cpp csharp
|
||||
# csharp_omnisharp dart elixir elm erlang
|
||||
# fortran fsharp go groovy haskell
|
||||
# java julia kotlin lua markdown
|
||||
# matlab nix pascal perl php
|
||||
# php_phpactor powershell python python_jedi r
|
||||
# rego ruby ruby_solargraph rust scala
|
||||
# swift terraform toml typescript typescript_vts
|
||||
# vue yaml zig
|
||||
# (This list may be outdated. For the current list, see values of Language enum here:
|
||||
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
|
||||
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
|
||||
# Note:
|
||||
# - For C, use cpp
|
||||
# - For JavaScript, use typescript
|
||||
# - For Free Pascal/Lazarus, use pascal
|
||||
# Special requirements:
|
||||
# Some languages require additional setup/installations.
|
||||
# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
|
||||
# When using multiple languages, the first language server that supports a given file will be used for that file.
|
||||
# The first language is the default language and the respective language server will be used as a fallback.
|
||||
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
|
||||
languages:
|
||||
- python
|
||||
|
||||
# the encoding used by text files in the project
|
||||
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
|
||||
encoding: "utf-8"
|
||||
|
||||
# line ending convention to use when writing source files.
|
||||
# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default)
|
||||
# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings.
|
||||
line_ending:
|
||||
|
||||
# The language backend to use for this project.
|
||||
# If not set, the global setting from serena_config.yml is used.
|
||||
# Valid values: LSP, JetBrains
|
||||
# Note: the backend is fixed at startup. If a project with a different backend
|
||||
# is activated post-init, an error will be returned.
|
||||
language_backend:
|
||||
|
||||
# whether to use project's .gitignore files to ignore files
|
||||
ignore_all_files_in_gitignore: true
|
||||
|
||||
# list of additional paths to ignore in this project.
|
||||
# Same syntax as gitignore, so you can use * and **.
|
||||
# Note: global ignored_paths from serena_config.yml are also applied additively.
|
||||
ignored_paths: []
|
||||
|
||||
# whether the project is in read-only mode
|
||||
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
|
||||
# Added on 2025-04-18
|
||||
read_only: false
|
||||
|
||||
# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
|
||||
# Below is the complete list of tools for convenience.
|
||||
# To make sure you have the latest list of tools, and to view their descriptions,
|
||||
# execute `uv run scripts/print_tool_overview.py`.
|
||||
#
|
||||
# * `activate_project`: Activates a project by name.
|
||||
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
|
||||
# * `create_text_file`: Creates/overwrites a file in the project directory.
|
||||
# * `delete_lines`: Deletes a range of lines within a file.
|
||||
# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
|
||||
# * `execute_shell_command`: Executes a shell command.
|
||||
# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
|
||||
# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
|
||||
# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
|
||||
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
|
||||
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
|
||||
# * `initial_instructions`: Gets the initial instructions for the current project.
|
||||
# Should only be used in settings where the system prompt cannot be set,
|
||||
# e.g. in clients you have no control over, like Claude Desktop.
|
||||
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
|
||||
# * `insert_at_line`: Inserts content at a given line in a file.
|
||||
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
|
||||
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
|
||||
# * `list_memories`: Lists memories in Serena's project-specific memory store.
|
||||
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
|
||||
# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
|
||||
# * `read_file`: Reads a file within the project directory.
|
||||
# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
|
||||
# * `remove_project`: Removes a project from the Serena configuration.
|
||||
# * `replace_lines`: Replaces a range of lines within a file with new content.
|
||||
# * `replace_symbol_body`: Replaces the full definition of a symbol.
|
||||
# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
|
||||
# * `search_for_pattern`: Performs a search for a pattern in the project.
|
||||
# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
|
||||
# * `switch_modes`: Activates modes by providing a list of their names
|
||||
# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
|
||||
# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
|
||||
# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
|
||||
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
|
||||
excluded_tools: []
|
||||
|
||||
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default)
|
||||
included_optional_tools: []
|
||||
|
||||
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
|
||||
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
|
||||
fixed_tools: []
|
||||
|
||||
# list of mode names to that are always to be included in the set of active modes
|
||||
# The full set of modes to be activated is base_modes + default_modes.
|
||||
# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply.
|
||||
# Otherwise, this setting overrides the global configuration.
|
||||
# Set this to [] to disable base modes for this project.
|
||||
# Set this to a list of mode names to always include the respective modes for this project.
|
||||
base_modes:
|
||||
|
||||
# list of mode names that are to be activated by default.
|
||||
# The full set of modes to be activated is base_modes + default_modes.
|
||||
# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply.
|
||||
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
|
||||
# This setting can, in turn, be overridden by CLI parameters (--mode).
|
||||
default_modes:
|
||||
|
||||
# initial prompt for the project. It will always be given to the LLM upon activating the project
|
||||
# (contrary to the memories, which are loaded on demand).
|
||||
initial_prompt: ""
|
||||
|
||||
# time budget (seconds) per tool call for the retrieval of additional symbol information
|
||||
# such as docstrings or parameter information.
|
||||
# This overrides the corresponding setting in the global configuration; see the documentation there.
|
||||
# If null or missing, use the setting from the global configuration.
|
||||
symbol_info_budget:
|
||||
|
||||
# list of regex patterns which, when matched, mark a memory entry as read‑only.
|
||||
# Extends the list from the global configuration, merging the two lists.
|
||||
read_only_memory_patterns: []
|
||||
3
=41.0.0
Normal file
3
=41.0.0
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
[notice] A new release of pip is available: 25.0.1 -> 26.0.1
|
||||
[notice] To update, run: python.exe -m pip install --upgrade pip
|
||||
30
README.md
30
README.md
@@ -13,19 +13,21 @@ python main.py
|
||||
|
||||
## 지원 키 타입
|
||||
|
||||
| 타입 | 비트 | 포맷 |
|
||||
|------|------|------|
|
||||
| JWT Secret (HS256) | 256-bit | Hex |
|
||||
| JWT Secret (HS384) | 384-bit | Hex |
|
||||
| JWT Secret (HS512) | 512-bit | Hex |
|
||||
| JWT Secret (Base64URL) | 256-bit | Base64URL |
|
||||
| API Key `sk-...` | 256-bit | Base64URL |
|
||||
| Operation Key `ops-...` | 192-bit | Base64URL |
|
||||
| Random Hex 256-bit | 256-bit | Hex |
|
||||
| Random Hex 512-bit | 512-bit | Hex |
|
||||
| Alphanumeric | 256-bit | A-Za-z0-9 |
|
||||
| UUID v4 | 128-bit | UUID |
|
||||
| Custom | 자유 | 직접 선택 |
|
||||
| 타입 | 비트 | 포맷 | 비고 |
|
||||
|------|------|------|------|
|
||||
| JWT Key Pair (RS256) | 2048-bit | PEM | RSA 비대칭 키 쌍 (Private + Public) |
|
||||
| JWT Key Pair (ES256) | 256-bit | PEM | EC P-256 비대칭 키 쌍 (Private + Public) |
|
||||
| JWT Secret (HS256) | 256-bit | Hex | HMAC 대칭 키 |
|
||||
| JWT Secret (HS384) | 384-bit | Hex | HMAC 대칭 키 |
|
||||
| JWT Secret (HS512) | 512-bit | Hex | HMAC 대칭 키 |
|
||||
| JWT Secret (Base64URL) | 256-bit | Base64URL | |
|
||||
| API Key `sk-...` | 256-bit | Base64URL | |
|
||||
| Operation Key `ops-...` | 192-bit | Base64URL | |
|
||||
| Random Hex 256-bit | 256-bit | Hex | |
|
||||
| Random Hex 512-bit | 512-bit | Hex | |
|
||||
| Alphanumeric | 256-bit | A-Za-z0-9 | |
|
||||
| UUID v4 | 128-bit | UUID | |
|
||||
| Custom | 자유 | 직접 선택 | |
|
||||
|
||||
---
|
||||
|
||||
@@ -36,6 +38,7 @@ python main.py
|
||||
- **대량 생성** 체크박스 활성화 시 최대 20개 한번에 생성, **Copy All**로 전체 복사
|
||||
- **Custom** 타입 선택 시 바이트 수(8~512)와 출력 포맷 직접 지정
|
||||
- 모든 키는 Python `secrets` 모듈(암호학적 난수) 사용
|
||||
- **RS256 / ES256** 선택 시 Private Key와 Public Key를 각각 개별 복사 가능 (웹 UI)
|
||||
|
||||
---
|
||||
|
||||
@@ -44,3 +47,4 @@ python main.py
|
||||
- Python 3.8+
|
||||
- customtkinter 5.2+
|
||||
- pyperclip 1.9+
|
||||
- cryptography 41.0+ (RS256 / ES256 키 쌍 생성)
|
||||
|
||||
Binary file not shown.
61
app.py
61
app.py
@@ -5,12 +5,26 @@ import base64
|
||||
import uuid
|
||||
import os
|
||||
import string
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa, ec
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from flask import Flask, render_template, request, jsonify
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
KEY_CONFIGS = {
|
||||
"jwt_rs256": {
|
||||
"label": "JWT Key Pair (RS256)",
|
||||
"description": "RSA 2048-bit 비대칭 키 쌍 (PEM)",
|
||||
"bytes": None,
|
||||
"format": "rsa_keypair",
|
||||
},
|
||||
"jwt_es256": {
|
||||
"label": "JWT Key Pair (ES256)",
|
||||
"description": "EC P-256 비대칭 키 쌍 (PEM)",
|
||||
"bytes": None,
|
||||
"format": "ec_keypair",
|
||||
},
|
||||
"jwt_hs256": {
|
||||
"label": "JWT Secret (HS256)",
|
||||
"description": "HMAC-SHA256용 JWT 시크릿 키",
|
||||
@@ -85,9 +99,53 @@ def generate_key(key_type: str, custom_bytes: int = 32, custom_format: str = "he
|
||||
if not config:
|
||||
raise ValueError(f"Unknown key type: {key_type}")
|
||||
|
||||
byte_length = config["bytes"] if config["bytes"] is not None else custom_bytes
|
||||
fmt = custom_format if key_type == "custom" else config["format"]
|
||||
|
||||
if fmt == "rsa_keypair":
|
||||
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
priv_pem = private_key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.PKCS8,
|
||||
encryption_algorithm=serialization.NoEncryption(),
|
||||
).decode()
|
||||
pub_pem = private_key.public_key().public_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
||||
).decode()
|
||||
return {
|
||||
"key": priv_pem,
|
||||
"public_key": pub_pem,
|
||||
"keypair": True,
|
||||
"type": key_type,
|
||||
"label": config["label"],
|
||||
"bits": 2048,
|
||||
"length": len(priv_pem),
|
||||
"algorithm": "RS256",
|
||||
}
|
||||
|
||||
if fmt == "ec_keypair":
|
||||
private_key = ec.generate_private_key(ec.SECP256R1())
|
||||
priv_pem = private_key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.PKCS8,
|
||||
encryption_algorithm=serialization.NoEncryption(),
|
||||
).decode()
|
||||
pub_pem = private_key.public_key().public_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
||||
).decode()
|
||||
return {
|
||||
"key": priv_pem,
|
||||
"public_key": pub_pem,
|
||||
"keypair": True,
|
||||
"type": key_type,
|
||||
"label": config["label"],
|
||||
"bits": 256,
|
||||
"length": len(priv_pem),
|
||||
"algorithm": "ES256",
|
||||
}
|
||||
|
||||
byte_length = config["bytes"] if config["bytes"] is not None else custom_bytes
|
||||
raw = secrets.token_bytes(byte_length)
|
||||
|
||||
if fmt == "hex":
|
||||
@@ -110,6 +168,7 @@ def generate_key(key_type: str, custom_bytes: int = 32, custom_format: str = "he
|
||||
|
||||
return {
|
||||
"key": key,
|
||||
"keypair": False,
|
||||
"type": key_type,
|
||||
"label": config["label"],
|
||||
"bits": byte_length * 8,
|
||||
|
||||
33
main.py
33
main.py
@@ -3,6 +3,8 @@ import base64
|
||||
import uuid
|
||||
import string
|
||||
import pyperclip
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa, ec
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
import customtkinter as ctk
|
||||
from tkinter import messagebox
|
||||
|
||||
@@ -10,6 +12,8 @@ ctk.set_appearance_mode("dark")
|
||||
ctk.set_default_color_theme("blue")
|
||||
|
||||
KEY_TYPES = [
|
||||
("JWT Key Pair (RS256)", "jwt_rs256", "RSA 2048-bit · PEM", 256, "rsa_keypair"),
|
||||
("JWT Key Pair (ES256)", "jwt_es256", "EC P-256 · PEM", 32, "ec_keypair"),
|
||||
("JWT Secret (HS256)", "jwt_hs256", "32 bytes · Hex", 32, "hex"),
|
||||
("JWT Secret (HS384)", "jwt_hs384", "48 bytes · Hex", 48, "hex"),
|
||||
("JWT Secret (HS512)", "jwt_hs512", "64 bytes · Hex", 64, "hex"),
|
||||
@@ -34,6 +38,32 @@ FORMAT_MAP = {
|
||||
|
||||
|
||||
def generate_key(byte_length: int, fmt: str) -> str:
|
||||
if fmt == "rsa_keypair":
|
||||
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
||||
priv_pem = private_key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.PKCS8,
|
||||
encryption_algorithm=serialization.NoEncryption(),
|
||||
).decode()
|
||||
pub_pem = private_key.public_key().public_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
||||
).decode()
|
||||
return priv_pem + "\n" + pub_pem
|
||||
|
||||
if fmt == "ec_keypair":
|
||||
private_key = ec.generate_private_key(ec.SECP256R1())
|
||||
priv_pem = private_key.private_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PrivateFormat.PKCS8,
|
||||
encryption_algorithm=serialization.NoEncryption(),
|
||||
).decode()
|
||||
pub_pem = private_key.public_key().public_bytes(
|
||||
encoding=serialization.Encoding.PEM,
|
||||
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
||||
).decode()
|
||||
return priv_pem + "\n" + pub_pem
|
||||
|
||||
raw = secrets.token_bytes(byte_length)
|
||||
if fmt == "hex":
|
||||
return raw.hex()
|
||||
@@ -301,8 +331,9 @@ class App(ctk.CTk):
|
||||
self._key_box.insert("1.0", key)
|
||||
self._key_box.configure(state="disabled")
|
||||
|
||||
fmt_display = {"rsa_keypair": "RS256", "ec_keypair": "ES256"}.get(fmt, fmt.upper())
|
||||
self._meta_label.configure(
|
||||
text=f"{entry[0]} · {bits}-bit · {fmt.upper()} · {len(key)} chars"
|
||||
text=f"{entry[0]} · {bits}-bit · {fmt_display} · {len(key)} chars"
|
||||
)
|
||||
|
||||
# Reset copy button
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
customtkinter>=5.2.0
|
||||
pyperclip>=1.9.0
|
||||
cryptography>=41.0.0
|
||||
|
||||
@@ -375,6 +375,18 @@
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* Keypair display */
|
||||
.keypair-section { margin-bottom: 16px; }
|
||||
.keypair-section:last-child { margin-bottom: 0; }
|
||||
.keypair-section-label {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
/* Icon SVG */
|
||||
svg { display: inline-block; vertical-align: middle; }
|
||||
</style>
|
||||
@@ -389,7 +401,7 @@
|
||||
</svg>
|
||||
Key Generator
|
||||
</h1>
|
||||
<p>JWT Secret · API Key · Operation Key · UUID · Random Hex</p>
|
||||
<p>JWT Secret · RS256 · ES256 · API Key · Operation Key · UUID · Random Hex</p>
|
||||
</header>
|
||||
|
||||
<div class="card">
|
||||
@@ -463,6 +475,35 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Keypair result -->
|
||||
<div id="resultKeypair" style="display:none">
|
||||
<div class="keypair-section">
|
||||
<div class="keypair-section-label">Private Key</div>
|
||||
<div class="key-display" id="privateKeyDisplay" style="white-space:pre;font-size:0.75rem;"></div>
|
||||
<div class="copy-row">
|
||||
<button class="copy-btn" id="copyPrivateBtn" onclick="copyPrivateKey()">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
|
||||
</svg>
|
||||
Copy Private Key
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="keypair-section">
|
||||
<div class="keypair-section-label">Public Key</div>
|
||||
<div class="key-display" id="publicKeyDisplay" style="white-space:pre;font-size:0.75rem;"></div>
|
||||
<div class="copy-row">
|
||||
<button class="copy-btn" id="copyPublicBtn" onclick="copyPublicKey()">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
|
||||
</svg>
|
||||
Copy Public Key
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="key-meta" id="keyMetaKeypair"></div>
|
||||
</div>
|
||||
|
||||
<!-- Bulk result -->
|
||||
<div id="resultBulk" style="display:none">
|
||||
<div class="bulk-list" id="bulkList"></div>
|
||||
@@ -483,6 +524,8 @@
|
||||
let selectedType = 'jwt_hs256';
|
||||
let bulkMode = false;
|
||||
let lastBulkKeys = [];
|
||||
let lastBulkData = [];
|
||||
let lastKeypair = null;
|
||||
|
||||
function selectType(type, el) {
|
||||
selectedType = type;
|
||||
@@ -541,11 +584,36 @@
|
||||
}
|
||||
}
|
||||
|
||||
const COPY_ICON = `<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>`;
|
||||
const CHECK_ICON = `<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`;
|
||||
|
||||
function renderSingle(data) {
|
||||
document.getElementById('resultArea').style.display = 'block';
|
||||
document.getElementById('resultSingle').style.display = 'block';
|
||||
document.getElementById('resultBulk').style.display = 'none';
|
||||
|
||||
if (data.keypair) {
|
||||
lastKeypair = data;
|
||||
document.getElementById('resultSingle').style.display = 'none';
|
||||
document.getElementById('resultKeypair').style.display = 'block';
|
||||
|
||||
document.getElementById('privateKeyDisplay').textContent = data.key;
|
||||
document.getElementById('publicKeyDisplay').textContent = data.public_key;
|
||||
|
||||
const meta = document.getElementById('keyMetaKeypair');
|
||||
meta.innerHTML = `
|
||||
<span class="meta-tag highlight">${data.label}</span>
|
||||
<span class="meta-tag">${data.bits} bit</span>
|
||||
<span class="meta-tag">${data.algorithm}</span>
|
||||
`;
|
||||
document.getElementById('copyPrivateBtn').classList.remove('copied');
|
||||
document.getElementById('copyPublicBtn').classList.remove('copied');
|
||||
document.getElementById('copyPrivateBtn').innerHTML = `${COPY_ICON} Copy Private Key`;
|
||||
document.getElementById('copyPublicBtn').innerHTML = `${COPY_ICON} Copy Public Key`;
|
||||
} else {
|
||||
lastKeypair = null;
|
||||
document.getElementById('resultSingle').style.display = 'block';
|
||||
document.getElementById('resultKeypair').style.display = 'none';
|
||||
|
||||
document.getElementById('keyDisplay').textContent = data.key;
|
||||
|
||||
const meta = document.getElementById('keyMeta');
|
||||
@@ -558,52 +626,69 @@
|
||||
|
||||
const copyBtn = document.getElementById('copyBtn');
|
||||
copyBtn.classList.remove('copied');
|
||||
copyBtn.innerHTML = `
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
|
||||
</svg> Copy`;
|
||||
copyBtn.innerHTML = `${COPY_ICON} Copy`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderBulk(items) {
|
||||
lastBulkData = items;
|
||||
lastBulkKeys = items.map(i => i.key);
|
||||
document.getElementById('resultArea').style.display = 'block';
|
||||
document.getElementById('resultSingle').style.display = 'none';
|
||||
document.getElementById('resultKeypair').style.display = 'none';
|
||||
document.getElementById('resultBulk').style.display = 'block';
|
||||
|
||||
const list = document.getElementById('bulkList');
|
||||
list.innerHTML = items.map((item, idx) => `
|
||||
list.innerHTML = items.map((item, idx) => {
|
||||
const displayKey = item.keypair
|
||||
? item.key.split('\n').slice(0, 2).join('\n') + '\n...'
|
||||
: item.key;
|
||||
return `
|
||||
<div class="bulk-item">
|
||||
<span class="bulk-index">#${idx + 1}</span>
|
||||
<span class="bulk-key">${item.key}</span>
|
||||
<button class="bulk-copy" onclick="copyBulkItem(this, '${escHtml(item.key)}')">Copy</button>
|
||||
</div>
|
||||
`).join('');
|
||||
<span class="bulk-key" style="${item.keypair ? 'white-space:pre;font-size:0.72rem;' : ''}">${escHtmlDisplay(displayKey)}</span>
|
||||
<button class="bulk-copy" data-index="${idx}" onclick="copyBulkItem(this)">Copy</button>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function escHtml(s) {
|
||||
return s.replace(/'/g, "\\'");
|
||||
function escHtmlDisplay(s) {
|
||||
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
function flashCopied(btn, resetLabel) {
|
||||
btn.classList.add('copied');
|
||||
btn.innerHTML = `${CHECK_ICON} Copied!`;
|
||||
setTimeout(() => {
|
||||
btn.classList.remove('copied');
|
||||
btn.innerHTML = `${COPY_ICON} ${resetLabel}`;
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function copyKey() {
|
||||
const key = document.getElementById('keyDisplay').textContent;
|
||||
navigator.clipboard.writeText(key).then(() => {
|
||||
const btn = document.getElementById('copyBtn');
|
||||
btn.classList.add('copied');
|
||||
btn.innerHTML = `
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="20 6 9 17 4 12"/>
|
||||
</svg> Copied!`;
|
||||
setTimeout(() => {
|
||||
btn.classList.remove('copied');
|
||||
btn.innerHTML = `
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
|
||||
</svg> Copy`;
|
||||
}, 2000);
|
||||
flashCopied(document.getElementById('copyBtn'), 'Copy');
|
||||
});
|
||||
}
|
||||
|
||||
function copyBulkItem(btn, key) {
|
||||
function copyPrivateKey() {
|
||||
if (!lastKeypair) return;
|
||||
navigator.clipboard.writeText(lastKeypair.key).then(() => {
|
||||
flashCopied(document.getElementById('copyPrivateBtn'), 'Copy Private Key');
|
||||
});
|
||||
}
|
||||
|
||||
function copyPublicKey() {
|
||||
if (!lastKeypair) return;
|
||||
navigator.clipboard.writeText(lastKeypair.public_key).then(() => {
|
||||
flashCopied(document.getElementById('copyPublicBtn'), 'Copy Public Key');
|
||||
});
|
||||
}
|
||||
|
||||
function copyBulkItem(btn) {
|
||||
const idx = parseInt(btn.dataset.index);
|
||||
const key = lastBulkData[idx].key;
|
||||
navigator.clipboard.writeText(key).then(() => {
|
||||
btn.classList.add('copied');
|
||||
btn.textContent = 'Copied!';
|
||||
|
||||
Reference in New Issue
Block a user