@beclab/olaresid 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/business/index.d.ts +3 -3
- package/dist/business/index.d.ts.map +1 -1
- package/dist/business/index.js +31 -37
- package/dist/business/index.js.map +1 -1
- package/dist/cli.js +3 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -6
- package/dist/index.js.map +1 -1
- package/dist/utils/crypto-utils.d.ts +4 -32
- package/dist/utils/crypto-utils.d.ts.map +1 -1
- package/dist/utils/crypto-utils.js +32 -95
- package/dist/utils/crypto-utils.js.map +1 -1
- package/examples/crypto-utilities.ts +3 -3
- package/examples/ed25519-jwk.ts +1 -1
- package/examples/frontend-demo/index.html +13 -0
- package/examples/frontend-demo/package-lock.json +5370 -0
- package/examples/frontend-demo/package.json +33 -0
- package/examples/frontend-demo/src/App.vue +1211 -0
- package/examples/frontend-demo/src/main.ts +8 -0
- package/examples/frontend-demo/src/style.css +341 -0
- package/examples/frontend-demo/tsconfig.json +24 -0
- package/examples/frontend-demo/webpack.config.js +87 -0
- package/examples/generate-mnemonic.ts +3 -3
- package/examples/register-subdomain.ts +1 -1
- package/examples/transfer-domain.ts +1 -1
- package/package.json +2 -1
- package/src/business/index.ts +23 -34
- package/src/cli.ts +3 -4
- package/src/index.ts +1 -6
- package/src/utils/crypto-utils.ts +34 -112
- package/examples/encoding-utils.ts +0 -96
- package/examples/quasar-demo/.eslintrc.js +0 -23
- package/examples/quasar-demo/.quasar/app.js +0 -43
- package/examples/quasar-demo/.quasar/client-entry.js +0 -38
- package/examples/quasar-demo/.quasar/client-prefetch.js +0 -130
- package/examples/quasar-demo/.quasar/quasar-user-options.js +0 -16
- package/examples/quasar-demo/README.md +0 -49
- package/examples/quasar-demo/index.html +0 -11
- package/examples/quasar-demo/package-lock.json +0 -6407
- package/examples/quasar-demo/package.json +0 -36
- package/examples/quasar-demo/quasar.config.js +0 -73
- package/examples/quasar-demo/src/App.vue +0 -13
- package/examples/quasar-demo/src/css/app.scss +0 -1
- package/examples/quasar-demo/src/layouts/MainLayout.vue +0 -21
- package/examples/quasar-demo/src/pages/IndexPage.vue +0 -905
- package/examples/quasar-demo/src/router/index.ts +0 -25
- package/examples/quasar-demo/src/router/routes.ts +0 -11
- package/examples/quasar-demo/tsconfig.json +0 -28
|
@@ -0,0 +1,1211 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="app">
|
|
3
|
+
<header>
|
|
4
|
+
<h1>🔑 OlaresID SDK Demo</h1>
|
|
5
|
+
<p class="subtitle">Complete SDK functionality demonstration</p>
|
|
6
|
+
</header>
|
|
7
|
+
|
|
8
|
+
<main>
|
|
9
|
+
<!-- MetaMask Connection -->
|
|
10
|
+
<section class="card">
|
|
11
|
+
<h2>🦊 MetaMask Connection</h2>
|
|
12
|
+
|
|
13
|
+
<div
|
|
14
|
+
v-if="currentNetwork"
|
|
15
|
+
class="network-badge"
|
|
16
|
+
:class="networkClass"
|
|
17
|
+
>
|
|
18
|
+
{{ currentNetwork }} ({{ currentChainId }})
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div
|
|
22
|
+
v-if="currentNetwork.startsWith('Unsupported')"
|
|
23
|
+
class="warning"
|
|
24
|
+
>
|
|
25
|
+
⚠️ Please switch to Optimism Mainnet or OP Sepolia Testnet
|
|
26
|
+
<div class="button-grid-small" style="margin-top: 0.75rem">
|
|
27
|
+
<button @click="switchToMainnet" class="btn-link">
|
|
28
|
+
Switch to OP Mainnet
|
|
29
|
+
</button>
|
|
30
|
+
<button @click="switchToTestnet" class="btn-link">
|
|
31
|
+
Switch to OP Testnet
|
|
32
|
+
</button>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div v-if="!walletConnected">
|
|
37
|
+
<button
|
|
38
|
+
@click="connectMetaMask"
|
|
39
|
+
:disabled="connecting"
|
|
40
|
+
class="btn-primary"
|
|
41
|
+
>
|
|
42
|
+
{{
|
|
43
|
+
connecting ? 'Connecting...' : '🦊 Connect MetaMask'
|
|
44
|
+
}}
|
|
45
|
+
</button>
|
|
46
|
+
</div>
|
|
47
|
+
<div v-else class="connected-info">
|
|
48
|
+
<div class="success-badge">
|
|
49
|
+
✅ Connected: {{ walletAddress }}
|
|
50
|
+
</div>
|
|
51
|
+
<button @click="disconnectWallet" class="btn-secondary">
|
|
52
|
+
Disconnect
|
|
53
|
+
</button>
|
|
54
|
+
</div>
|
|
55
|
+
</section>
|
|
56
|
+
|
|
57
|
+
<!-- Domain Input - Top Level -->
|
|
58
|
+
<section class="card domain-input-section">
|
|
59
|
+
<h2>🏷️ Domain Configuration</h2>
|
|
60
|
+
<p class="section-description">
|
|
61
|
+
Enter your domain name to manage its settings below
|
|
62
|
+
</p>
|
|
63
|
+
<div class="form-group">
|
|
64
|
+
<label class="input-label">Domain Name</label>
|
|
65
|
+
<input
|
|
66
|
+
v-model="domainName"
|
|
67
|
+
placeholder="e.g., alice.olares.com or 1.com"
|
|
68
|
+
:disabled="!walletConnected"
|
|
69
|
+
class="input input-large"
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
72
|
+
<div v-if="!domainName && walletConnected" class="info-message">
|
|
73
|
+
👆 Please enter a domain name to access management features
|
|
74
|
+
</div>
|
|
75
|
+
</section>
|
|
76
|
+
|
|
77
|
+
<!-- Domain Management Area - Only show when domain is entered -->
|
|
78
|
+
<div v-if="domainName" class="management-area">
|
|
79
|
+
<!-- Domain Info Demo -->
|
|
80
|
+
<section class="card subsection">
|
|
81
|
+
<h3>📋 Domain Info</h3>
|
|
82
|
+
<div class="button-grid">
|
|
83
|
+
<button
|
|
84
|
+
@click="getMetaInfo"
|
|
85
|
+
:disabled="
|
|
86
|
+
!domainName ||
|
|
87
|
+
!walletConnected ||
|
|
88
|
+
loading.metaInfo
|
|
89
|
+
"
|
|
90
|
+
class="btn-primary"
|
|
91
|
+
>
|
|
92
|
+
{{
|
|
93
|
+
loading.metaInfo
|
|
94
|
+
? 'Loading...'
|
|
95
|
+
: 'Get Meta Info'
|
|
96
|
+
}}
|
|
97
|
+
</button>
|
|
98
|
+
<button
|
|
99
|
+
@click="getOwner"
|
|
100
|
+
:disabled="
|
|
101
|
+
!domainName || !walletConnected || loading.owner
|
|
102
|
+
"
|
|
103
|
+
class="btn-primary"
|
|
104
|
+
>
|
|
105
|
+
{{ loading.owner ? 'Loading...' : 'Get Owner' }}
|
|
106
|
+
</button>
|
|
107
|
+
<button
|
|
108
|
+
@click="checkIsOwner"
|
|
109
|
+
:disabled="
|
|
110
|
+
!domainName ||
|
|
111
|
+
!walletConnected ||
|
|
112
|
+
loading.isOwner
|
|
113
|
+
"
|
|
114
|
+
class="btn-secondary"
|
|
115
|
+
>
|
|
116
|
+
{{ loading.isOwner ? 'Loading...' : 'Is Owner?' }}
|
|
117
|
+
</button>
|
|
118
|
+
</div>
|
|
119
|
+
<div v-if="results.domainContext" class="result">
|
|
120
|
+
<pre>{{ results.domainContext }}</pre>
|
|
121
|
+
</div>
|
|
122
|
+
</section>
|
|
123
|
+
|
|
124
|
+
<!-- IP Address Management -->
|
|
125
|
+
<section class="card subsection">
|
|
126
|
+
<h3>🌐 IP Address Management</h3>
|
|
127
|
+
<div class="form-group">
|
|
128
|
+
<input
|
|
129
|
+
v-model="ipAddress"
|
|
130
|
+
placeholder="e.g., 192.168.1.100"
|
|
131
|
+
:disabled="!walletConnected"
|
|
132
|
+
class="input"
|
|
133
|
+
/>
|
|
134
|
+
</div>
|
|
135
|
+
<div class="button-grid">
|
|
136
|
+
<button
|
|
137
|
+
@click="setIP"
|
|
138
|
+
:disabled="
|
|
139
|
+
!domainName ||
|
|
140
|
+
!ipAddress ||
|
|
141
|
+
!walletConnected ||
|
|
142
|
+
loading.setIP
|
|
143
|
+
"
|
|
144
|
+
class="btn-success"
|
|
145
|
+
>
|
|
146
|
+
{{ loading.setIP ? 'Setting...' : 'Set IP' }}
|
|
147
|
+
</button>
|
|
148
|
+
<button
|
|
149
|
+
@click="getIP"
|
|
150
|
+
:disabled="
|
|
151
|
+
!domainName || !walletConnected || loading.getIP
|
|
152
|
+
"
|
|
153
|
+
class="btn-primary"
|
|
154
|
+
>
|
|
155
|
+
{{ loading.getIP ? 'Loading...' : 'Get IP' }}
|
|
156
|
+
</button>
|
|
157
|
+
<button
|
|
158
|
+
@click="removeIP"
|
|
159
|
+
:disabled="
|
|
160
|
+
!domainName ||
|
|
161
|
+
!walletConnected ||
|
|
162
|
+
loading.removeIP
|
|
163
|
+
"
|
|
164
|
+
class="btn-danger"
|
|
165
|
+
>
|
|
166
|
+
{{ loading.removeIP ? 'Removing...' : 'Remove IP' }}
|
|
167
|
+
</button>
|
|
168
|
+
</div>
|
|
169
|
+
<div v-if="results.ip" class="result success">
|
|
170
|
+
<pre>{{ results.ip }}</pre>
|
|
171
|
+
</div>
|
|
172
|
+
</section>
|
|
173
|
+
|
|
174
|
+
<!-- RSA Key Management -->
|
|
175
|
+
<section class="card subsection">
|
|
176
|
+
<h3>🔐 RSA Key Management</h3>
|
|
177
|
+
<div class="button-grid">
|
|
178
|
+
<button
|
|
179
|
+
@click="generateRSAKeyPair"
|
|
180
|
+
:disabled="loading.generateRSA"
|
|
181
|
+
class="btn-purple"
|
|
182
|
+
>
|
|
183
|
+
{{
|
|
184
|
+
loading.generateRSA
|
|
185
|
+
? 'Generating...'
|
|
186
|
+
: '🔑 Generate RSA Key Pair'
|
|
187
|
+
}}
|
|
188
|
+
</button>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
<div v-if="rsaKeyPair" class="rsa-keys">
|
|
192
|
+
<div class="key-display">
|
|
193
|
+
<strong>Public Key (PEM):</strong>
|
|
194
|
+
<pre
|
|
195
|
+
>{{
|
|
196
|
+
rsaKeyPair.publicKey.substring(0, 100)
|
|
197
|
+
}}...</pre
|
|
198
|
+
>
|
|
199
|
+
</div>
|
|
200
|
+
<div class="key-display">
|
|
201
|
+
<strong>Private Key (PEM):</strong>
|
|
202
|
+
<pre
|
|
203
|
+
>{{
|
|
204
|
+
rsaKeyPair.privateKey.substring(0, 100)
|
|
205
|
+
}}...</pre
|
|
206
|
+
>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
<div class="button-grid">
|
|
211
|
+
<button
|
|
212
|
+
@click="setRSAPublicKey"
|
|
213
|
+
:disabled="
|
|
214
|
+
!domainName ||
|
|
215
|
+
!rsaKeyPair ||
|
|
216
|
+
!walletConnected ||
|
|
217
|
+
loading.setRSA
|
|
218
|
+
"
|
|
219
|
+
class="btn-success"
|
|
220
|
+
>
|
|
221
|
+
{{
|
|
222
|
+
loading.setRSA
|
|
223
|
+
? 'Setting...'
|
|
224
|
+
: 'Set RSA Public Key'
|
|
225
|
+
}}
|
|
226
|
+
</button>
|
|
227
|
+
<button
|
|
228
|
+
@click="getRSAPublicKey"
|
|
229
|
+
:disabled="
|
|
230
|
+
!domainName ||
|
|
231
|
+
!walletConnected ||
|
|
232
|
+
loading.getRSA
|
|
233
|
+
"
|
|
234
|
+
class="btn-primary"
|
|
235
|
+
>
|
|
236
|
+
{{
|
|
237
|
+
loading.getRSA
|
|
238
|
+
? 'Loading...'
|
|
239
|
+
: 'Get RSA Public Key'
|
|
240
|
+
}}
|
|
241
|
+
</button>
|
|
242
|
+
<button
|
|
243
|
+
@click="removeRSAPublicKey"
|
|
244
|
+
:disabled="
|
|
245
|
+
!domainName ||
|
|
246
|
+
!walletConnected ||
|
|
247
|
+
loading.removeRSA
|
|
248
|
+
"
|
|
249
|
+
class="btn-danger"
|
|
250
|
+
>
|
|
251
|
+
{{
|
|
252
|
+
loading.removeRSA
|
|
253
|
+
? 'Removing...'
|
|
254
|
+
: 'Remove RSA Key'
|
|
255
|
+
}}
|
|
256
|
+
</button>
|
|
257
|
+
</div>
|
|
258
|
+
|
|
259
|
+
<div v-if="results.rsa" class="result purple">
|
|
260
|
+
<pre>{{ results.rsa }}</pre>
|
|
261
|
+
</div>
|
|
262
|
+
</section>
|
|
263
|
+
</div>
|
|
264
|
+
|
|
265
|
+
<!-- Toast Messages -->
|
|
266
|
+
<div v-if="toast.show" class="toast" :class="toast.type">
|
|
267
|
+
{{ toast.message }}
|
|
268
|
+
</div>
|
|
269
|
+
</main>
|
|
270
|
+
</div>
|
|
271
|
+
</template>
|
|
272
|
+
|
|
273
|
+
<script setup lang="ts">
|
|
274
|
+
import { ref, onMounted, reactive } from 'vue';
|
|
275
|
+
import * as OlaresIDModule from '@beclab/olaresid';
|
|
276
|
+
import { ethers } from 'ethers';
|
|
277
|
+
|
|
278
|
+
const OlaresID = (OlaresIDModule as any).default || OlaresIDModule;
|
|
279
|
+
const { createRsaKeyPair } = OlaresIDModule as any;
|
|
280
|
+
|
|
281
|
+
declare global {
|
|
282
|
+
interface Window {
|
|
283
|
+
ethereum?: any;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// State
|
|
288
|
+
const domainName = ref('1.com');
|
|
289
|
+
const ipAddress = ref('192.168.1.100');
|
|
290
|
+
const rsaKeyPair = ref<any>(null);
|
|
291
|
+
|
|
292
|
+
// Loading states
|
|
293
|
+
const loading = reactive({
|
|
294
|
+
metaInfo: false,
|
|
295
|
+
owner: false,
|
|
296
|
+
isOwner: false,
|
|
297
|
+
setIP: false,
|
|
298
|
+
getIP: false,
|
|
299
|
+
removeIP: false,
|
|
300
|
+
generateRSA: false,
|
|
301
|
+
setRSA: false,
|
|
302
|
+
getRSA: false,
|
|
303
|
+
removeRSA: false
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// Results
|
|
307
|
+
const results = reactive({
|
|
308
|
+
domainContext: '',
|
|
309
|
+
ip: '',
|
|
310
|
+
rsa: ''
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// MetaMask state
|
|
314
|
+
const walletConnected = ref(false);
|
|
315
|
+
const walletAddress = ref('');
|
|
316
|
+
const connecting = ref(false);
|
|
317
|
+
const currentNetwork = ref('');
|
|
318
|
+
const currentChainId = ref('');
|
|
319
|
+
const networkClass = ref('');
|
|
320
|
+
|
|
321
|
+
// Toast state
|
|
322
|
+
const toast = reactive({
|
|
323
|
+
show: false,
|
|
324
|
+
message: '',
|
|
325
|
+
type: 'info'
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Initialize OlaresID
|
|
329
|
+
let olaresId = OlaresID.createTestnet();
|
|
330
|
+
|
|
331
|
+
onMounted(async () => {
|
|
332
|
+
await checkMetaMaskConnection();
|
|
333
|
+
|
|
334
|
+
if (typeof window.ethereum !== 'undefined') {
|
|
335
|
+
window.ethereum.on('chainChanged', handleChainChanged);
|
|
336
|
+
window.ethereum.on('accountsChanged', handleAccountsChanged);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
const showToast = (
|
|
341
|
+
message: string,
|
|
342
|
+
type: 'success' | 'error' | 'info' = 'info'
|
|
343
|
+
) => {
|
|
344
|
+
toast.message = message;
|
|
345
|
+
toast.type = type;
|
|
346
|
+
toast.show = true;
|
|
347
|
+
setTimeout(() => {
|
|
348
|
+
toast.show = false;
|
|
349
|
+
}, 3000);
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
const handleChainChanged = async (chainId: string) => {
|
|
353
|
+
console.log('Chain changed to:', chainId);
|
|
354
|
+
currentChainId.value = chainId;
|
|
355
|
+
currentNetwork.value = getNetworkName(chainId);
|
|
356
|
+
|
|
357
|
+
if (!isSupportedNetwork(chainId)) {
|
|
358
|
+
networkClass.value = 'unsupported';
|
|
359
|
+
if (walletConnected.value) {
|
|
360
|
+
olaresId.clearSigner();
|
|
361
|
+
}
|
|
362
|
+
showToast(
|
|
363
|
+
`⚠️ Unsupported network detected. Please switch to Optimism Mainnet or OP Sepolia Testnet.`,
|
|
364
|
+
'error'
|
|
365
|
+
);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
await reinitializeOlaresId();
|
|
370
|
+
showToast(`✅ Network switched to ${currentNetwork.value}`, 'success');
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const handleAccountsChanged = async (accounts: string[]) => {
|
|
374
|
+
console.log('Accounts changed:', accounts);
|
|
375
|
+
if (accounts.length === 0) {
|
|
376
|
+
disconnectWallet();
|
|
377
|
+
} else if (accounts[0] !== walletAddress.value) {
|
|
378
|
+
walletAddress.value = accounts[0];
|
|
379
|
+
await setSignerToOlaresId();
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const getNetworkName = (chainId: string): string => {
|
|
384
|
+
const networks: Record<string, string> = {
|
|
385
|
+
'0xa': 'Optimism Mainnet',
|
|
386
|
+
'0xaa37dc': 'OP Sepolia Testnet'
|
|
387
|
+
};
|
|
388
|
+
return networks[chainId] || `Unsupported Network (Chain ID: ${chainId})`;
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const isMainnet = (chainId: string): boolean => {
|
|
392
|
+
return chainId === '0xa';
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const isSupportedNetwork = (chainId: string): boolean => {
|
|
396
|
+
return chainId === '0xa' || chainId === '0xaa37dc';
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
const reinitializeOlaresId = async () => {
|
|
400
|
+
if (!isSupportedNetwork(currentChainId.value)) {
|
|
401
|
+
currentNetwork.value = getNetworkName(currentChainId.value);
|
|
402
|
+
networkClass.value = 'unsupported';
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const isMainnetNetwork = isMainnet(currentChainId.value);
|
|
407
|
+
|
|
408
|
+
try {
|
|
409
|
+
if (isMainnetNetwork) {
|
|
410
|
+
olaresId = OlaresID.createMainnet();
|
|
411
|
+
currentNetwork.value = 'Optimism Mainnet';
|
|
412
|
+
networkClass.value = 'mainnet';
|
|
413
|
+
} else {
|
|
414
|
+
olaresId = OlaresID.createTestnet();
|
|
415
|
+
currentNetwork.value = 'OP Sepolia Testnet';
|
|
416
|
+
networkClass.value = 'testnet';
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (walletConnected.value && walletAddress.value) {
|
|
420
|
+
await setSignerToOlaresId();
|
|
421
|
+
console.log(
|
|
422
|
+
'OlaresID reinitialized with new network and signer set'
|
|
423
|
+
);
|
|
424
|
+
} else {
|
|
425
|
+
console.log('OlaresID reinitialized with new network');
|
|
426
|
+
}
|
|
427
|
+
} catch (err) {
|
|
428
|
+
console.error('Error reinitializing OlaresID:', err);
|
|
429
|
+
showToast('Failed to reinitialize OlaresID', 'error');
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
const checkMetaMaskConnection = async () => {
|
|
434
|
+
if (typeof window.ethereum !== 'undefined') {
|
|
435
|
+
try {
|
|
436
|
+
const chainId = await window.ethereum.request({
|
|
437
|
+
method: 'eth_chainId'
|
|
438
|
+
});
|
|
439
|
+
currentChainId.value = chainId;
|
|
440
|
+
await reinitializeOlaresId();
|
|
441
|
+
|
|
442
|
+
const accounts = await window.ethereum.request({
|
|
443
|
+
method: 'eth_accounts'
|
|
444
|
+
});
|
|
445
|
+
if (accounts.length > 0) {
|
|
446
|
+
walletAddress.value = accounts[0];
|
|
447
|
+
walletConnected.value = true;
|
|
448
|
+
await setSignerToOlaresId();
|
|
449
|
+
}
|
|
450
|
+
} catch (err) {
|
|
451
|
+
console.error('Error checking MetaMask connection:', err);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
const connectMetaMask = async () => {
|
|
457
|
+
if (typeof window.ethereum === 'undefined') {
|
|
458
|
+
showToast(
|
|
459
|
+
'MetaMask not detected. Please install MetaMask extension.',
|
|
460
|
+
'error'
|
|
461
|
+
);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
connecting.value = true;
|
|
466
|
+
|
|
467
|
+
try {
|
|
468
|
+
const chainId = await window.ethereum.request({
|
|
469
|
+
method: 'eth_chainId'
|
|
470
|
+
});
|
|
471
|
+
currentChainId.value = chainId;
|
|
472
|
+
|
|
473
|
+
if (!isSupportedNetwork(chainId)) {
|
|
474
|
+
showToast(
|
|
475
|
+
'Unsupported network. Please switch to Optimism Mainnet or OP Sepolia Testnet.',
|
|
476
|
+
'error'
|
|
477
|
+
);
|
|
478
|
+
await reinitializeOlaresId();
|
|
479
|
+
connecting.value = false;
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
await reinitializeOlaresId();
|
|
484
|
+
|
|
485
|
+
const accounts = await window.ethereum.request({
|
|
486
|
+
method: 'eth_requestAccounts'
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
if (accounts.length > 0) {
|
|
490
|
+
walletAddress.value = accounts[0];
|
|
491
|
+
walletConnected.value = true;
|
|
492
|
+
await setSignerToOlaresId();
|
|
493
|
+
showToast(
|
|
494
|
+
`Connected: ${walletAddress.value.substring(
|
|
495
|
+
0,
|
|
496
|
+
6
|
|
497
|
+
)}...${walletAddress.value.substring(38)}`,
|
|
498
|
+
'success'
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
} catch (err: any) {
|
|
502
|
+
console.error('MetaMask connection error:', err);
|
|
503
|
+
showToast(`Failed to connect: ${err.message}`, 'error');
|
|
504
|
+
} finally {
|
|
505
|
+
connecting.value = false;
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const setSignerToOlaresId = async () => {
|
|
510
|
+
try {
|
|
511
|
+
const provider = new ethers.BrowserProvider(window.ethereum);
|
|
512
|
+
const signer = await provider.getSigner();
|
|
513
|
+
olaresId.setSigner(signer);
|
|
514
|
+
console.log('Signer set to OlaresID');
|
|
515
|
+
} catch (err) {
|
|
516
|
+
console.error('Error setting signer:', err);
|
|
517
|
+
throw err;
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
const disconnectWallet = () => {
|
|
522
|
+
walletConnected.value = false;
|
|
523
|
+
walletAddress.value = '';
|
|
524
|
+
olaresId.clearSigner();
|
|
525
|
+
showToast('Wallet disconnected', 'info');
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
const switchToMainnet = async () => {
|
|
529
|
+
try {
|
|
530
|
+
await window.ethereum.request({
|
|
531
|
+
method: 'wallet_switchEthereumChain',
|
|
532
|
+
params: [{ chainId: '0xa' }] // OP Mainnet
|
|
533
|
+
});
|
|
534
|
+
} catch (error: any) {
|
|
535
|
+
if (error.code === 4902) {
|
|
536
|
+
try {
|
|
537
|
+
await window.ethereum.request({
|
|
538
|
+
method: 'wallet_addEthereumChain',
|
|
539
|
+
params: [
|
|
540
|
+
{
|
|
541
|
+
chainId: '0xa',
|
|
542
|
+
chainName: 'Optimism',
|
|
543
|
+
nativeCurrency: {
|
|
544
|
+
name: 'Ethereum',
|
|
545
|
+
symbol: 'ETH',
|
|
546
|
+
decimals: 18
|
|
547
|
+
},
|
|
548
|
+
rpcUrls: ['https://optimism-rpc.publicnode.com'],
|
|
549
|
+
blockExplorerUrls: [
|
|
550
|
+
'https://optimistic.etherscan.io'
|
|
551
|
+
]
|
|
552
|
+
}
|
|
553
|
+
]
|
|
554
|
+
});
|
|
555
|
+
} catch (addError) {
|
|
556
|
+
showToast('Failed to add Optimism network', 'error');
|
|
557
|
+
}
|
|
558
|
+
} else {
|
|
559
|
+
showToast('Failed to switch network', 'error');
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
const switchToTestnet = async () => {
|
|
565
|
+
try {
|
|
566
|
+
await window.ethereum.request({
|
|
567
|
+
method: 'wallet_switchEthereumChain',
|
|
568
|
+
params: [{ chainId: '0xaa37dc' }] // OP Sepolia
|
|
569
|
+
});
|
|
570
|
+
} catch (error: any) {
|
|
571
|
+
if (error.code === 4902) {
|
|
572
|
+
try {
|
|
573
|
+
await window.ethereum.request({
|
|
574
|
+
method: 'wallet_addEthereumChain',
|
|
575
|
+
params: [
|
|
576
|
+
{
|
|
577
|
+
chainId: '0xaa37dc',
|
|
578
|
+
chainName: 'OP Sepolia',
|
|
579
|
+
nativeCurrency: {
|
|
580
|
+
name: 'Ethereum',
|
|
581
|
+
symbol: 'ETH',
|
|
582
|
+
decimals: 18
|
|
583
|
+
},
|
|
584
|
+
rpcUrls: ['https://sepolia.optimism.io'],
|
|
585
|
+
blockExplorerUrls: [
|
|
586
|
+
'https://sepolia-optimistic.etherscan.io'
|
|
587
|
+
]
|
|
588
|
+
}
|
|
589
|
+
]
|
|
590
|
+
});
|
|
591
|
+
} catch (addError) {
|
|
592
|
+
showToast('Failed to add OP Sepolia network', 'error');
|
|
593
|
+
}
|
|
594
|
+
} else {
|
|
595
|
+
showToast('Failed to switch network', 'error');
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
// Domain Info Methods
|
|
601
|
+
const getMetaInfo = async () => {
|
|
602
|
+
loading.metaInfo = true;
|
|
603
|
+
results.domainContext = '';
|
|
604
|
+
try {
|
|
605
|
+
const domain = olaresId.domain(domainName.value);
|
|
606
|
+
const metaInfo = await domain.getMetaInfo();
|
|
607
|
+
results.domainContext = `Meta Info:
|
|
608
|
+
ID: ${metaInfo.id}
|
|
609
|
+
Name: ${metaInfo.name}
|
|
610
|
+
DID: ${metaInfo.did}
|
|
611
|
+
Note: ${metaInfo.note || 'N/A'}
|
|
612
|
+
Allow Subdomain: ${metaInfo.allowSubdomain}`;
|
|
613
|
+
showToast('Meta info retrieved!', 'success');
|
|
614
|
+
} catch (err: any) {
|
|
615
|
+
showToast(`Failed: ${err.message}`, 'error');
|
|
616
|
+
} finally {
|
|
617
|
+
loading.metaInfo = false;
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
const getOwner = async () => {
|
|
622
|
+
loading.owner = true;
|
|
623
|
+
results.domainContext = '';
|
|
624
|
+
try {
|
|
625
|
+
const domain = olaresId.domain(domainName.value);
|
|
626
|
+
const owner = await domain.getOwner();
|
|
627
|
+
results.domainContext = `Owner Address:\n${owner}`;
|
|
628
|
+
showToast('Owner retrieved!', 'success');
|
|
629
|
+
} catch (err: any) {
|
|
630
|
+
showToast(`Failed: ${err.message}`, 'error');
|
|
631
|
+
} finally {
|
|
632
|
+
loading.owner = false;
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
const checkIsOwner = async () => {
|
|
637
|
+
loading.isOwner = true;
|
|
638
|
+
results.domainContext = '';
|
|
639
|
+
try {
|
|
640
|
+
const domain = olaresId.domain(domainName.value);
|
|
641
|
+
const isOwner = await domain.isOwner();
|
|
642
|
+
results.domainContext = `Is Owner: ${isOwner ? 'Yes ✅' : 'No ❌'}`;
|
|
643
|
+
showToast(
|
|
644
|
+
isOwner ? 'You are the owner!' : 'You are not the owner',
|
|
645
|
+
isOwner ? 'success' : 'info'
|
|
646
|
+
);
|
|
647
|
+
} catch (err: any) {
|
|
648
|
+
showToast(`Failed: ${err.message}`, 'error');
|
|
649
|
+
} finally {
|
|
650
|
+
loading.isOwner = false;
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
|
|
654
|
+
// IP Methods
|
|
655
|
+
const setIP = async () => {
|
|
656
|
+
loading.setIP = true;
|
|
657
|
+
results.ip = '';
|
|
658
|
+
try {
|
|
659
|
+
const domain = olaresId.domain(domainName.value);
|
|
660
|
+
const result = await domain.setIP(ipAddress.value);
|
|
661
|
+
if (result.success) {
|
|
662
|
+
results.ip = `IP Set Successfully!
|
|
663
|
+
Transaction Hash: ${result.transactionHash}
|
|
664
|
+
Gas Used: ${result.gasUsed?.toString()}`;
|
|
665
|
+
showToast('IP address set!', 'success');
|
|
666
|
+
} else {
|
|
667
|
+
throw new Error(result.error || 'Failed to set IP');
|
|
668
|
+
}
|
|
669
|
+
} catch (err: any) {
|
|
670
|
+
showToast(`Failed: ${err.message}`, 'error');
|
|
671
|
+
} finally {
|
|
672
|
+
loading.setIP = false;
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
const getIP = async () => {
|
|
677
|
+
loading.getIP = true;
|
|
678
|
+
results.ip = '';
|
|
679
|
+
try {
|
|
680
|
+
const domain = olaresId.domain(domainName.value);
|
|
681
|
+
const ip = await domain.getIP();
|
|
682
|
+
results.ip = ip ? `IP Address: ${ip}` : 'No IP address set';
|
|
683
|
+
showToast('IP retrieved!', 'success');
|
|
684
|
+
} catch (err: any) {
|
|
685
|
+
showToast(`Failed: ${err.message}`, 'error');
|
|
686
|
+
} finally {
|
|
687
|
+
loading.getIP = false;
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
const removeIP = async () => {
|
|
692
|
+
loading.removeIP = true;
|
|
693
|
+
results.ip = '';
|
|
694
|
+
try {
|
|
695
|
+
const domain = olaresId.domain(domainName.value);
|
|
696
|
+
const result = await domain.removeIP();
|
|
697
|
+
if (result.success) {
|
|
698
|
+
results.ip = `IP Removed Successfully!\nTransaction Hash: ${result.transactionHash}`;
|
|
699
|
+
showToast('IP address removed!', 'success');
|
|
700
|
+
} else {
|
|
701
|
+
throw new Error(result.error || 'Failed to remove IP');
|
|
702
|
+
}
|
|
703
|
+
} catch (err: any) {
|
|
704
|
+
showToast(`Failed: ${err.message}`, 'error');
|
|
705
|
+
} finally {
|
|
706
|
+
loading.removeIP = false;
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
// RSA Methods
|
|
711
|
+
const generateRSAKeyPair = async () => {
|
|
712
|
+
loading.generateRSA = true;
|
|
713
|
+
results.rsa = '';
|
|
714
|
+
try {
|
|
715
|
+
const keyPair = await createRsaKeyPair();
|
|
716
|
+
rsaKeyPair.value = {
|
|
717
|
+
publicKey: keyPair.rsaPublicKey,
|
|
718
|
+
privateKey: keyPair.rsaPrivateKey
|
|
719
|
+
};
|
|
720
|
+
results.rsa = 'RSA Key Pair Generated Successfully!';
|
|
721
|
+
showToast('RSA key pair generated!', 'success');
|
|
722
|
+
} catch (err: any) {
|
|
723
|
+
showToast(`Failed: ${err.message}`, 'error');
|
|
724
|
+
} finally {
|
|
725
|
+
loading.generateRSA = false;
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
|
|
729
|
+
const setRSAPublicKey = async () => {
|
|
730
|
+
loading.setRSA = true;
|
|
731
|
+
results.rsa = '';
|
|
732
|
+
try {
|
|
733
|
+
const domain = olaresId.domain(domainName.value);
|
|
734
|
+
const result = await domain.setRSAPublicKey(rsaKeyPair.value.publicKey);
|
|
735
|
+
if (result.success) {
|
|
736
|
+
results.rsa = `RSA Public Key Set Successfully!
|
|
737
|
+
Transaction Hash: ${result.transactionHash}
|
|
738
|
+
Gas Used: ${result.gasUsed?.toString()}`;
|
|
739
|
+
showToast('RSA public key set!', 'success');
|
|
740
|
+
} else {
|
|
741
|
+
throw new Error(result.error || 'Failed to set RSA key');
|
|
742
|
+
}
|
|
743
|
+
} catch (err: any) {
|
|
744
|
+
showToast(`Failed: ${err.message}`, 'error');
|
|
745
|
+
} finally {
|
|
746
|
+
loading.setRSA = false;
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
|
|
750
|
+
const getRSAPublicKey = async () => {
|
|
751
|
+
loading.getRSA = true;
|
|
752
|
+
results.rsa = '';
|
|
753
|
+
try {
|
|
754
|
+
const domain = olaresId.domain(domainName.value);
|
|
755
|
+
const key = await domain.getRSAPublicKey();
|
|
756
|
+
results.rsa = key
|
|
757
|
+
? `RSA Public Key (PEM):\n${key}`
|
|
758
|
+
: 'No RSA public key set';
|
|
759
|
+
showToast('RSA key retrieved!', 'success');
|
|
760
|
+
} catch (err: any) {
|
|
761
|
+
showToast(`Failed: ${err.message}`, 'error');
|
|
762
|
+
} finally {
|
|
763
|
+
loading.getRSA = false;
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
|
|
767
|
+
const removeRSAPublicKey = async () => {
|
|
768
|
+
loading.removeRSA = true;
|
|
769
|
+
results.rsa = '';
|
|
770
|
+
try {
|
|
771
|
+
const domain = olaresId.domain(domainName.value);
|
|
772
|
+
const result = await domain.removeRSAPublicKey();
|
|
773
|
+
if (result.success) {
|
|
774
|
+
results.rsa = `RSA Public Key Removed Successfully!\nTransaction Hash: ${result.transactionHash}`;
|
|
775
|
+
showToast('RSA key removed!', 'success');
|
|
776
|
+
} else {
|
|
777
|
+
throw new Error(result.error || 'Failed to remove RSA key');
|
|
778
|
+
}
|
|
779
|
+
} catch (err: any) {
|
|
780
|
+
showToast(`Failed: ${err.message}`, 'error');
|
|
781
|
+
} finally {
|
|
782
|
+
loading.removeRSA = false;
|
|
783
|
+
}
|
|
784
|
+
};
|
|
785
|
+
</script>
|
|
786
|
+
|
|
787
|
+
<style scoped>
|
|
788
|
+
* {
|
|
789
|
+
box-sizing: border-box;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
.app {
|
|
793
|
+
min-height: 100vh;
|
|
794
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
795
|
+
padding: 2rem 1rem;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
header {
|
|
799
|
+
text-align: center;
|
|
800
|
+
color: white;
|
|
801
|
+
margin-bottom: 2rem;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
header h1 {
|
|
805
|
+
font-size: 2.5rem;
|
|
806
|
+
margin: 0 0 0.5rem 0;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
.subtitle {
|
|
810
|
+
font-size: 1.1rem;
|
|
811
|
+
opacity: 0.9;
|
|
812
|
+
margin: 0;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
main {
|
|
816
|
+
max-width: 900px;
|
|
817
|
+
margin: 0 auto;
|
|
818
|
+
display: flex;
|
|
819
|
+
flex-direction: column;
|
|
820
|
+
gap: 1.5rem;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
.card {
|
|
824
|
+
background: white;
|
|
825
|
+
border-radius: 12px;
|
|
826
|
+
padding: 2rem;
|
|
827
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
.card h2 {
|
|
831
|
+
margin: 0 0 1.5rem 0;
|
|
832
|
+
color: #333;
|
|
833
|
+
font-size: 1.5rem;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
.network-badge {
|
|
837
|
+
display: inline-block;
|
|
838
|
+
padding: 0.5rem 1rem;
|
|
839
|
+
border-radius: 20px;
|
|
840
|
+
font-weight: 600;
|
|
841
|
+
margin-bottom: 1rem;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
.network-badge.mainnet {
|
|
845
|
+
background: #ff9800;
|
|
846
|
+
color: white;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
.network-badge.testnet {
|
|
850
|
+
background: #2196f3;
|
|
851
|
+
color: white;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
.network-badge.unsupported {
|
|
855
|
+
background: #f44336;
|
|
856
|
+
color: white;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
.warning {
|
|
860
|
+
background: #fff3cd;
|
|
861
|
+
color: #856404;
|
|
862
|
+
padding: 0.75rem;
|
|
863
|
+
border-radius: 8px;
|
|
864
|
+
margin-bottom: 1rem;
|
|
865
|
+
border: 1px solid #ffc107;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
.connected-info {
|
|
869
|
+
display: flex;
|
|
870
|
+
flex-direction: column;
|
|
871
|
+
gap: 1rem;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
.success-badge {
|
|
875
|
+
background: #d4edda;
|
|
876
|
+
color: #155724;
|
|
877
|
+
padding: 0.75rem;
|
|
878
|
+
border-radius: 8px;
|
|
879
|
+
border: 1px solid #c3e6cb;
|
|
880
|
+
font-weight: 500;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
.form-group {
|
|
884
|
+
margin-bottom: 1rem;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
.input {
|
|
888
|
+
width: 100%;
|
|
889
|
+
padding: 0.875rem;
|
|
890
|
+
border: 2px solid #e0e0e0;
|
|
891
|
+
border-radius: 8px;
|
|
892
|
+
font-size: 1rem;
|
|
893
|
+
transition: border-color 0.3s;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
.input:focus {
|
|
897
|
+
outline: none;
|
|
898
|
+
border-color: #667eea;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
.input:disabled {
|
|
902
|
+
background: #f5f5f5;
|
|
903
|
+
cursor: not-allowed;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/* Domain Input Section - Top Level */
|
|
907
|
+
.domain-input-section {
|
|
908
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
909
|
+
color: white;
|
|
910
|
+
border: 3px solid #667eea;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
.domain-input-section h2 {
|
|
914
|
+
color: white;
|
|
915
|
+
margin-bottom: 0.5rem;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
.section-description {
|
|
919
|
+
color: rgba(255, 255, 255, 0.9);
|
|
920
|
+
font-size: 0.95rem;
|
|
921
|
+
margin-bottom: 1rem;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
.input-label {
|
|
925
|
+
display: block;
|
|
926
|
+
font-weight: 600;
|
|
927
|
+
margin-bottom: 0.5rem;
|
|
928
|
+
color: white;
|
|
929
|
+
font-size: 1rem;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
.input-large {
|
|
933
|
+
font-size: 1.1rem;
|
|
934
|
+
padding: 1rem;
|
|
935
|
+
background: white;
|
|
936
|
+
color: #333;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
.info-message {
|
|
940
|
+
background: rgba(255, 255, 255, 0.2);
|
|
941
|
+
padding: 0.75rem;
|
|
942
|
+
border-radius: 8px;
|
|
943
|
+
margin-top: 0.75rem;
|
|
944
|
+
text-align: center;
|
|
945
|
+
font-size: 0.95rem;
|
|
946
|
+
color: white;
|
|
947
|
+
border: 1px dashed rgba(255, 255, 255, 0.5);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
/* Management Area - Container for subsections */
|
|
951
|
+
.management-area {
|
|
952
|
+
margin-top: 2rem;
|
|
953
|
+
padding-left: 1.5rem;
|
|
954
|
+
border-left: 4px solid #667eea;
|
|
955
|
+
position: relative;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
.management-area::before {
|
|
959
|
+
content: '';
|
|
960
|
+
position: absolute;
|
|
961
|
+
left: -2px;
|
|
962
|
+
top: 0;
|
|
963
|
+
bottom: 0;
|
|
964
|
+
width: 4px;
|
|
965
|
+
background: linear-gradient(180deg, #667eea 0%, transparent 100%);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/* Subsection - Child cards */
|
|
969
|
+
.subsection {
|
|
970
|
+
position: relative;
|
|
971
|
+
margin-bottom: 1.5rem;
|
|
972
|
+
border-left: 3px solid #e0e0e0;
|
|
973
|
+
padding-left: 1.5rem;
|
|
974
|
+
margin-left: -1.5rem;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
.subsection::before {
|
|
978
|
+
content: '';
|
|
979
|
+
position: absolute;
|
|
980
|
+
left: -1.5rem;
|
|
981
|
+
top: 2rem;
|
|
982
|
+
width: 1rem;
|
|
983
|
+
height: 2px;
|
|
984
|
+
background: #e0e0e0;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
.subsection h3 {
|
|
988
|
+
font-size: 1.25rem;
|
|
989
|
+
color: #555;
|
|
990
|
+
margin-bottom: 1rem;
|
|
991
|
+
font-weight: 600;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
.subsection:hover {
|
|
995
|
+
border-left-color: #667eea;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
.subsection:hover::before {
|
|
999
|
+
background: #667eea;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
.button-grid {
|
|
1003
|
+
display: grid;
|
|
1004
|
+
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
1005
|
+
gap: 0.75rem;
|
|
1006
|
+
margin-bottom: 1rem;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
.button-grid-small {
|
|
1010
|
+
display: flex;
|
|
1011
|
+
gap: 0.5rem;
|
|
1012
|
+
margin-bottom: 1rem;
|
|
1013
|
+
flex-wrap: wrap;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
button {
|
|
1017
|
+
padding: 0.875rem 1.5rem;
|
|
1018
|
+
border: none;
|
|
1019
|
+
border-radius: 8px;
|
|
1020
|
+
font-size: 1rem;
|
|
1021
|
+
font-weight: 600;
|
|
1022
|
+
cursor: pointer;
|
|
1023
|
+
transition: all 0.3s;
|
|
1024
|
+
min-width: 150px;
|
|
1025
|
+
width: 100%;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
button:disabled {
|
|
1029
|
+
opacity: 0.5;
|
|
1030
|
+
cursor: not-allowed;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
.btn-primary {
|
|
1034
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
1035
|
+
color: white;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
.btn-primary:hover:not(:disabled) {
|
|
1039
|
+
transform: translateY(-2px);
|
|
1040
|
+
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
.btn-secondary {
|
|
1044
|
+
background: #f0f0f0;
|
|
1045
|
+
color: #333;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
.btn-secondary:hover:not(:disabled) {
|
|
1049
|
+
background: #e0e0e0;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
.btn-success {
|
|
1053
|
+
background: #4caf50;
|
|
1054
|
+
color: white;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
.btn-success:hover:not(:disabled) {
|
|
1058
|
+
background: #45a049;
|
|
1059
|
+
box-shadow: 0 5px 15px rgba(76, 175, 80, 0.4);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
.btn-danger {
|
|
1063
|
+
background: #f44336;
|
|
1064
|
+
color: white;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
.btn-danger:hover:not(:disabled) {
|
|
1068
|
+
background: #da190b;
|
|
1069
|
+
box-shadow: 0 5px 15px rgba(244, 67, 54, 0.4);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
.btn-purple {
|
|
1073
|
+
background: #9c27b0;
|
|
1074
|
+
color: white;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
.btn-purple:hover:not(:disabled) {
|
|
1078
|
+
background: #7b1fa2;
|
|
1079
|
+
box-shadow: 0 5px 15px rgba(156, 39, 176, 0.4);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
.btn-link {
|
|
1083
|
+
background: transparent;
|
|
1084
|
+
color: #667eea;
|
|
1085
|
+
border: 1px solid #667eea;
|
|
1086
|
+
padding: 0.5rem 1rem;
|
|
1087
|
+
font-size: 0.9rem;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
.btn-link:hover:not(:disabled) {
|
|
1091
|
+
background: #667eea;
|
|
1092
|
+
color: white;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
.result {
|
|
1096
|
+
background: #f5f7fa;
|
|
1097
|
+
padding: 1rem;
|
|
1098
|
+
border-radius: 8px;
|
|
1099
|
+
margin-top: 1rem;
|
|
1100
|
+
border-left: 4px solid #667eea;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
.result.success {
|
|
1104
|
+
background: #d4edda;
|
|
1105
|
+
border-left-color: #4caf50;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
.result.purple {
|
|
1109
|
+
background: #f3e5f5;
|
|
1110
|
+
border-left-color: #9c27b0;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
.result pre {
|
|
1114
|
+
margin: 0;
|
|
1115
|
+
font-family: 'Courier New', monospace;
|
|
1116
|
+
font-size: 0.875rem;
|
|
1117
|
+
white-space: pre-wrap;
|
|
1118
|
+
word-break: break-all;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
.rsa-keys {
|
|
1122
|
+
background: #f3e5f5;
|
|
1123
|
+
padding: 1rem;
|
|
1124
|
+
border-radius: 8px;
|
|
1125
|
+
margin-bottom: 1rem;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
.key-display {
|
|
1129
|
+
margin-bottom: 1rem;
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
.key-display:last-child {
|
|
1133
|
+
margin-bottom: 0;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
.key-display strong {
|
|
1137
|
+
display: block;
|
|
1138
|
+
margin-bottom: 0.5rem;
|
|
1139
|
+
color: #7b1fa2;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
.key-display pre {
|
|
1143
|
+
background: white;
|
|
1144
|
+
padding: 0.75rem;
|
|
1145
|
+
border-radius: 4px;
|
|
1146
|
+
margin: 0;
|
|
1147
|
+
font-size: 0.75rem;
|
|
1148
|
+
overflow-x: auto;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
.toast {
|
|
1152
|
+
position: fixed;
|
|
1153
|
+
bottom: 2rem;
|
|
1154
|
+
right: 2rem;
|
|
1155
|
+
padding: 1rem 1.5rem;
|
|
1156
|
+
border-radius: 8px;
|
|
1157
|
+
color: white;
|
|
1158
|
+
font-weight: 500;
|
|
1159
|
+
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
|
|
1160
|
+
animation: slideIn 0.3s ease-out;
|
|
1161
|
+
max-width: 400px;
|
|
1162
|
+
z-index: 1000;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
.toast.success {
|
|
1166
|
+
background: #4caf50;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
.toast.error {
|
|
1170
|
+
background: #f44336;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
.toast.info {
|
|
1174
|
+
background: #2196f3;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
@keyframes slideIn {
|
|
1178
|
+
from {
|
|
1179
|
+
transform: translateX(400px);
|
|
1180
|
+
opacity: 0;
|
|
1181
|
+
}
|
|
1182
|
+
to {
|
|
1183
|
+
transform: translateX(0);
|
|
1184
|
+
opacity: 1;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
@media (max-width: 768px) {
|
|
1189
|
+
.app {
|
|
1190
|
+
padding: 1rem 0.5rem;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
header h1 {
|
|
1194
|
+
font-size: 2rem;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
.card {
|
|
1198
|
+
padding: 1.5rem;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
.button-grid {
|
|
1202
|
+
grid-template-columns: 1fr;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
.toast {
|
|
1206
|
+
left: 1rem;
|
|
1207
|
+
right: 1rem;
|
|
1208
|
+
bottom: 1rem;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
</style>
|